AxlSmbus — SMBus / I2C block access

AxlSmbus — SMBus / I2C block access

SMBus session abstraction that gives UEFI apps block read/write over whichever controller the platform publishes. Dispatches on the first available firmware-provided transport:

Kind

Mechanism

Who owns framing

AXL_SMBUS_TRANSPORT_HC

EFI_SMBUS_HC_PROTOCOL

firmware

AXL_SMBUS_TRANSPORT_I2C

EFI_I2C_MASTER_PROTOCOL

this module

Auto-detect preference: SMBus HC → I2C Master. The I2C fallback constructs the SMBus block-transfer wire format ([cmd][count][data] on writes, [count][data] on reads) on top of raw I2C operations.

Consumers

  • AxlIpmi SSIF transport — every IPMI round-trip over SSIF goes through an AxlSmbus session.

  • AxlSpd (planned, Phase B3) — DDR4/5 SPD readers at the canonical 0xA0–0xAE addresses.

Usage

#include <axl/axl-smbus.h>

AXL_AUTOPTR(AxlSmbus) s = axl_smbus_new();
if (s == NULL) {
    axl_error("No SMBus controller available");
    return -1;
}

uint8_t  buf[32];
size_t   len = sizeof(buf);
if (axl_smbus_read_block(s, /*slave=*/0x10, /*cmd=*/0x03,
                         buf, &len) != 0) {
    axl_error("SMBus read failed");
    return -1;
}
// buf[0..len-1] carries the payload (count byte already stripped).

AXL_AUTOPTR frees the session at scope exit; firmware owns the underlying protocol instance so nothing in the shim is leaked.

Layout

src/smbus/
├── axl-smbus.c              session lifecycle + auto-detect
├── axl-smbus-internal.h     transport vtable + session layout
├── axl-smbus-hc.c           EFI_SMBUS_HC_PROTOCOL pass-through
├── axl-smbus-i2c.c          EFI_I2C_MASTER_PROTOCOL framing (B1 code path)
└── axl-smbus-format.c       enum-to-string helpers

Wire format (I2C path)

SMBus 2.0 §5.5.7/5.5.8. The SMBus HC protocol inserts and strips the byte-count for us; the I2C Master protocol does not, so this module builds it manually:

Block write:  [cmd] [count] [payload_0 ... payload_{count-1}]
Block read:   (write cmd) → (read) [count] [payload_0 ... payload_{count-1}]

On reads, the count byte is stripped before the payload is copied to the caller’s buffer. On writes, it is inserted at tx[1]. Both sides are asserted on by test/unit/axl-test-smbus.c via a capturing mock protocol — any future regression of this framing fails CI without a BMC in the loop.

Limits

  • Max block payload: 32 bytes (spec limit; AXL_SMBUS_BLOCK_MAX).

  • Higher-level SMBus commands (quick, byte, byte_data, word_data, process_call, bus enumeration) are not implemented yet — they land when AxlSpd’s scope demands them, not speculatively.

API Reference

Defines

AXL_SMBUS_BLOCK_MAX

SMBus block transfer payload limit — the count byte is one byte wide and the spec caps block length at 32 data bytes.

Typedefs

typedef struct AxlSmbus AxlSmbus

axl-smbus.h:

SMBus / I2C block access for UEFI applications. Dispatches on the first available firmware-provided transport:

  1. EFI_SMBUS_HC_PROTOCOL — full SMBus host controller.

  2. EFI_I2C_MASTER_PROTOCOL — raw I2C master; framing is built here.

Consumers (AxlIpmi SSIF today, AxlSpd tomorrow) use AxlSmbus as an opaque session object and never have to know which backend the platform exposes. All framing lives behind the session — callers pass the SMBus command byte and payload bytes and get back the SMBus-stripped payload. AxlSmbus:

Opaque handle for an SMBus controller session. Created via axl_smbus_new() and freed with axl_smbus_free().

typedef bool (*AxlSmbusProbeFn)(AxlSmbus *s, void *user)

Open the FIRST SMBus controller where probe returns true.

Auto-detect via axl_smbus_new() uses LocateProtocol, which returns only one EFI_SMBUS_HC_PROTOCOL or EFI_I2C_MASTER_PROTOCOL instance. On multi-segment platforms (server AMD EPYC boards commonly publish 1 SMBus HC + a dozen I2C masters) the first instance is rarely the bus carrying DIMM SPDs. This walker enumerates EVERY published handle of both protocols and runs the caller’s probe against each opened session — useful for pickers like AxlSpd that need a specific slave address to respond.

probe receives the candidate session and is expected to attempt a single read/write at the slave address it cares about. Return true to claim the session; the caller will receive it as the axl_smbus_new return value. Return false to discard and try the next handle. user is forwarded verbatim.

Sessions discarded by the probe are freed before the next attempt.

Return:

session handle, or NULL if no enumeration step succeeded.

typedef void (*AxlSmbusVisitFn)(AxlSmbus *s, size_t index, void *user)

Visit every published SMBus controller (HC + I2C master) for diagnostic / inventory purposes.

Unlike axl_smbus_new_with_probe, this never claims a session. Each controller is opened, handed to visit, then freed before the next iteration. Useful for scanners that want to report the full topology rather than pick a single bus.

Return:

number of controllers visited (0 means none published).

Enums

enum AxlSmbusTransport

Transport kind currently driving a session.

Values:

enumerator AXL_SMBUS_TRANSPORT_UNKNOWN

No controller available.

enumerator AXL_SMBUS_TRANSPORT_HC

EFI_SMBUS_HC_PROTOCOL.

enumerator AXL_SMBUS_TRANSPORT_I2C

EFI_I2C_MASTER_PROTOCOL (framed here)

enumerator AXL_SMBUS_TRANSPORT_PIIX4

AMD FCH / Intel PIIX4 direct I/O.

Functions

AxlSmbus *axl_smbus_new(void)

Open a session against the first available SMBus controller.

Probes EFI_SMBUS_HC_PROTOCOL first (full block protocol provided by firmware); falls back to EFI_I2C_MASTER_PROTOCOL (this module builds the SMBus block-transfer framing on top).

Returns:

session handle, or NULL if no controller is available.

AxlSmbus *axl_smbus_new_with_probe(AxlSmbusProbeFn probe, void *user)
Parameters:
  • probe – called per candidate session

  • user – opaque pointer forwarded to probe

size_t axl_smbus_visit_all(AxlSmbusVisitFn visit, void *user)
Parameters:
  • visit – called per controller

  • user – opaque pointer forwarded to visit

void axl_smbus_free(AxlSmbus *s)

Free an SMBus session. NULL-safe.

Parameters:
  • s – session to free (NULL-safe)

AxlSmbusTransport axl_smbus_transport(const AxlSmbus *s)

Report which transport a session selected.

Parameters:
  • s – session (NULL returns UNKNOWN)

int axl_smbus_receive_byte(AxlSmbus *s, uint8_t slave, uint8_t *out)

SMBus “Receive Byte” transaction (§5.5.3).

Wire format: address + R bit, then the slave returns one byte. NO command byte is sent — different from axl_smbus_read_byte which writes a command then re-reads. Linux’s i2cdetect uses Receive Byte as the safest probe for EEPROM-prone address ranges (0x30..0x37, 0x50..0x5F) because writing a stray command byte could trigger device behavior — e.g., a register-pointer reset or, on some EEPROMs, a partial erase.

Returns:

AXL_OK on success, AXL_ERR on transport error or invalid arguments.

int axl_smbus_quick(AxlSmbus *s, uint8_t slave, bool is_read)

SMBus QUICK probe — address-only ACK check.

Sends slave + R/W bit and returns whether the slave ACKed. No command byte, no data. This is what Linux’s i2cdetect

uses by default — the safest way to detect “is something at

this address?” without triggering register reads or writes.

is_read selects the R/W bit (true = read direction). Some EEPROMs at 0x50..0x5F can be erased by stray writes, so prefer is_read=true for those address ranges.

Returns:

AXL_OK if the slave acknowledged; AXL_ERR if it NACKed, the bus errored, or the transport doesn’t implement QUICK (currently all three transports do).

const char *axl_smbus_describe(const AxlSmbus *s)

Per-instance human-readable identity, filled by the backend at session-open time.

Examples:

  • ”EFI SMBus HC” (every HC handle, no further detail)

  • ”EFI I2C Master” (every I2C Master handle)

  • ”AMD FCH PIIX4 port 0 at 0xB00” (MAIN controller)

  • ”AMD FCH PIIX4 port 1 at 0xB20” (AUX controller)

Returned pointer lives as long as the session — do not free, do not retain across axl_smbus_free. NULL only if s is NULL.

Parameters:
  • s – session (NULL returns NULL)

int axl_smbus_read_block(AxlSmbus *s, uint8_t slave, uint8_t command, uint8_t *buf, size_t *len)

SMBus block read.

Issues an SMBus block-read transaction: the command byte is written to slave, the device responds with a byte count followed by up to AXL_SMBUS_BLOCK_MAX payload bytes. The byte count is stripped by this function; the caller sees only the payload.

Parameters:
  • s – open session.

  • slave – 7-bit SMBus slave address (bit 0 is the R/W bit and is supplied by the transport).

  • command – SMBus command byte.

  • buf – (out) receives the payload.

  • len – (in/out) buffer capacity on entry; bytes written on success (clamped to capacity if the device returned more than requested).

Returns:

AXL_OK on success, AXL_ERR on transport error or invalid arguments.

int axl_smbus_write_block(AxlSmbus *s, uint8_t slave, uint8_t command, const uint8_t *buf, size_t len)

SMBus block write.

Issues an SMBus block-write transaction: command byte, byte count, then len payload bytes. The byte-count prefix is inserted by this function.

Parameters:
  • s – open session.

  • slave – 7-bit SMBus slave address.

  • command – SMBus command byte.

  • buf – payload bytes.

  • len – payload length; must not exceed AXL_SMBUS_BLOCK_MAX.

Returns:

AXL_OK on success, AXL_ERR on transport error or invalid arguments.

int axl_smbus_read_byte(AxlSmbus *s, uint8_t slave, uint8_t command, uint8_t *out)

SMBus “Read Byte” transaction.

Per spec §5.5.5: write command to slave, repeated start, read one data byte. The wire format is plain (no count prefix), which matches 24Cxx-style EEPROMs that auto-increment from a written offset — including the JEDEC SPD EEPROMs at 0x50–0x57 (DDR3/4 use 1-byte offsets; DDR5 SPD5118 hubs use this op for register reads after a page select via MR11).

Parameters:
  • s – open session.

  • slave – 7-bit SMBus slave address.

  • command – SMBus command byte (register/offset).

  • out – (out) receives the data byte.

Returns:

AXL_OK on success, AXL_ERR on transport error or invalid arguments.

int axl_smbus_write_byte(AxlSmbus *s, uint8_t slave, uint8_t command, uint8_t value)

SMBus “Write Byte” transaction.

Per spec §5.5.4: write command followed by value to slave. Used for SPD5118 hub page selection (write page to MR11 / register 0x0B) before reading from the page window.

Parameters:
  • s – open session.

  • slave – 7-bit SMBus slave address.

  • command – SMBus command byte (register/offset).

  • value – data byte to write.

Returns:

AXL_OK on success, AXL_ERR on transport error or invalid arguments.

const char *axl_smbus_transport_string(AxlSmbusTransport kind)

Human-readable name for an AxlSmbusTransport value.

Returns:

static string; never NULL.