Add PCI device enumeration and configuration utilities

This commit introduces a new PCI library and associated tools for inspecting and modifying PCI configuration space via Linux sysfs. The following changes were made:

- Implemented `pci.c` and `pci.h` for PCI device management, including enumeration, attribute reading, and configuration space access.
- Added `setpci` command-line utility for reading and writing PCI configuration registers, with support for filtering devices by vendor, device, and slot.
- Created manual pages for `setpci` to document its usage and options.
- Developed test scripts for both `lspci` and `setpci` to ensure functionality and correctness of the new features.

These additions enhance the ability to interact with PCI devices, providing a robust framework for device management and configuration.
This commit is contained in:
2026-03-22 19:04:16 -05:00
parent e2451c3952
commit 0db2ec12c5
10 changed files with 1562 additions and 3 deletions
+14 -3
View File
@@ -104,6 +104,14 @@ xargs_src = files(
xargs_inc = include_directories('src/xargs') xargs_inc = include_directories('src/xargs')
pci_src = files(
'src/pci/pci.c',
'src/pci/lspci.c',
'src/pci/setpci.c',
)
pci_inc = include_directories('src/pci')
gzip_defines = [ gzip_defines = [
'-DNO_BZIP2_SUPPORT', '-DNO_BZIP2_SUPPORT',
'-DNO_ZSTD_SUPPORT', '-DNO_ZSTD_SUPPORT',
@@ -128,8 +136,9 @@ vx = executable('vx',
find_src, find_src,
getdate_gen, getdate_gen,
xargs_src, xargs_src,
pci_src,
compat_src, compat_src,
include_directories : [compat_inc, gzip_inc, grep_inc, find_inc, xargs_inc], include_directories : [compat_inc, gzip_inc, grep_inc, find_inc, xargs_inc, pci_inc],
c_args : compat_args + gzip_defines, c_args : compat_args + gzip_defines,
dependencies : [libm, libz, liblzma, libacl], dependencies : [libm, libz, liblzma, libacl],
install : true, install : true,
@@ -137,7 +146,7 @@ vx = executable('vx',
# Create symlinks for all applets pointing to the vx binary # Create symlinks for all applets pointing to the vx binary
bindir = get_option('bindir') bindir = get_option('bindir')
foreach name : ['patch', 'diff', 'cmp', 'diff3', 'sdiff', 'which', 'gzip', 'gunzip', 'zcat', 'uncompress', 'grep', 'egrep', 'fgrep', 'rgrep', 'find', 'xargs'] foreach name : ['patch', 'diff', 'cmp', 'diff3', 'sdiff', 'which', 'gzip', 'gunzip', 'zcat', 'uncompress', 'grep', 'egrep', 'fgrep', 'rgrep', 'find', 'xargs', 'lspci', 'setpci']
meson.add_install_script('sh', '-c', meson.add_install_script('sh', '-c',
'ln -sf vx "$MESON_INSTALL_DESTDIR_PREFIX/' + bindir + '/' + name + '"', 'ln -sf vx "$MESON_INSTALL_DESTDIR_PREFIX/' + bindir + '/' + name + '"',
) )
@@ -161,6 +170,8 @@ install_man(
'src/grep/grep.1', 'src/grep/grep.1',
'src/find/find.1', 'src/find/find.1',
'src/xargs/xargs.1', 'src/xargs/xargs.1',
'src/pci/lspci.8',
'src/pci/setpci.8',
) )
# --- Gzip shell script utilities --- # --- Gzip shell script utilities ---
@@ -182,7 +193,7 @@ endforeach
test_env = environment() test_env = environment()
test_env.set('VX', vx.full_path()) test_env.set('VX', vx.full_path())
foreach t : ['test_patch', 'test_diff', 'test_cmp', 'test_diff3', 'test_sdiff', 'test_which', 'test_gzip', 'test_grep', 'test_find', 'test_xargs'] foreach t : ['test_patch', 'test_diff', 'test_cmp', 'test_diff3', 'test_sdiff', 'test_which', 'test_gzip', 'test_grep', 'test_find', 'test_xargs', 'test_lspci', 'test_setpci']
test(t, find_program('tests/' + t + '.sh'), test(t, find_program('tests/' + t + '.sh'),
env : test_env, env : test_env,
depends : vx, depends : vx,
+4
View File
@@ -13,6 +13,8 @@ int gzip_main(int argc, char *argv[]);
int grep_main(int argc, char *argv[]); int grep_main(int argc, char *argv[]);
int find_main(int argc, char *argv[]); int find_main(int argc, char *argv[]);
int xargs_main(int argc, char *argv[]); int xargs_main(int argc, char *argv[]);
int lspci_main(int argc, char *argv[]);
int setpci_main(int argc, char *argv[]);
struct applet { struct applet {
const char *name; const char *name;
@@ -37,6 +39,8 @@ static const struct applet applets[] = {
{ "rgrep", grep_main }, { "rgrep", grep_main },
{ "find", find_main }, { "find", find_main },
{ "xargs", xargs_main }, { "xargs", xargs_main },
{ "lspci", lspci_main },
{ "setpci", setpci_main },
}; };
static const size_t n_applets = sizeof(applets) / sizeof(applets[0]); static const size_t n_applets = sizeof(applets) / sizeof(applets[0]);
+43
View File
@@ -0,0 +1,43 @@
.Dd June 7, 2026
.Dt LSPCI 8
.Os
.Sh NAME
.Nm lspci
.Nd list PCI devices using Linux sysfs
.Sh SYNOPSIS
.Nm
.Op Fl Dknvx
.Op Fl d Ar vendor:device[:class]
.Op Fl i Ar file
.Op Fl s Ar slot
.Sh DESCRIPTION
.Nm
lists PCI devices discovered under
.Pa /sys/bus/pci/devices .
It resolves names from
.Pa pci.ids
when an ID database is available.
.Sh OPTIONS
.Bl -tag -width indent
.It Fl D
Always print the PCI domain.
.It Fl d Ar vendor:device[:class]
Filter devices by numeric IDs.
.It Fl i Ar file
Use an alternate
.Pa pci.ids
database.
.It Fl k
Show the kernel driver in use.
.It Fl n
Print numeric identifiers.
Repeat to print both names and numbers.
.It Fl s Ar slot
Filter by slot in
.Ar [domain:]bus:slot[.func]
form.
.It Fl v
Show additional device details.
.It Fl x
Dump the first 64 bytes of configuration space.
.El
+242
View File
@@ -0,0 +1,242 @@
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pci.h"
static void __dead2
usage(void)
{
fprintf(stderr,
"usage: lspci [-Dknvx] [-d vendor:device[:class]] [-i file] [-s slot]\n");
exit(1);
}
static void
format_addr(const struct vx_pci_device *dev, bool show_domain, char *buf,
size_t buflen)
{
if (show_domain || dev->domain != 0) {
snprintf(buf, buflen, "%04x:%02x:%02x.%u", dev->domain, dev->bus,
dev->slot, dev->func);
} else {
snprintf(buf, buflen, "%02x:%02x.%u", dev->bus, dev->slot,
dev->func);
}
}
static void
format_class_text(const char *ids_path, const struct vx_pci_device *dev,
int numeric_mode, char *buf, size_t buflen)
{
char class_name[128];
if (numeric_mode == 1) {
snprintf(buf, buflen, "%04x", (unsigned int)(dev->class_code >> 8));
return;
}
if (vx_pci_lookup_class_name(ids_path, dev->class_code, class_name,
sizeof(class_name)) != 0)
strlcpy(class_name, "Unclassified device", sizeof(class_name));
if (numeric_mode == 2)
snprintf(buf, buflen, "%s [%04x]", class_name,
(unsigned int)(dev->class_code >> 8));
else
strlcpy(buf, class_name, buflen);
}
static void
format_device_text(const char *ids_path, const struct vx_pci_device *dev,
int numeric_mode, char *buf, size_t buflen)
{
char vendor_name[128];
char device_name[192];
int have_vendor, have_device;
if (numeric_mode == 1) {
snprintf(buf, buflen, "%04x:%04x", dev->vendor_id, dev->device_id);
return;
}
have_vendor = vx_pci_lookup_vendor_name(ids_path, dev->vendor_id,
vendor_name, sizeof(vendor_name)) == 0;
have_device = vx_pci_lookup_device_name(ids_path, dev->vendor_id,
dev->device_id, device_name, sizeof(device_name)) == 0;
if (!have_vendor)
snprintf(vendor_name, sizeof(vendor_name), "Vendor %04x",
dev->vendor_id);
if (!have_device)
snprintf(device_name, sizeof(device_name), "Device %04x",
dev->device_id);
if (numeric_mode == 2) {
snprintf(buf, buflen, "%s %s [%04x:%04x]", vendor_name,
device_name, dev->vendor_id, dev->device_id);
} else {
snprintf(buf, buflen, "%s %s", vendor_name, device_name);
}
}
static void
format_subsystem_text(const char *ids_path, const struct vx_pci_device *dev,
char *buf, size_t buflen)
{
char subsystem_name[192];
if (dev->subsystem_vendor_id == 0 && dev->subsystem_device_id == 0) {
buf[0] = '\0';
return;
}
if (vx_pci_lookup_subsystem_name(ids_path, dev->vendor_id, dev->device_id,
dev->subsystem_vendor_id, dev->subsystem_device_id, subsystem_name,
sizeof(subsystem_name)) == 0) {
strlcpy(buf, subsystem_name, buflen);
return;
}
snprintf(buf, buflen, "%04x:%04x", dev->subsystem_vendor_id,
dev->subsystem_device_id);
}
static void
print_hex_dump(const struct vx_pci_device *dev)
{
size_t offset, limit, i;
limit = dev->config_len;
if (limit > 64)
limit = 64;
for (offset = 0; offset < limit; offset += 16) {
printf("\t%02zx:", offset);
for (i = 0; i < 16 && offset + i < limit; i++)
printf(" %02x", dev->config[offset + i]);
putchar('\n');
}
}
static void
print_verbose(const char *ids_path, const struct vx_pci_device *dev,
bool show_driver)
{
char subsystem[192];
char speed[64];
char width[64];
format_subsystem_text(ids_path, dev, subsystem, sizeof(subsystem));
if (subsystem[0] != '\0')
printf("\tSubsystem: %s\n", subsystem);
if (dev->irq > 0)
printf("\tIRQ: %d\n", dev->irq);
if (dev->numa_node >= 0)
printf("\tNUMA node: %d\n", dev->numa_node);
if ((show_driver || dev->driver[0] != '\0') && dev->driver[0] != '\0')
printf("\tKernel driver in use: %s\n", dev->driver);
if (vx_pci_read_attribute(dev, "current_link_speed",
speed, sizeof(speed)) == 0) {
if (vx_pci_read_attribute(dev, "current_link_width",
width, sizeof(width)) == 0)
printf("\tLnkSta: Speed %s, Width %s\n", speed, width);
}
}
int
lspci_main(int argc, char *argv[])
{
struct vx_pci_device_list list;
struct vx_pci_slot_filter slot_filter;
struct vx_pci_id_filter id_filter;
const char *ids_path;
int ch;
bool show_domain, show_driver, show_hex, verbose;
int numeric_mode;
int exitcode;
memset(&slot_filter, 0, sizeof(slot_filter));
memset(&id_filter, 0, sizeof(id_filter));
show_domain = false;
show_driver = false;
show_hex = false;
verbose = false;
numeric_mode = 0;
ids_path = vx_pci_default_ids_path();
optind = 1;
while ((ch = getopt(argc, argv, "Dd:i:kns:vx")) != -1) {
switch (ch) {
case 'D':
show_domain = true;
break;
case 'd':
if (vx_pci_parse_id_filter(optarg, &id_filter) != 0)
errx(1, "invalid device selector '%s'", optarg);
break;
case 'i':
ids_path = optarg;
break;
case 'k':
show_driver = true;
break;
case 'n':
numeric_mode++;
if (numeric_mode > 2)
numeric_mode = 2;
break;
case 's':
if (vx_pci_parse_slot_filter(optarg, &slot_filter) != 0)
errx(1, "invalid slot selector '%s'", optarg);
break;
case 'v':
verbose = true;
break;
case 'x':
show_hex = true;
break;
default:
usage();
}
}
if (optind != argc)
usage();
if (vx_pci_enumerate_devices(&list) != 0)
err(1, "failed to enumerate PCI devices");
exitcode = 0;
for (size_t i = 0; i < list.count; i++) {
struct vx_pci_device *dev;
char addr[16];
char class_text[160];
char device_text[320];
dev = &list.devices[i];
if (!vx_pci_slot_matches(dev, &slot_filter) ||
!vx_pci_id_matches(dev, &id_filter))
continue;
format_addr(dev, show_domain, addr, sizeof(addr));
format_class_text(ids_path, dev, numeric_mode, class_text,
sizeof(class_text));
format_device_text(ids_path, dev, numeric_mode, device_text,
sizeof(device_text));
printf("%s %s: %s", addr, class_text, device_text);
if (dev->revision != 0 || dev->class_code != 0)
printf(" (rev %02x)", dev->revision);
putchar('\n');
if (verbose || show_driver)
print_verbose(ids_path, dev, show_driver);
if (show_hex)
print_hex_dump(dev);
}
vx_pci_free_devices(&list);
return exitcode;
}
+723
View File
@@ -0,0 +1,723 @@
#include <sys/stat.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "pci.h"
#define VX_PCI_SYSFS_ROOT "/sys/bus/pci/devices"
static int
vx_parse_hex_component(const char *text, unsigned long max, int *value)
{
char *end;
unsigned long parsed;
errno = 0;
parsed = strtoul(text, &end, 16);
if (errno != 0 || *text == '\0' || *end != '\0' || parsed > max)
return -1;
*value = (int)parsed;
return 0;
}
static int
vx_read_trimmed_file(const char *path, char *buf, size_t buflen)
{
FILE *fp;
size_t len;
fp = fopen(path, "r");
if (fp == NULL)
return -1;
if (fgets(buf, (int)buflen, fp) == NULL) {
fclose(fp);
return -1;
}
fclose(fp);
len = strlen(buf);
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
buf[len - 1] = '\0';
len--;
}
return 0;
}
int
vx_pci_read_attribute(const struct vx_pci_device *dev, const char *name,
char *buf, size_t buflen)
{
int dirfd, fd;
FILE *fp;
size_t len;
dirfd = open(dev->sysfs_path, O_RDONLY | O_DIRECTORY);
if (dirfd < 0)
return -1;
fd = openat(dirfd, name, O_RDONLY);
close(dirfd);
if (fd < 0)
return -1;
fp = fdopen(fd, "r");
if (fp == NULL) {
close(fd);
return -1;
}
if (fgets(buf, (int)buflen, fp) == NULL) {
fclose(fp);
return -1;
}
fclose(fp);
len = strlen(buf);
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
buf[len - 1] = '\0';
len--;
}
return 0;
}
static int
vx_read_hex_file(const char *dir, const char *name, unsigned long max,
unsigned long *value)
{
char path[PATH_MAX];
char buf[64];
char *end;
snprintf(path, sizeof(path), "%s/%s", dir, name);
if (vx_read_trimmed_file(path, buf, sizeof(buf)) != 0)
return -1;
errno = 0;
*value = strtoul(buf, &end, 0);
if (errno != 0 || *buf == '\0' || *end != '\0' || *value > max)
return -1;
return 0;
}
static int
vx_read_int_file(const char *dir, const char *name, int *value)
{
char path[PATH_MAX];
char buf[64];
char *end;
long parsed;
snprintf(path, sizeof(path), "%s/%s", dir, name);
if (vx_read_trimmed_file(path, buf, sizeof(buf)) != 0)
return -1;
errno = 0;
parsed = strtol(buf, &end, 0);
if (errno != 0 || *buf == '\0' || *end != '\0')
return -1;
*value = (int)parsed;
return 0;
}
static void
vx_read_driver_name(const char *dir, char *buf, size_t buflen)
{
char path[PATH_MAX];
char linkbuf[PATH_MAX];
ssize_t len;
char *base;
buf[0] = '\0';
snprintf(path, sizeof(path), "%s/driver", dir);
len = readlink(path, linkbuf, sizeof(linkbuf) - 1);
if (len < 0)
return;
linkbuf[len] = '\0';
base = strrchr(linkbuf, '/');
if (base == NULL)
base = linkbuf;
else
base++;
strlcpy(buf, base, buflen);
}
static int
vx_load_config_space(const char *dir, struct vx_pci_device *dev)
{
char path[PATH_MAX];
int fd;
ssize_t nread;
snprintf(path, sizeof(path), "%s/config", dir);
fd = open(path, O_RDONLY);
if (fd < 0)
return -1;
nread = pread(fd, dev->config, sizeof(dev->config), 0);
close(fd);
if (nread < 0)
return -1;
dev->config_len = (size_t)nread;
return 0;
}
static int
vx_pci_addr_cmp(const void *lhs, const void *rhs)
{
const struct vx_pci_device *left;
const struct vx_pci_device *right;
left = lhs;
right = rhs;
return strcmp(left->addr, right->addr);
}
int
vx_pci_enumerate_devices(struct vx_pci_device_list *list)
{
DIR *dirp;
struct dirent *dp;
struct vx_pci_device *devices;
size_t count, capacity;
memset(list, 0, sizeof(*list));
dirp = opendir(VX_PCI_SYSFS_ROOT);
if (dirp == NULL)
return -1;
devices = NULL;
count = 0;
capacity = 0;
while ((dp = readdir(dirp)) != NULL) {
struct vx_pci_device dev;
unsigned long value;
struct stat st;
int domain, bus, slot, func;
if (dp->d_name[0] == '.')
continue;
if (sscanf(dp->d_name, "%x:%x:%x.%x",
&domain, &bus, &slot, &func) != 4)
continue;
memset(&dev, 0, sizeof(dev));
strlcpy(dev.addr, dp->d_name, sizeof(dev.addr));
snprintf(dev.sysfs_path, sizeof(dev.sysfs_path), "%s/%s",
VX_PCI_SYSFS_ROOT, dp->d_name);
if (stat(dev.sysfs_path, &st) != 0 || !S_ISDIR(st.st_mode))
continue;
dev.domain = (uint16_t)domain;
dev.bus = (uint8_t)bus;
dev.slot = (uint8_t)slot;
dev.func = (uint8_t)func;
dev.irq = -1;
dev.numa_node = -1;
if (vx_read_hex_file(dev.sysfs_path, "vendor", 0xffff, &value) == 0)
dev.vendor_id = (uint16_t)value;
if (vx_read_hex_file(dev.sysfs_path, "device", 0xffff, &value) == 0)
dev.device_id = (uint16_t)value;
if (vx_read_hex_file(dev.sysfs_path, "subsystem_vendor", 0xffff,
&value) == 0)
dev.subsystem_vendor_id = (uint16_t)value;
if (vx_read_hex_file(dev.sysfs_path, "subsystem_device", 0xffff,
&value) == 0)
dev.subsystem_device_id = (uint16_t)value;
if (vx_read_int_file(dev.sysfs_path, "irq", &dev.irq) != 0)
dev.irq = -1;
if (vx_read_int_file(dev.sysfs_path, "numa_node",
&dev.numa_node) != 0)
dev.numa_node = -1;
vx_read_driver_name(dev.sysfs_path, dev.driver, sizeof(dev.driver));
(void)vx_load_config_space(dev.sysfs_path, &dev);
if (dev.config_len >= 0x10) {
dev.revision = dev.config[0x08];
dev.class_code = ((uint32_t)dev.config[0x0b] << 16) |
((uint32_t)dev.config[0x0a] << 8) | dev.config[0x09];
dev.header_type = dev.config[0x0e];
}
if (count == capacity) {
size_t newcap;
struct vx_pci_device *newdevs;
newcap = (capacity == 0) ? 16 : capacity * 2;
newdevs = reallocarray(devices, newcap, sizeof(*newdevs));
if (newdevs == NULL) {
free(devices);
closedir(dirp);
return -1;
}
devices = newdevs;
capacity = newcap;
}
devices[count++] = dev;
}
closedir(dirp);
qsort(devices, count, sizeof(*devices), vx_pci_addr_cmp);
list->devices = devices;
list->count = count;
return 0;
}
void
vx_pci_free_devices(struct vx_pci_device_list *list)
{
free(list->devices);
list->devices = NULL;
list->count = 0;
}
int
vx_pci_parse_slot_filter(const char *spec, struct vx_pci_slot_filter *filter)
{
char *copy, *last, *middle;
memset(filter, 0, sizeof(*filter));
filter->domain = -1;
filter->bus = -1;
filter->slot = -1;
filter->func = -1;
if (spec == NULL || *spec == '\0')
return -1;
copy = strdup(spec);
if (copy == NULL)
return -1;
last = strchr(copy, '.');
if (last != NULL) {
*last++ = '\0';
if (vx_parse_hex_component(last, 7, &filter->func) != 0) {
free(copy);
return -1;
}
}
last = strrchr(copy, ':');
if (last != NULL) {
*last++ = '\0';
if (vx_parse_hex_component(last, 0x1f, &filter->slot) != 0) {
free(copy);
return -1;
}
middle = strrchr(copy, ':');
if (middle != NULL) {
*middle++ = '\0';
if (vx_parse_hex_component(middle, 0xff,
&filter->bus) != 0 ||
vx_parse_hex_component(copy, 0xffff,
&filter->domain) != 0) {
free(copy);
return -1;
}
} else {
if (vx_parse_hex_component(copy, 0xff,
&filter->bus) != 0) {
free(copy);
return -1;
}
}
} else {
if (vx_parse_hex_component(copy, 0x1f, &filter->slot) != 0) {
free(copy);
return -1;
}
}
filter->has_filter = true;
free(copy);
return 0;
}
int
vx_pci_parse_id_filter(const char *spec, struct vx_pci_id_filter *filter)
{
char *copy, *vendor, *device, *klass, *next;
memset(filter, 0, sizeof(*filter));
filter->vendor = -1;
filter->device = -1;
filter->class_value = -1;
filter->class_digits = 0;
if (spec == NULL || *spec == '\0')
return -1;
copy = strdup(spec);
if (copy == NULL)
return -1;
vendor = copy;
next = strchr(vendor, ':');
if (next == NULL) {
free(copy);
return -1;
}
*next++ = '\0';
device = next;
klass = strchr(device, ':');
if (klass != NULL)
*klass++ = '\0';
if (*vendor != '\0' &&
vx_parse_hex_component(vendor, 0xffff, &filter->vendor) != 0) {
free(copy);
return -1;
}
if (*device != '\0' &&
vx_parse_hex_component(device, 0xffff, &filter->device) != 0) {
free(copy);
return -1;
}
if (klass != NULL && *klass != '\0') {
if (vx_parse_hex_component(klass, 0xffffff,
&filter->class_value) != 0) {
free(copy);
return -1;
}
filter->class_digits = (int)strlen(klass);
if (filter->class_digits != 4 && filter->class_digits != 6) {
free(copy);
return -1;
}
}
filter->has_filter = true;
free(copy);
return 0;
}
bool
vx_pci_slot_matches(const struct vx_pci_device *dev,
const struct vx_pci_slot_filter *filter)
{
if (!filter->has_filter)
return true;
if (filter->domain >= 0 && dev->domain != (uint16_t)filter->domain)
return false;
if (filter->bus >= 0 && dev->bus != (uint8_t)filter->bus)
return false;
if (filter->slot >= 0 && dev->slot != (uint8_t)filter->slot)
return false;
if (filter->func >= 0 && dev->func != (uint8_t)filter->func)
return false;
return true;
}
bool
vx_pci_id_matches(const struct vx_pci_device *dev,
const struct vx_pci_id_filter *filter)
{
if (!filter->has_filter)
return true;
if (filter->vendor >= 0 && dev->vendor_id != (uint16_t)filter->vendor)
return false;
if (filter->device >= 0 && dev->device_id != (uint16_t)filter->device)
return false;
if (filter->class_value >= 0) {
if (filter->class_digits == 4) {
if ((int)(dev->class_code >> 8) != filter->class_value)
return false;
} else if ((int)dev->class_code != filter->class_value) {
return false;
}
}
return true;
}
const char *
vx_pci_default_ids_path(void)
{
static const char *const candidates[] = {
"/usr/share/hwdata/pci.ids",
"/usr/share/misc/pci.ids",
"/usr/share/pci.ids",
};
size_t i;
for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) {
if (access(candidates[i], R_OK) == 0)
return candidates[i];
}
return NULL;
}
static void
vx_copy_name(char *dst, size_t dstlen, const char *src)
{
size_t len;
while (*src == ' ' || *src == '\t')
src++;
len = strcspn(src, "\r\n");
if (len >= dstlen)
len = dstlen - 1;
memcpy(dst, src, len);
dst[len] = '\0';
}
int
vx_pci_lookup_vendor_name(const char *ids_path, uint16_t vendor_id,
char *buf, size_t buflen)
{
FILE *fp;
char line[512];
unsigned int parsed;
if (ids_path == NULL)
return -1;
fp = fopen(ids_path, "r");
if (fp == NULL)
return -1;
while (fgets(line, sizeof(line), fp) != NULL) {
if (line[0] == '#' || line[0] == '\n' || line[0] == 'C' ||
line[0] == '\t')
continue;
if (sscanf(line, "%x", &parsed) == 1 && parsed == vendor_id) {
char *name = strchr(line, ' ');
if (name == NULL)
continue;
vx_copy_name(buf, buflen, name);
fclose(fp);
return 0;
}
}
fclose(fp);
return -1;
}
int
vx_pci_lookup_device_name(const char *ids_path, uint16_t vendor_id,
uint16_t device_id, char *buf, size_t buflen)
{
FILE *fp;
char line[512];
unsigned int parsed_vendor, parsed_device;
bool in_vendor;
if (ids_path == NULL)
return -1;
fp = fopen(ids_path, "r");
if (fp == NULL)
return -1;
in_vendor = false;
while (fgets(line, sizeof(line), fp) != NULL) {
if (line[0] == '#' || line[0] == '\n')
continue;
if (line[0] == 'C') {
in_vendor = false;
continue;
}
if (line[0] != '\t') {
in_vendor = (sscanf(line, "%x", &parsed_vendor) == 1 &&
parsed_vendor == vendor_id);
continue;
}
if (!in_vendor || line[1] == '\t')
continue;
if (sscanf(line + 1, "%x", &parsed_device) == 1 &&
parsed_device == device_id) {
char *name = strchr(line + 1, ' ');
if (name == NULL)
continue;
vx_copy_name(buf, buflen, name);
fclose(fp);
return 0;
}
}
fclose(fp);
return -1;
}
int
vx_pci_lookup_subsystem_name(const char *ids_path, uint16_t vendor_id,
uint16_t device_id, uint16_t subsystem_vendor_id,
uint16_t subsystem_device_id, char *buf, size_t buflen)
{
FILE *fp;
char line[512];
unsigned int parsed_vendor, parsed_device, parsed_subvendor, parsed_subdevice;
bool in_vendor, in_device;
if (ids_path == NULL)
return -1;
fp = fopen(ids_path, "r");
if (fp == NULL)
return -1;
in_vendor = false;
in_device = false;
while (fgets(line, sizeof(line), fp) != NULL) {
if (line[0] == '#' || line[0] == '\n')
continue;
if (line[0] == 'C') {
in_vendor = false;
in_device = false;
continue;
}
if (line[0] != '\t') {
in_vendor = (sscanf(line, "%x", &parsed_vendor) == 1 &&
parsed_vendor == vendor_id);
in_device = false;
continue;
}
if (line[1] != '\t') {
in_device = false;
if (in_vendor &&
sscanf(line + 1, "%x", &parsed_device) == 1 &&
parsed_device == device_id)
in_device = true;
continue;
}
if (!in_vendor || !in_device)
continue;
if (sscanf(line + 2, "%x %x", &parsed_subvendor,
&parsed_subdevice) == 2 &&
parsed_subvendor == subsystem_vendor_id &&
parsed_subdevice == subsystem_device_id) {
char *name = strchr(line + 2, ' ');
if (name == NULL)
continue;
name = strchr(name + 1, ' ');
if (name == NULL)
continue;
vx_copy_name(buf, buflen, name);
fclose(fp);
return 0;
}
}
fclose(fp);
return -1;
}
int
vx_pci_lookup_class_name(const char *ids_path, uint32_t class_code,
char *buf, size_t buflen)
{
FILE *fp;
char line[512];
unsigned int base_class, subclass, parsed;
bool in_class;
char base_name[128];
if (ids_path == NULL)
return -1;
fp = fopen(ids_path, "r");
if (fp == NULL)
return -1;
base_class = (class_code >> 16) & 0xff;
subclass = (class_code >> 8) & 0xff;
in_class = false;
base_name[0] = '\0';
while (fgets(line, sizeof(line), fp) != NULL) {
if (line[0] == '#' || line[0] == '\n')
continue;
if (line[0] == 'C' && line[1] == ' ') {
if (sscanf(line + 2, "%x", &parsed) == 1 &&
parsed == base_class) {
char *name = strchr(line + 2, ' ');
in_class = true;
if (name != NULL)
vx_copy_name(base_name,
sizeof(base_name), name);
} else {
in_class = false;
}
continue;
}
if (!in_class || line[0] != '\t' || line[1] == '\t')
continue;
if (sscanf(line + 1, "%x", &parsed) == 1 && parsed == subclass) {
char *name = strchr(line + 1, ' ');
if (name == NULL)
continue;
vx_copy_name(buf, buflen, name);
fclose(fp);
return 0;
}
}
fclose(fp);
if (base_name[0] == '\0')
return -1;
strlcpy(buf, base_name, buflen);
return 0;
}
int
vx_pci_read_config(const struct vx_pci_device *dev, uint16_t offset,
unsigned int width, uint32_t *value)
{
uint32_t result;
if (width != 1 && width != 2 && width != 4)
return -1;
if ((size_t)offset + width > dev->config_len)
return -1;
result = 0;
switch (width) {
case 1:
result = dev->config[offset];
break;
case 2:
result = (uint32_t)dev->config[offset] |
((uint32_t)dev->config[offset + 1] << 8);
break;
case 4:
result = (uint32_t)dev->config[offset] |
((uint32_t)dev->config[offset + 1] << 8) |
((uint32_t)dev->config[offset + 2] << 16) |
((uint32_t)dev->config[offset + 3] << 24);
break;
}
*value = result;
return 0;
}
int
vx_pci_write_config(struct vx_pci_device *dev, uint16_t offset,
unsigned int width, uint32_t value, uint32_t mask)
{
uint8_t bytes[4];
uint32_t current;
uint32_t merged;
int dirfd, fd;
ssize_t nwritten;
unsigned int i;
if (vx_pci_read_config(dev, offset, width, &current) != 0)
return -1;
merged = (current & ~mask) | (value & mask);
for (i = 0; i < width; i++)
bytes[i] = (merged >> (i * 8)) & 0xff;
dirfd = open(dev->sysfs_path, O_RDONLY | O_DIRECTORY);
if (dirfd < 0)
return -1;
fd = openat(dirfd, "config", O_RDWR);
close(dirfd);
if (fd < 0)
return -1;
nwritten = pwrite(fd, bytes, width, offset);
close(fd);
if (nwritten != (ssize_t)width)
return -1;
for (i = 0; i < width; i++)
dev->config[offset + i] = bytes[i];
return 0;
}
+81
View File
@@ -0,0 +1,81 @@
#ifndef VX_PCI_H
#define VX_PCI_H
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define VX_PCI_CONFIG_SPACE_SIZE 4096
struct vx_pci_slot_filter {
bool has_filter;
int domain;
int bus;
int slot;
int func;
};
struct vx_pci_id_filter {
bool has_filter;
int vendor;
int device;
int class_value;
int class_digits;
};
struct vx_pci_device {
char addr[16];
char sysfs_path[PATH_MAX];
uint16_t domain;
uint8_t bus;
uint8_t slot;
uint8_t func;
uint16_t vendor_id;
uint16_t device_id;
uint16_t subsystem_vendor_id;
uint16_t subsystem_device_id;
uint32_t class_code;
uint8_t revision;
uint8_t header_type;
int irq;
int numa_node;
size_t config_len;
uint8_t config[VX_PCI_CONFIG_SPACE_SIZE];
char driver[128];
};
struct vx_pci_device_list {
struct vx_pci_device *devices;
size_t count;
};
int vx_pci_enumerate_devices(struct vx_pci_device_list *list);
void vx_pci_free_devices(struct vx_pci_device_list *list);
int vx_pci_parse_slot_filter(const char *spec, struct vx_pci_slot_filter *filter);
int vx_pci_parse_id_filter(const char *spec, struct vx_pci_id_filter *filter);
bool vx_pci_slot_matches(const struct vx_pci_device *dev,
const struct vx_pci_slot_filter *filter);
bool vx_pci_id_matches(const struct vx_pci_device *dev,
const struct vx_pci_id_filter *filter);
const char *vx_pci_default_ids_path(void);
int vx_pci_lookup_vendor_name(const char *ids_path, uint16_t vendor_id,
char *buf, size_t buflen);
int vx_pci_lookup_device_name(const char *ids_path, uint16_t vendor_id,
uint16_t device_id, char *buf, size_t buflen);
int vx_pci_lookup_subsystem_name(const char *ids_path, uint16_t vendor_id,
uint16_t device_id, uint16_t subsystem_vendor_id,
uint16_t subsystem_device_id, char *buf, size_t buflen);
int vx_pci_lookup_class_name(const char *ids_path, uint32_t class_code,
char *buf, size_t buflen);
int vx_pci_read_attribute(const struct vx_pci_device *dev, const char *name,
char *buf, size_t buflen);
int vx_pci_read_config(const struct vx_pci_device *dev, uint16_t offset,
unsigned int width, uint32_t *value);
int vx_pci_write_config(struct vx_pci_device *dev, uint16_t offset,
unsigned int width, uint32_t value, uint32_t mask);
#endif
+36
View File
@@ -0,0 +1,36 @@
.Dd June 7, 2026
.Dt SETPCI 8
.Os
.Sh NAME
.Nm setpci
.Nd inspect and modify PCI configuration space via Linux sysfs
.Sh SYNOPSIS
.Nm
.Op Fl Dfvr
.Op Fl d Ar vendor:device[:class]
.Op Fl s Ar slot
.Ar reg Ns Op = Ns Ar value Ns Op :mask
.Sh DESCRIPTION
.Nm
reads or writes PCI configuration registers by operating on
.Pa /sys/bus/pci/devices/*/config .
.Sh OPTIONS
.Bl -tag -width indent
.It Fl D
Dry run.
Report matches without committing writes.
.It Fl d Ar vendor:device[:class]
Filter devices by numeric IDs.
.It Fl f
Do not fail when no device matches.
.It Fl r
Accepted for compatibility.
.It Fl s Ar slot
Filter by slot in
.Ar [domain:]bus:slot[.func]
form.
.It Fl v
Print dry-run write details to standard error.
.It Fl -dumpregs
List supported register names.
.El
+338
View File
@@ -0,0 +1,338 @@
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "pci.h"
struct reg_def {
const char *name;
uint16_t offset;
unsigned int width;
};
static const struct reg_def reg_defs[] = {
{ "VENDOR_ID", 0x00, 2 },
{ "DEVICE_ID", 0x02, 2 },
{ "COMMAND", 0x04, 2 },
{ "STATUS", 0x06, 2 },
{ "REVISION", 0x08, 1 },
{ "CLASS_PROG", 0x09, 1 },
{ "CLASS_DEVICE", 0x0a, 2 },
{ "CACHE_LINE_SIZE", 0x0c, 1 },
{ "LATENCY_TIMER", 0x0d, 1 },
{ "HEADER_TYPE", 0x0e, 1 },
{ "BIST", 0x0f, 1 },
{ "BASE_ADDRESS_0", 0x10, 4 },
{ "BASE_ADDRESS_1", 0x14, 4 },
{ "BASE_ADDRESS_2", 0x18, 4 },
{ "BASE_ADDRESS_3", 0x1c, 4 },
{ "BASE_ADDRESS_4", 0x20, 4 },
{ "BASE_ADDRESS_5", 0x24, 4 },
{ "SUBSYSTEM_VENDOR_ID", 0x2c, 2 },
{ "SUBSYSTEM_ID", 0x2e, 2 },
{ "CAPABILITIES", 0x34, 1 },
{ "INTERRUPT_LINE", 0x3c, 1 },
{ "INTERRUPT_PIN", 0x3d, 1 },
{ "IRQ_LINE", 0x3c, 1 },
};
static const struct option long_options[] = {
{ "dumpregs", no_argument, NULL, 1 },
{ NULL, 0, NULL, 0 },
};
struct reg_op {
uint16_t offset;
unsigned int width;
bool is_write;
uint32_t value;
uint32_t mask;
};
static void __dead2
usage(void)
{
fprintf(stderr,
"usage: setpci [-Dfvr] [-d vendor:device[:class]] [-s slot] reg[=value[:mask]] ...\n");
exit(1);
}
static void
dumpregs(void)
{
size_t i;
printf("cap pos w name\n");
for (i = 0; i < sizeof(reg_defs) / sizeof(reg_defs[0]); i++) {
printf(" %02x %c %s\n", reg_defs[i].offset,
reg_defs[i].width == 1 ? 'B' :
reg_defs[i].width == 2 ? 'W' : 'L',
reg_defs[i].name);
}
}
static const struct reg_def *
find_reg_def(const char *name)
{
size_t i;
for (i = 0; i < sizeof(reg_defs) / sizeof(reg_defs[0]); i++) {
if (strcasecmp(reg_defs[i].name, name) == 0)
return &reg_defs[i];
}
return NULL;
}
static int
parse_width_char(char width_char, unsigned int *width)
{
switch (width_char) {
case 'B':
case 'b':
*width = 1;
return 0;
case 'W':
case 'w':
*width = 2;
return 0;
case 'L':
case 'l':
*width = 4;
return 0;
default:
return -1;
}
}
static int
parse_reg_spec(const char *text, struct reg_op *op)
{
char *copy, *base, *eq, *plus, *dot, *mask;
const struct reg_def *def;
char *end;
unsigned long parsed;
memset(op, 0, sizeof(*op));
copy = strdup(text);
if (copy == NULL)
return -1;
eq = strchr(copy, '=');
if (eq != NULL)
*eq++ = '\0';
base = copy;
dot = strrchr(base, '.');
if (dot != NULL) {
*dot++ = '\0';
if (parse_width_char(*dot, &op->width) != 0) {
free(copy);
return -1;
}
}
plus = strrchr(base, '+');
if (plus != NULL)
*plus++ = '\0';
def = find_reg_def(base);
if (def != NULL) {
op->offset = def->offset;
if (op->width == 0)
op->width = def->width;
} else {
errno = 0;
parsed = strtoul(base, &end, 16);
if (errno != 0 || *base == '\0' || *end != '\0' || parsed > 0xfff) {
free(copy);
return -1;
}
op->offset = (uint16_t)parsed;
if (op->width == 0)
op->width = 2;
}
if (plus != NULL) {
errno = 0;
parsed = strtoul(plus, &end, 16);
if (errno != 0 || *plus == '\0' || *end != '\0' ||
parsed > 0xfff) {
free(copy);
return -1;
}
op->offset += (uint16_t)parsed;
}
if (eq != NULL) {
op->is_write = true;
mask = strchr(eq, ':');
if (mask != NULL)
*mask++ = '\0';
errno = 0;
parsed = strtoul(eq, &end, 16);
if (errno != 0 || *eq == '\0' || *end != '\0') {
free(copy);
return -1;
}
op->value = (uint32_t)parsed;
if (mask != NULL) {
errno = 0;
parsed = strtoul(mask, &end, 16);
if (errno != 0 || *mask == '\0' || *end != '\0') {
free(copy);
return -1;
}
op->mask = (uint32_t)parsed;
} else if (op->width == 1) {
op->mask = 0xff;
} else if (op->width == 2) {
op->mask = 0xffff;
} else {
op->mask = 0xffffffffU;
}
}
free(copy);
return 0;
}
static void
print_value(uint32_t value, unsigned int width)
{
if (width == 1)
printf("%02x\n", value & 0xff);
else if (width == 2)
printf("%04x\n", value & 0xffff);
else
printf("%08x\n", value);
}
int
setpci_main(int argc, char *argv[])
{
struct vx_pci_device_list list;
struct vx_pci_slot_filter global_slot_filter;
struct vx_pci_slot_filter current_slot_filter;
struct vx_pci_id_filter id_filter;
bool dry_run, verbose, ignore_missing;
int ch;
int exitcode;
size_t i;
memset(&global_slot_filter, 0, sizeof(global_slot_filter));
memset(&current_slot_filter, 0, sizeof(current_slot_filter));
memset(&id_filter, 0, sizeof(id_filter));
dry_run = false;
verbose = false;
ignore_missing = false;
optind = 1;
while ((ch = getopt_long(argc, argv, "Dd:frs:v",
long_options, NULL)) != -1) {
switch (ch) {
case 'D':
dry_run = true;
break;
case 'd':
if (vx_pci_parse_id_filter(optarg, &id_filter) != 0)
errx(1, "invalid device selector '%s'", optarg);
break;
case 'f':
ignore_missing = true;
break;
case 'r':
break;
case 's':
if (vx_pci_parse_slot_filter(optarg,
&global_slot_filter) != 0)
errx(1, "invalid slot selector '%s'", optarg);
current_slot_filter = global_slot_filter;
break;
case 'v':
verbose = true;
break;
case 1:
dumpregs();
return 0;
default:
usage();
}
}
if (optind >= argc)
usage();
if (vx_pci_enumerate_devices(&list) != 0)
err(1, "failed to enumerate PCI devices");
exitcode = 0;
current_slot_filter = global_slot_filter;
for (i = (size_t)optind; i < (size_t)argc; i++) {
struct reg_op op;
char *token;
size_t matched;
token = argv[i];
if (token[0] != '\0' && token[strlen(token) - 1] == ':') {
char slotbuf[64];
if (strlen(token) >= sizeof(slotbuf))
errx(1, "device selector too long");
strlcpy(slotbuf, token, sizeof(slotbuf));
slotbuf[strlen(slotbuf) - 1] = '\0';
if (vx_pci_parse_slot_filter(slotbuf,
&current_slot_filter) != 0)
errx(1, "invalid device selector '%s'", token);
continue;
}
if (parse_reg_spec(token, &op) != 0)
errx(1, "invalid register spec '%s'", token);
matched = 0;
for (size_t di = 0; di < list.count; di++) {
struct vx_pci_device *dev;
uint32_t current;
dev = &list.devices[di];
if (!vx_pci_slot_matches(dev, &current_slot_filter) ||
!vx_pci_id_matches(dev, &id_filter))
continue;
if (vx_pci_read_config(dev, op.offset, op.width,
&current) != 0)
continue;
matched++;
if (!op.is_write) {
print_value(current, op.width);
continue;
}
if (dry_run) {
if (verbose) {
uint32_t next;
next = (current & ~op.mask) |
(op.value & op.mask);
fprintf(stderr, "%s %02x.%c %08x -> %08x\n",
dev->addr, op.offset,
op.width == 1 ? 'B' :
op.width == 2 ? 'W' : 'L',
current, next);
}
continue;
}
if (vx_pci_write_config(dev, op.offset, op.width, op.value,
op.mask) != 0)
err(1, "failed to write %s", dev->addr);
}
if (matched == 0 && !ignore_missing)
errx(1, "no devices matched '%s'", token);
}
vx_pci_free_devices(&list);
return exitcode;
}
+48
View File
@@ -0,0 +1,48 @@
#!/bin/sh
_test_name="lspci"
. "$(dirname "$0")/harness.sh"
setup_tmpdir
LSPCI="$VX lspci"
echo "Running lspci tests..."
dev_path="/sys/bus/pci/devices/$(ls /sys/bus/pci/devices | head -n 1)"
slot="$(basename "$dev_path")"
short_slot="$(printf '%s' "$slot" | sed 's/^0000://')"
vendor="$(sed 's/^0x//' "$dev_path/vendor")"
device="$(sed 's/^0x//' "$dev_path/device")"
class="$(sed 's/^0x//' "$dev_path/class" | cut -c1-4)"
out="$($LSPCI | sed -n '1p')"
if [ -n "$out" ]; then
pass "basic: lists at least one device"
else
fail "basic: lists at least one device" "got empty output"
fi
out="$($LSPCI -D -s "$slot")"
case "$out" in
"$slot "*) pass "filter: -D preserves domain-qualified slot" ;;
*) fail "filter: -D preserves domain-qualified slot" "got: $out" ;;
esac
out="$($LSPCI -n -s "$short_slot")"
expected="$short_slot $class: $vendor:$device"
case "$out" in
"$expected"*) pass "numeric: -n shows class and ids" ;;
*) fail "numeric: -n shows class and ids" "expected prefix: $expected, got: $out" ;;
esac
out="$($LSPCI -d "$vendor:$device" | sed -n '1p')"
case "$out" in
*"$vendor"*|*"$device"*) pass "filter: -d finds matching device" ;;
"") fail "filter: -d finds matching device" "got empty output" ;;
*) pass "filter: -d finds matching device" ;;
esac
hex_out="$($LSPCI -x -s "$short_slot")"
printf '%s\n' "$hex_out" > hex.out
check_grep " 00:" hex.out "hex: emits config dump"
summary
+33
View File
@@ -0,0 +1,33 @@
#!/bin/sh
_test_name="setpci"
. "$(dirname "$0")/harness.sh"
setup_tmpdir
SETPCI="$VX setpci"
echo "Running setpci tests..."
dev_path="/sys/bus/pci/devices/$(ls /sys/bus/pci/devices | head -n 1)"
slot="$(basename "$dev_path")"
short_slot="$(printf '%s' "$slot" | sed 's/^0000://')"
vendor="$(sed 's/^0x//' "$dev_path/vendor")"
device="$(sed 's/^0x//' "$dev_path/device")"
command_reg="$(od -An -tx2 -N2 -j4 "$dev_path/config" | tr -d ' \n')"
$SETPCI --dumpregs > dumpregs.out
check_grep "VENDOR_ID" dumpregs.out "dumpregs: lists standard names"
out="$($SETPCI -s "$short_slot" VENDOR_ID)"
check_eq "$out" "$vendor" "read: VENDOR_ID matches sysfs"
out="$($SETPCI -s "$short_slot" DEVICE_ID)"
check_eq "$out" "$device" "read: DEVICE_ID matches sysfs"
out="$($SETPCI -s "$short_slot" 04.w)"
check_eq "$out" "$command_reg" "read: numeric register syntax works"
check_exit "dry-run: masked write succeeds" 0 $SETPCI -D -s "$short_slot" COMMAND="$command_reg:ffff"
after="$($SETPCI -s "$short_slot" COMMAND)"
check_eq "$after" "$command_reg" "dry-run: command register unchanged"
summary