AxlPci — PCI/PCIe config-space access
PCI / PCIe configuration-space access via ECAM.
Header: <axl/axl-pci.h>. Lazy on first call: AxlPci consults
AxlAcpi for the MCFG table to find each segment’s ECAM base
address, then computes register offsets directly. Never falls
back to legacy 0xCF8/0xCFC port pair; on platforms without MCFG
(rare on modern hardware), every axl_pci_* call returns -1.
Cursor-style enumeration matches axl_smbios_find_next and
axl_acpi_find_next:
AxlPciAddr *p = NULL;
while ((p = axl_pci_next(p)) != NULL) {
uint16_t vid, did;
axl_pci_read_config_16(*p, 0x00, &vid);
axl_pci_read_config_16(*p, 0x02, &did);
axl_printf("%04x:%02x:%02x.%u %04x:%04x\n",
p->seg, p->bus, p->dev, p->func, vid, did);
}
axl_pci_next skips empty slots (vendor ID 0xFFFF) and honours
the multi-function header bit, so single-function devices’
phantom funcs 1–7 don’t appear in the walk.
Address tuple
typedef struct {
uint16_t seg; ///< PCI segment group
uint8_t bus;
uint8_t dev;
uint8_t func;
} AxlPciAddr;
uint16_t seg matches the UEFI MCFG / PCIe spec’s segment-group
width — multi-segment platforms (large servers, some ARM SoCs)
are addressable directly through every axl_pci_* API. Single-
segment systems leave it at 0.
axl_pci_addr_parse and axl_pci_addr_format round-trip an
AxlPciAddr through the canonical lower-hex SSSS:BB:DD.F form
(same shape as lspci). Parse accepts both 3-component
(bus:dev.func, segment defaults to 0) and 4-component
(seg:bus:dev.func) variants, with bounded-range checks at parse
time:
AxlPciAddr a;
if (axl_pci_addr_parse(argv[1], &a) != 0) { /* malformed */ }
char buf[AXL_PCI_ADDR_STR_MAX];
axl_pci_addr_format(a, buf, sizeof(buf));
axl_printf("device %s\n", buf);
Common header reads
Two boilerplate-killer wrappers around the standard config-space header offsets — they fold the “is this function absent?” and “unpack the 24-bit class triplet” patterns into one call:
uint16_t vid, did;
if (axl_pci_get_vid_did(addr, &vid, &did) == 0) {
/* function is present (vid != 0xFFFF) and both fields read OK */
}
uint32_t class24; /* (base << 16) | (sub << 8) | prog_if */
axl_pci_get_class24(addr, &class24);
axl_pci_get_vid_did returns -1 when the function is absent (vid
reads as 0xFFFF), so callers don’t have to special-case the
sentinel. class24 matches the shape consumed by
axl_pci_find_by_class.
Lookups
AxlPciAddr nic;
if (axl_pci_find_by_vid_did(0x8086, 0x100E, 0, &nic) == 0) {
/* Intel 82540EM e1000 */
}
AxlPciAddr usb_xhci;
/* class 0x0C 0x03 0x30 = serial bus, USB controller, xHCI prog-if */
axl_pci_find_by_class(0x0C0330, 0, &usb_xhci);
Capabilities
Two cursors — legacy (chain at 0x34) and PCIe extended (chain at 0x100):
uint16_t off = 0;
uint16_t id;
while (axl_pci_cap_next(addr, off, &off, &id) == 0) {
if (id == 0x10) {
/* PCIe Capability — PCI Express link/device control */
}
}
off = 0;
while (axl_pci_ext_cap_next(addr, off, &off, &id) == 0) {
if (id == 0x0002) {
/* Virtual Channel */
}
}
VPD
Vital Product Data (PCI 3.0 §6.4) — keyword-tagged inventory data exposed by some NICs and storage controllers. AxlPci handles the F-bit handshake on the address register, the dword-aligned data window, and the Read-Only / Read-Write tag walk:
uint8_t buf[64];
size_t len = 0;
if (axl_pci_vpd_read(nic, "PN", buf, sizeof(buf), &len) == 0) {
/* `len` is the actual on-device length; buf was filled with
up to sizeof(buf) bytes of it. */
axl_printf("Part number: %.*s\n", (int)len, buf);
}
For “show me everything that’s there” — vendor-specific V0..V9 /
Y0..Y9 keywords included — use axl_pci_vpd_iter and dispatch
through a callback:
static int dump_cb(const char keyword[2], const uint8_t *data,
size_t len, void *ctx) {
(void)ctx;
axl_printf(" %c%c (%zu): %.*s\n",
keyword[0], keyword[1], len, (int)len, data);
return 0; /* return non-zero to stop the walk early */
}
axl_pci_vpd_iter(nic, dump_cb, NULL);
axl_pci_vpd_iter and axl_pci_vpd_read share the same VPD
walker — one cap-list lookup, one tag walk — so calling either
reflects the same on-device state. The callback’s data pointer
is only valid for the duration of the call; copy any bytes you
want to retain.
API Reference
PCI/PCIe configuration-space access via ECAM.
Configuration space is reached through the MCFG-described Enhanced Configuration Access Mechanism (ECAM) on both x86 and AArch64 — never via the legacy 0xCF8/0xCFC port pair. The MCFG discovery is lazy on first access; if the firmware did not publish an MCFG table, all axl_pci_* calls fail with -1 rather than silently falling back to legacy ports.
Cursor-style iteration mirrors axl_smbios_find_next and axl_acpi_find_next:
AxlPciAddr *p = NULL;
while ((p = axl_pci_next(p)) != NULL) {
uint16_t vid;
axl_pci_read_config_16(*p, 0x00, &vid);
// ...
}
Defines
-
AXL_PCI_ADDR_STR_MAX
Buffer size that fits the canonical “SSSS:BB:DD.F” form plus NUL.
Functions
-
int axl_pci_addr_parse(const char *s, AxlPciAddr *out)
Parse a textual PCI address into an AxlPciAddr.
Accepts two hex-only formats:
bus:dev.func— segment defaults to 0seg:bus:dev.func— explicit segment
Components are bounded at parse time (bus 0..0xFF, dev 0..0x1F, func 0..0x07, seg 0..0xFFFF); out-of-range or malformed input returns -1 with
outleft unmodified.- Parameters:
s – input string (NUL-terminated, hex digits +
:+.)out – [out] parsed address (untouched on error)
- Returns:
0 on success, -1 on malformed input.
-
int axl_pci_addr_format(AxlPciAddr addr, char *buf, size_t buflen)
Write an AxlPciAddr in canonical
SSSS:BB:DD.Fform.Always emits the 4-digit segment — round-trips with axl_pci_addr_parse. NUL-terminates
bufwhenbuflen>= 13.- Parameters:
addr – address to format
buf – destination buffer
buflen – capacity of
buf
- Returns:
number of bytes written excluding NUL, or -1 if
buflenis too small (need >= AXL_PCI_ADDR_STR_MAX).
-
int axl_pci_read_config_8(AxlPciAddr addr, uint16_t reg, uint8_t *out)
Read a byte from PCI configuration space.
- Parameters:
addr – target function
reg – register offset (0..4095 for ECAM)
out – [out] receives the value
- Returns:
0 on success, -1 if the address is outside any MCFG segment or MCFG isn’t available.
-
int axl_pci_read_config_16(AxlPciAddr addr, uint16_t reg, uint16_t *out)
16-bit variant of axl_pci_read_config_8. Register should be 16-bit aligned.
-
int axl_pci_read_config_32(AxlPciAddr addr, uint16_t reg, uint32_t *out)
32-bit variant of axl_pci_read_config_8. Register should be 32-bit aligned.
-
int axl_pci_write_config_8(AxlPciAddr addr, uint16_t reg, uint8_t value)
Write counterpart to axl_pci_read_config_8.
-
int axl_pci_write_config_16(AxlPciAddr addr, uint16_t reg, uint16_t value)
Write counterpart to axl_pci_read_config_16.
-
int axl_pci_write_config_32(AxlPciAddr addr, uint16_t reg, uint32_t value)
Write counterpart to axl_pci_read_config_32.
-
int axl_pci_get_vid_did(AxlPciAddr addr, uint16_t *vid, uint16_t *did)
Read vendor ID and device ID from a function’s standard header.
Reads offsets 0x00 and 0x02. The “function absent” sentinel (vendor ID == 0xFFFF) is folded into the return code so callers don’t have to special-case it.
- Parameters:
addr – target function
vid – [out] vendor ID
did – [out] device ID
- Returns:
0 on success (both fields populated), -1 if the function is absent or any bus error is encountered.
-
int axl_pci_get_class24(AxlPciAddr addr, uint32_t *class24)
Read the 24-bit class code from a function’s standard header.
Folds the three bytes at offsets 0x09 (programming interface), 0x0A (subclass), and 0x0B (base class) into the canonical
(base << 16) | (sub << 8) | prog_ifform — same shape consumed by axl_pci_find_by_class.- Parameters:
addr – target function
class24 – [out] 24-bit class code
- Returns:
0 on success, -1 on bus error.
-
AxlPciAddr *axl_pci_next(AxlPciAddr *prev)
Iterate every responding PCI function across all MCFG segments.
Returns a pointer to a static internal cursor; the storage is reused across calls and is invalidated by the next call. Pass NULL to start the walk fresh, or the previous non-NULL return value to advance — passing any other pointer (including a caller-allocated AxlPciAddr) restarts iteration silently. The caller never owns the cursor’s storage.
Empty slots (vendor ID 0xFFFF) are skipped. Single-function devices are detected via the header-type byte and their functions 1–7 are skipped.
- Parameters:
prev – previous result, or NULL to start
- Returns:
pointer to the next populated function, or NULL when enumeration is complete (or MCFG is unavailable).
-
int axl_pci_find_by_vid_did(uint16_t vid, uint16_t did, uint16_t nth, AxlPciAddr *out)
Find the
nthfunction with matching vendor+device IDs.- Parameters:
vid – vendor ID
did – device ID
nth – 0-based match index
out – [out] address of the matching function
- Returns:
0 on success, -1 if no
nthmatch exists.
-
int axl_pci_find_by_class(uint32_t class24, uint16_t nth, AxlPciAddr *out)
Find the
nthfunction with a matching class triplet.The 24-bit class is
(base_class << 16) | (subclass << 8) | prog_if, matching howlspci -vvvprints it. Pass0xFFFFFFto match any.- Parameters:
class24 – 24-bit class code
nth – 0-based match index
out – [out] address of the matching function
- Returns:
0 on success, -1 if no
nthmatch exists.
-
int axl_pci_cap_next(AxlPciAddr addr, uint16_t prev_off, uint16_t *out_off, uint16_t *out_id)
Iterate the standard PCI capability list.
Pass 0 in
prev_offto start (the function’s capability list pointer at 0x34 is consulted automatically). On the first call the function returns the first capability; on subsequent calls passout_offfrom the previous return value to advance.- Parameters:
addr – target function
prev_off – previous offset, or 0 to start
out_off – [out] offset of the next capability
out_id – [out] capability ID
- Returns:
0 on success (capability found), -1 when no more capabilities exist or the function has no capabilities.
-
int axl_pci_ext_cap_next(AxlPciAddr addr, uint16_t prev_off, uint16_t *out_off, uint16_t *out_id)
Iterate the PCIe extended capability list (offsets 0x100..).
Same conventions as axl_pci_cap_next, but operates on the PCIe extended capability chain. Returns -1 if the device is not PCIe (no extended caps).
- Parameters:
addr – target function
prev_off – previous offset, or 0 to start
out_off – [out] offset of the next capability
out_id – [out] extended capability ID
- Returns:
0 on success, -1 when the chain ends or no extended caps are present.
-
int axl_pci_vpd_read(AxlPciAddr addr, const char keyword[2], uint8_t *buf, size_t buflen, size_t *out_len)
Read a VPD keyword from a function’s Vital Product Data area.
Walks the VPD capability (PCI 3.0 §6.4) — keyword-tagged blocks inside the Read-Only and Read/Write resource sections. Keyword is exactly 2 ASCII characters (e.g. “PN” for part number, “EC” for engineering change, “SN” for serial). The function locates the matching keyword in either RO or RW area and copies up to
buflenbytes of its data intobuf.- Parameters:
addr – target function
keyword – 2-char ASCII keyword (NOT nul-terminated)
buf – destination buffer
buflen – capacity of
bufout_len – [out] keyword’s actual length
- Returns:
0 on success, -1 if VPD is unsupported, the keyword is not present, or any bus error is encountered. On success,
*out_lenis set to the keyword’s actual data length (which may exceedbuflen— in which case the buffer was truncated).
-
int axl_pci_vpd_iter(AxlPciAddr addr, int (*cb)(const char keyword[2], const uint8_t *data, size_t len, void *ctx), void *ctx)
Walk every keyword in a function’s VPD area and dispatch to a callback.
Complements axl_pci_vpd_read
for tools that want “show me
everything that’s there” rather than “fetch this specific
keyword.” Visits both the Read-Only (PN/EC/SN/MN/RV/V0..V9/…) and Read-Write (Y0..Y9/RW/…) resource sections in document order. Vendor-specific keywords (V0..V9, Y0..Y9) reach the callback alongside the standard ones.
The data buffer passed to
cbis owned by the implementation and is only valid for the duration of the call — the callback must copy bytes it wants to retain. Returning non-zero fromcbstops iteration; that value becomes the iter return.- Parameters:
addr – target function
ctx – opaque context forwarded to
cb
- Returns:
0 if iteration completed without the callback stopping it, the callback’s non-zero return if it stopped early, or -1 if VPD is unsupported or any bus error is encountered.
-
struct AxlPciAddr
- #include <axl-pci.h>
A PCI configuration-space address (segment:bus:dev:func).
16-bit segment matches the UEFI MCFG / PCIe spec width so multi- segment platforms are addressable directly — every lookup, walk, and find helper takes segment as part of the address tuple. Single-segment systems leave it at 0. Bus / dev / func are the standard 8/5/3-bit fields.