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:
+14
-3
@@ -104,6 +104,14 @@ xargs_src = files(
|
||||
|
||||
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 = [
|
||||
'-DNO_BZIP2_SUPPORT',
|
||||
'-DNO_ZSTD_SUPPORT',
|
||||
@@ -128,8 +136,9 @@ vx = executable('vx',
|
||||
find_src,
|
||||
getdate_gen,
|
||||
xargs_src,
|
||||
pci_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,
|
||||
dependencies : [libm, libz, liblzma, libacl],
|
||||
install : true,
|
||||
@@ -137,7 +146,7 @@ vx = executable('vx',
|
||||
|
||||
# Create symlinks for all applets pointing to the vx binary
|
||||
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',
|
||||
'ln -sf vx "$MESON_INSTALL_DESTDIR_PREFIX/' + bindir + '/' + name + '"',
|
||||
)
|
||||
@@ -161,6 +170,8 @@ install_man(
|
||||
'src/grep/grep.1',
|
||||
'src/find/find.1',
|
||||
'src/xargs/xargs.1',
|
||||
'src/pci/lspci.8',
|
||||
'src/pci/setpci.8',
|
||||
)
|
||||
|
||||
# --- Gzip shell script utilities ---
|
||||
@@ -182,7 +193,7 @@ endforeach
|
||||
test_env = environment()
|
||||
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'),
|
||||
env : test_env,
|
||||
depends : vx,
|
||||
|
||||
@@ -13,6 +13,8 @@ int gzip_main(int argc, char *argv[]);
|
||||
int grep_main(int argc, char *argv[]);
|
||||
int find_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 {
|
||||
const char *name;
|
||||
@@ -37,6 +39,8 @@ static const struct applet applets[] = {
|
||||
{ "rgrep", grep_main },
|
||||
{ "find", find_main },
|
||||
{ "xargs", xargs_main },
|
||||
{ "lspci", lspci_main },
|
||||
{ "setpci", setpci_main },
|
||||
};
|
||||
|
||||
static const size_t n_applets = sizeof(applets) / sizeof(applets[0]);
|
||||
|
||||
@@ -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
@@ -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
@@ -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, ¤t) != 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ®_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(¤t_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,
|
||||
¤t_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, ¤t_slot_filter) ||
|
||||
!vx_pci_id_matches(dev, &id_filter))
|
||||
continue;
|
||||
if (vx_pci_read_config(dev, op.offset, op.width,
|
||||
¤t) != 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user