AxlUsb — USB device enumeration + descriptor reads

USB device enumeration via EFI_USB_IO_PROTOCOL.

Header: <axl/axl-usb.h>. Lazy on first call: AxlUsb walks the firmware-installed USB I/O protocol handles, derives stable (bus, addr, intf) ordinals from each handle’s device path, and caches the result. On platforms without a USB stack (rare; some constrained BMC firmware) every axl_usb_* call returns -1 / NULL cleanly.

Cursor-style enumeration matches axl_pci_next and axl_smbios_find_next:

AxlUsbAddr *u = NULL;
while ((u = axl_usb_next(u)) != NULL) {
    uint16_t vid, pid;
    if (axl_usb_get_vid_pid(*u, &vid, &pid) == 0) {
        axl_printf("Bus %03u Device %03u If %u  %04x:%04x\n",
                   u->bus, u->addr, u->intf, vid, pid);
    }
}

axl_usb_next emits one entry per EFI_USB_IO_PROTOCOL handle — i.e. one per USB interface. A composite device with N interfaces returns N entries that share (bus, addr) and differ only in intf. A consumer that wants one row per physical device should dedupe on (bus, addr) (mirrors what Linux lsusb does in its default short form; see tools/lsusb.c for the reference renderer).

Address tuple

typedef struct {
    uint8_t  bus;    ///< host-controller ordinal (1-based)
    uint8_t  addr;   ///< device ordinal within bus (1-based)
    uint8_t  intf;   ///< interface number from interface descriptor
} AxlUsbAddr;

bus and addr are synthesized — UEFI doesn’t expose USB topology the way Linux does, so AxlUsb derives ordinals from each handle’s device path: bus numbers each unique host controller in device-path order; addr numbers each unique physical device within a bus. They’re stable within a single boot but may shift across boots if the firmware enumerates handles in a different order. Consumers needing a persistent identity should hash the device descriptor (vid:pid:serial) instead.

intf IS the real bInterfaceNumber from the interface descriptor.

Per-device introspection

uint16_t vid, pid;
axl_usb_get_vid_pid(addr, &vid, &pid);

uint8_t cls, sub, prot;
axl_usb_get_class(addr, &cls, &sub, &prot);  // any out param may be NULL

char buf[AXL_USB_STRING_MAX];
axl_usb_get_manufacturer(addr, buf, sizeof(buf));
axl_usb_get_product     (addr, buf, sizeof(buf));
axl_usb_get_serial      (addr, buf, sizeof(buf));

axl_usb_get_vid_pid reads the device descriptor’s idVendor / idProduct (so all interfaces of one physical device return the same pair). axl_usb_get_class reads the interface’s class triplet — composite devices set bDeviceClass = 0 and drive their identity per interface, so this is the right granularity for the “one row per interface” walk lsusb does in -vv mode.

axl_usb_get_manufacturer / _product / _serial are convenience wrappers around axl_usb_get_string(addr, idx, buf, buflen) that read the device descriptor’s iManufacturer / iProduct / iSerialNumber index byte first. They return -1 when the device declares no string at that slot (index == 0). Each one lazily probes the device’s supported-language table on first use and caches the first language ID per interface.

Class triplet decode

char buf[AXL_USB_CLASS_NAME_MAX];
axl_usb_class_string_fmt(0x03, 0x01, 0x02, AXL_USB_CLASS_FMT_FULL,
                         buf, sizeof(buf));
// → "Human Interface Device / Boot Interface / Mouse"

AxlUsbClassFmt selects the output shape — same posture as AxlPciClassFmt:

  • FMT_FULL"<base> / <sub> / <prot>" (default; verbose tools)

  • FMT_SUBCLASS"<sub>" (collapses to <base> when sub unknown, then numeric — Linux lsusb shape)

  • FMT_BASE"<base>" (collapses to numeric when unknown)

Tiers with no spec-defined name are omitted rather than rendered as <unknown> placeholders; wholly-unknown class falls back to "Class XXXXXX" numeric. Compiled-in tables in src/usb/axl-usb-class.c cover the USB-IF Defined Class Codes (https://www.usb.org/defined-class-codes) — base classes, common subclasses (HID Boot, Mass Storage SCSI, CDC variants, …), and the most-used protocol bytes (HID Mouse / Keyboard, BBB, UAS). No sidecar overlay yet (AxlPci has one — pci-class.json5 — but the USB compiled-in set covers what we ship today).

Topology walk

static int print_node(AxlUsbAddr a, unsigned depth, void *ctx) {
    (void)ctx;
    for (unsigned i = 0; i < depth; i++) axl_print("  ");
    axl_printf("Bus %03u Dev %03u If %u\n", a.bus, a.addr, a.intf);
    return 0;
}

axl_usb_tree_for_each(print_node, NULL);

axl_usb_tree_for_each walks every interface in (bus, port_chain, intf) ascending order — guaranteeing parents arrive before children, so a renderer can emit indentation directly from depth without lookahead. depth is the USB hub depth: 0 = directly attached to the host controller’s root hub, 1 = behind one hub, etc. AXL_USB_TREE_MAX_DEPTH = 8 caps the recorded chain (USB spec real-world maximum is 5 hubs / 6 USB nodes).

The walker is built from each handle’s EFI device path — the chain of consecutive MSG_USB_DP nodes encodes the full hub-port path — and the existing dev-key sort guarantees parents-before-children (parent dev_keys are byte prefixes of children’s). tools/lsusb.c’s -t mode is the reference consumer.

Vendor / device name database

axl_usb_ids_load(override_path) loads a curated JSON5 sidecar (usb-ids.json5). When override_path is non-NULL it is used authoritatively (no fallback — explicit means explicit). When it is NULL the loader autodiscovers in this order: companion to the running .efi, then current working directory. axl-sdk ships a starter set in share/usb-ids.json5 covering common HID, NIC, hub, and storage vendors; bulk-extract from canonical usb.ids via scripts/usb-ids-to-json5.py.

if (axl_usb_ids_load(NULL) == AXL_SIDECAR_OK) {
    const char *vendor = axl_usb_vendor_name(0x046D);
    const char *device = axl_usb_device_name(0x046D, 0xC52B);
    /* both NULL-safe; consumers fall back to numeric IDs */
}

axl_usb_ids_load returns:

  • AXL_SIDECAR_OK on success (idempotent on subsequent calls)

  • AXL_SIDECAR_FILE_MISSING if no candidate file exists

  • AXL_SIDECAR_PARSE_ERROR if a candidate was found but failed to parse

The split lets tools log differently — “no database shipped” is a deployment problem (numeric fallback is fine), while “parse error” is an authoring problem worth being loud about. See <axl/axl-sidecar.h> for the shared status enum.

Schema 1 is the only supported layout — hierarchical from the start (vendors with nested devices). USB has no subsystem dimension that motivated AxlPciIds’s v1 (flat) → v2 (hierarchical) split.

{
    schema: 1,
    vendors: [
        { id: 0x046D, name: 'Logitech, Inc.',
          devices: [
            { pid: 0xC52B, name: 'Unifying Receiver' },
          ],
        },
    ],
}

Composed-name helper

axl_usb_format_name(vid, pid, buf, buflen) centralizes the “vendor + device + numeric tail” rendering convention so every consumer prints the same string for the same (vid, pid) pair:

char buf[AXL_USB_NAME_COMPOSED_MAX];
axl_usb_format_name(0x046D, 0xC52B, buf, sizeof(buf));
// → "Logitech, Inc. Unifying Receiver"

Vendor-known + device-unknown produces "<vendor> Device <pid hex>"; vendor-unknown short-circuits to "<vid>:<pid>" regardless of whether a device entry happens to exist (without a verified vendor the device hit is ambiguous provenance).

Layered databases (handle API)

Same shape as AxlPciIds. Consumers that ship a private OEM sheet on top of the public set load two handles and query in priority order:

AxlUsbIds *pub  = NULL;
AxlUsbIds *priv = NULL;
axl_usb_ids_open("usb-ids.json5",         &pub);
axl_usb_ids_open("private-usb-ids.json5", &priv);

const char *d = axl_usb_ids_device_name(priv, vid, pid);
if (d == NULL) d = axl_usb_ids_device_name(pub, vid, pid);

axl_usb_ids_close(priv);
axl_usb_ids_close(pub);

axl_usb_ids_format_name(handle, vid, pid, buf, buflen) is the handle-aware equivalent of axl_usb_format_name. For “show me everything in this overlay” use axl_usb_ids_foreach_vendor / _foreach_device.

Per-name length contracts

AXL_USB_VENDOR_NAME_MAX     = 128 bytes
AXL_USB_DEVICE_NAME_MAX     = 192 bytes
AXL_USB_NAME_COMPOSED_MAX   = 384 bytes
AXL_USB_CLASS_NAME_MAX      = 128 bytes
AXL_USB_STRING_MAX          = 384 bytes  (USB string descriptors;
                                          127 BMP chars * 3 UTF-8
                                          bytes + NUL; BMP only)

Sized to comfortably hold real usb.ids entries; loader silently truncates over-cap names. Pin char buf[AXL_USB_NAME_COMPOSED_MAX] on the stack and never have to worry about formatter overflow.

Bulk population from canonical usb.ids

The shipped share/usb-ids.json5 is a curated starter set (~22 vendors). For fleet-scale OEM-rebadge coverage, run the conversion against the canonical usb.ids:

# Full set:
scripts/usb-ids-to-json5.py /usr/share/hwdata/usb.ids \
    > usb-ids.json5

# Curated subset (vendor entries always emitted; only their
# devices are dropped for vendors not in the list):
scripts/usb-ids-to-json5.py --vendors-only 046d,0bda,1d6b \
    /usr/share/hwdata/usb.ids > usb-ids.json5

# Verify the script itself:
scripts/usb-ids-to-json5.py --self-test

The .deb / .rpm install the converter under /usr/share/axl/scripts/; the line-level parser is shared with pci-ids-to-json5.py via _ids_parser.py.

API Reference

USB device enumeration via EFI_USB_IO_PROTOCOL handles.

The UEFI USB stack exposes one EFI_USB_IO_PROTOCOL handle per interface* of every enumerated device — multiple handles therefore refer to the same physical device when it has more than one function (e.g. a composite keyboard+mouse, or a USB-net adapter with separate control / data interfaces). AxlUsb derives stable (bus, addr, intf) ordinals from each handle’s device path so consumers can present a Linux-lsusb-shaped view: one row per interface, deduplicated and ordered by bus/device/interface.

Cursor-style iteration mirrors axl_pci_next:

AxlUsbAddr *u = NULL;
while ((u = axl_usb_next(u)) != NULL) {
    uint16_t vid, pid;
    if (axl_usb_get_vid_pid(*u, &vid, &pid) == 0) {
        // ...
    }
}

Phase A: enumeration + vendor/product ID readout. Phase B: interface class triplet (class / subclass / protocol) decode via the same compiled-in tables AxlPci uses for PCI classes. Phase C: string descriptor reads — manufacturer / product / serial via UsbGetStringDescriptor + UCS-2 → UTF-8. Phase D: vendor/device-name database via usb-ids.json5 sidecar (handle + singleton API mirroring AxlPciIds). See docs/AXL-Usb-Handoff.md.

USB-name length contracts

Maximum bytes (including NUL) any USB-IDs database lookup can return.

Sized comfortably for real usb.ids entries — vendor strings like “Logitech, Inc.” run ~20 bytes, device strings frequently 60-100, the rare full descriptive name fits in 192. Composed name covers vendor + device + small fixed overhead, so axl_usb_format_name never truncates non-truncated inputs.

AXL_USB_VENDOR_NAME_MAX

vendor entry max bytes

AXL_USB_DEVICE_NAME_MAX

device entry max bytes

AXL_USB_NAME_COMPOSED_MAX

axl_usb_format_name output max

Database iteration callbacks

Non-zero callback return stops iteration and propagates as the iter rc.

typedef int (*AxlUsbIdsVendorFn)(uint16_t vid, const char *name, void *ctx)
typedef int (*AxlUsbIdsDeviceFn)(uint16_t vid, uint16_t pid, const char *name, void *ctx)
int axl_usb_ids_foreach_vendor(const AxlUsbIds *ids, AxlUsbIdsVendorFn fn, void *ctx)
int axl_usb_ids_foreach_device(const AxlUsbIds *ids, AxlUsbIdsDeviceFn fn, void *ctx)

Defines

AXL_USB_CLASS_NAME_MAX

Buffer cap for a class-triplet decoded string. Sized for the longest plausible “Base / Sub / Prot” composition.

AXL_USB_STRING_MAX

Buffer cap for UTF-8 string-descriptor output. USB string descriptors max out at 254 bytes of UCS-2 payload (127 BMP code points); a BMP code point in U+0800..U+FFFF expands to 3 UTF-8 bytes, so the worst-case payload is 127 * 3 = 381 bytes plus NUL. BMP only — axl_ucs2_to_utf8_buf does not decode surrogate pairs, so supplementary-plane code points (U+10000+) are not supported.

AXL_USB_TREE_MAX_DEPTH

Maximum hub depth the tree walker will descend. USB 2.0/3.x caps the bus depth at 5 hubs between root and device (= 6 USB device-path nodes including the leaf), so 8 levels is generous headroom against malformed firmware paths.

Typedefs

typedef int (*AxlUsbTreeFn)(AxlUsbAddr addr, unsigned depth, void *ctx)

Per-node callback for axl_usb_tree_for_each.

Param addr:

the interface being visited (same shape axl_usb_next emits)

Param depth:

USB hub depth — 0 means the device is directly attached to the host controller’s root hub; 1 means the device is one hub deep; etc. Different from addr.bus (which is the controller ordinal).

Param ctx:

caller’s opaque context

Return:

non-zero to stop the walk early; the value becomes the return of axl_usb_tree_for_each. Return 0 to continue.

typedef struct AxlUsbIds AxlUsbIds

Opaque handle to a loaded USB vendor/device-name database.

Created by axl_usb_ids_open or axl_usb_ids_open_from_buffer, destroyed by axl_usb_ids_close. Multiple handles can coexist — a consumer that wants a “public + private” overlay loads two handles and queries them in priority order. Mirrors AxlPciIds and AxlSpdIds.

Schema 1 is the only supported layout. The structure is hierarchical from the start (vendors with nested devices) — USB has no subsystem dimension that motivated PCI’s v1→v2 split, so there’s no flat-vs-hierarchical schema dispatch.

{ schema: 1,
  vendors: [
    { id: 0x046D, name: 'Logitech, Inc.',
      devices: [
        { pid: 0xC52B, name: 'Unifying Receiver' },
      ],
    },
  ],
}

Enums

enum AxlUsbClassFmt

Output shape selector for axl_usb_class_string_fmt.

USB class triplets decode into base / subclass / protocol tiers. Verbose tools want the full slash-joined triplet; row-oriented tools want the subclass alone (matches Linux lsusb shape); coarse categorization wants just the base. Mirrors AxlPciClassFmt.

Values:

enumerator AXL_USB_CLASS_FMT_FULL

“Human Interface Device / Boot Interface / Mouse”

enumerator AXL_USB_CLASS_FMT_SUBCLASS

“Boot Interface” (collapses to base if unknown)

enumerator AXL_USB_CLASS_FMT_BASE

“Human Interface Device” (collapses to numeric if unknown)

Functions

AxlUsbAddr *axl_usb_next(AxlUsbAddr *prev)

Iterate every USB interface exposed by EFI_USB_IO_PROTOCOL.

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 AxlUsbAddr) restarts iteration silently. The caller never owns the cursor’s storage.

Iteration order is (bus, addr, intf) ascending. Returns NULL when the walk is complete or no EFI_USB_IO_PROTOCOL handles are installed (the platform has no USB stack, or the firmware did not enumerate any USB devices).

Parameters:
  • prev – previous result, or NULL to start

Returns:

pointer to the next interface, or NULL when enumeration is complete (or the USB stack is unavailable).

int axl_usb_get_vid_pid(AxlUsbAddr addr, uint16_t *vid, uint16_t *pid)

Read vendor ID and product ID from a USB device descriptor.

Reads the idVendor / idProduct fields of the standard 18-byte device descriptor via EFI_USB_IO_PROTOCOL.UsbGetDeviceDescriptor. All interfaces of one physical device share the same (vid, pid) — the per-interface granularity of AxlUsbAddr does not change the descriptor that backs this call.

Parameters:
  • addr – target interface

  • vid – [out] vendor ID

  • pid – [out] product ID

Returns:

0 on success (both fields populated), -1 if addr is not a known interface or the firmware fails the descriptor read.

int axl_usb_get_class(AxlUsbAddr addr, uint8_t *class_, uint8_t *sub, uint8_t *prot)

Read the interface class triplet for a USB interface.

Reads bInterfaceClass / bInterfaceSubClass / bInterfaceProtocol from the interface descriptor via EFI_USB_IO_PROTOCOL.UsbGetInterfaceDescriptor. Composite devices (DeviceDescriptor.bDeviceClass = 0) drive their class identity per interface — the right granularity to query for tools that present one row per interface (Linux lsusb -v shape).

Out parameters may be individually NULL if the caller doesn’t need that field.

Parameters:
  • addr – target interface

  • class_ – [out, optional] base class (e.g. 0x03 for HID)

  • sub – [out, optional] subclass

  • prot – [out, optional] protocol

Returns:

0 on success, -1 if addr is not a known interface or the firmware fails the descriptor read.

int axl_usb_class_string_fmt(uint8_t class_, uint8_t sub, uint8_t prot, AxlUsbClassFmt fmt, char *buf, size_t buflen)

Format a USB class triplet as a human-readable string.

Decodes per the USB-IF Defined Class Codes spec (https://www.usb.org/defined-class-codes) — up to three tiers: base class, subclass, and protocol. Tiers with no spec-defined name are omitted rather than rendered as <unknown> placeholders.

Output shapes (FMT_FULL):

  • All known: "<base> / <sub> / <prot>" (e.g. "Human Interface Device / Boot Interface / Mouse")

  • Known base+sub, unknown prot: "<base> / <sub>"

  • Known base, unknown sub: "<base>"

  • Wholly unknown: "Class XXXXXX" (numeric hex, in the spirit of Linux lsusb’s numeric fallback).

Always NUL-terminates buf (snprintf-shape).

Parameters:
  • class_ – base class

  • sub – subclass

  • prot – protocol

  • fmt – output shape selector

  • buf – destination buffer

  • buflen – capacity of buf

Returns:

number of bytes written excluding NUL, or -1 if buf is NULL or buflen is 0 or fmt is unrecognized.

int axl_usb_class_string(uint8_t class_, uint8_t sub, uint8_t prot, char *buf, size_t buflen)

Convenience wrapper for axl_usb_class_string_fmt with FMT_FULL.

int axl_usb_get_string(AxlUsbAddr addr, uint8_t string_index, char *buf, size_t buflen)

Read a USB string descriptor by index, decoded to UTF-8.

Indices are unsigned bytes in the device’s string descriptor table: the device descriptor’s iManufacturer / iProduct / iSerialNumber fields name three of them by convention; class- specific descriptors (HID, Audio, Configuration) reference others.

Index 0 is reserved for the language-ID table (USB 2.0 §9.6.7); axl_usb_get_string handles that internally — pass 1..255 for actual strings. The library picks the first language ID the device advertises and caches it per interface; multi-language devices that need a specific LANGID are out of scope for this helper.

Output is NUL-terminated UTF-8 in buf. Truncation is silent at the byte boundary (never writes a partial multi-byte sequence) — axl_ucs2_to_utf8_buf’s contract.

Parameters:
  • addr – target interface

  • string_index – 1..255; 0 is reserved

  • buf – destination UTF-8 buffer

  • buflen – capacity in bytes (incl. NUL)

Returns:

number of UTF-8 bytes written excluding NUL on success, -1 if addr is unknown, the device has no string descriptors, index 0 was passed, the firmware fails the descriptor read, or buf / buflen are bad.

int axl_usb_get_manufacturer(AxlUsbAddr addr, char *buf, size_t buflen)

Read the device’s manufacturer string (UTF-8).

Convenience over axl_usb_get_string — reads the iManufacturer index from the device descriptor, then fetches that string. Returns -1 if the device descriptor declares no manufacturer string (index = 0), mirroring axl_usb_get_string.

Returns:

UTF-8 byte count or -1 (see axl_usb_get_string).

int axl_usb_get_product(AxlUsbAddr addr, char *buf, size_t buflen)

Read the device’s product string (UTF-8). See axl_usb_get_manufacturer.

int axl_usb_get_serial(AxlUsbAddr addr, char *buf, size_t buflen)

Read the device’s serial-number string (UTF-8). See axl_usb_get_manufacturer.

int axl_usb_tree_for_each(AxlUsbTreeFn fn, void *ctx)

Walk the USB topology in tree order, depth-first per bus.

Each EFI_USB_IO_PROTOCOL handle’s device path encodes its full port chain via consecutive USB messaging-type nodes (PciRoot/.../Pci(usb_ctrl)/USB(parent_port, intf)/USB(...)). The walker recovers that chain at ingest time and emits entries in (bus, port_chain, intf) ascending order — guaranteeing parents arrive before children, so a renderer can print indentation derived from depth without lookahead.

Hubs that have no leaf descendants still appear (depth = N for an N-deep hub); their interface is enumerable via the same EFI_USB_IO_PROTOCOL handle every other USB device exposes. The caller can disambiguate hubs from leaves by reading the interface class via axl_usb_get_class — class 0x09 is the USB Hub class.

The callback may freely call AxlUsb read-only APIs against the visited addr (vid/pid, class, string descriptors) but must not invoke axl_usb_next during the walk — the cursor it uses is module-static and a re-entrant call would corrupt the outer iteration.

Returns:

0 on a clean walk, the callback’s first non-zero return if it stopped early, or -1 if fn is NULL or no USB stack is available.

AxlSidecarStatus axl_usb_ids_open(const char *path, AxlUsbIds **out)

Open a USB-IDs database from a JSON5 file.

Returns:

AXL_SIDECAR_OK on success (handle returned via out), AXL_SIDECAR_FILE_MISSING if path does not exist, AXL_SIDECAR_PARSE_ERROR on JSON5 / schema rejection.

AxlSidecarStatus axl_usb_ids_open_from_buffer(const char *json5, size_t len, AxlUsbIds **out)

Open a USB-IDs database from an in-memory JSON5 buffer.

Returns:

AXL_SIDECAR_OK on success, AXL_SIDECAR_PARSE_ERROR on parse / schema error.

void axl_usb_ids_close(AxlUsbIds *ids)

Free a database handle. NULL-safe.

const char *axl_usb_ids_vendor_name(const AxlUsbIds *ids, uint16_t vid)

Vendor lookup against an explicit handle.

Returns:

database-owned string or NULL if unknown / handle empty.

const char *axl_usb_ids_device_name(const AxlUsbIds *ids, uint16_t vid, uint16_t pid)

Device lookup against an explicit handle.

Returns:

database-owned string or NULL if (vid, pid) is unknown.

int axl_usb_ids_format_name(const AxlUsbIds *ids, uint16_t vid, uint16_t pid, char *buf, size_t buflen)

Compose a “vendor + device” display string against a handle.

Centralizes the rendering convention every consumer would otherwise reinvent — every tool prints the same string for the same (vid, pid) pair:

  • vendor known + device known → "<vendor> <device>"

  • vendor known + device unknown → "<vendor> Device <PID hex>"

  • vendor unknown → "<VID>:<PID>"

Hex literals are lowercase, 4-wide, zero-padded (matches Linux lsusb’s -d filter form). Output never exceeds AXL_USB_NAME_COMPOSED_MAX bytes.

Returns:

number of bytes written excluding NUL (snprintf shape), or -1 on bad arguments.

AxlSidecarStatus axl_usb_ids_load(const char *override_path)

Load the curated USB vendor/device name database into the process-global slot.

Two modes selected by override_path:

  • Explicit (override_path non-NULL): use exactly that path.

  • Autodiscover (override_path NULL): try usb-ids.json5 next to the running .efi (companion path), then in the current working directory.

Idempotent — a successful load is a no-op on subsequent calls. Registers an axl_atexit cleanup on first success so the parsed table is freed automatically at runtime cleanup.

void axl_usb_ids_free(void)

Free the loaded database. Safe to call when none is loaded.

const char *axl_usb_vendor_name(uint16_t vid)

Singleton-backed vendor lookup.

Returns:

database-owned string, or NULL if no database loaded or vid is not present.

const char *axl_usb_device_name(uint16_t vid, uint16_t pid)

Singleton-backed device lookup. Does not fall back to the vendor name when the device is unknown — callers compose their own “vendor name + numeric PID” via axl_usb_format_name.

int axl_usb_format_name(uint16_t vid, uint16_t pid, char *buf, size_t buflen)

Singleton-backed convenience wrapper for axl_usb_ids_format_name.

struct AxlUsbAddr
#include <axl-usb.h>

A USB controller address (bus / device / interface).

bus is a 1-based ordinal of the host controller (xHCI/EHCI/UHCI/ OHCI) the device is behind, in EFI device-path order. addr is a 1-based ordinal of the physical device within that bus, deduplicated across interfaces. intf is the real interface number returned by UsbGetInterfaceDescriptor — multiple (bus, addr, *) entries with different intf values refer to interfaces of the same physical device.

Ordinals are stable within a single boot but not across boots — the underlying EFI handle order can change. Consumers that need a persistent identity should hash the device descriptor (vid:pid:serial) instead.

Public Members

uint8_t bus

host controller ordinal (1-based)

uint8_t addr

device ordinal within bus (1-based)

uint8_t intf

interface number from interface descriptor