AxlIpmi – Local BMC access

AxlIpmi — local BMC access

Local BMC access via IPMI transports (KCS, SSIF, EDKII vendor protocol, Dell vendor protocol), auto-selected from SMBIOS Type 38 plus firmware protocol discovery. AxlIpmi gives UEFI apps a single axl_ipmi_raw() entry point that abstracts away which physical interface is attached.

Transports

Kind

Mechanism

Typical platform

AXL_IPMI_TRANSPORT_KCS

x86 I/O ports via axl_backend_io_*

x86 servers

AXL_IPMI_TRANSPORT_SSIF

SMBus / I2C via axl_backend_smbus_*

ARM servers

AXL_IPMI_TRANSPORT_EDKII

IPMI_PROTOCOL via LocateProtocol

firmware with MdeModulePkg IPMI stack

AXL_IPMI_TRANSPORT_DELL

EFI_IPMI_TRANSPORT via LocateProtocol

Dell platforms with iDRAC

Auto-detect priority (highest → lowest): EDKII protocol → Dell protocol → SMBIOS Type 38 (KCS/SSIF) → x86 default KCS at 0x0CA2/0x0CA3.

Phase 2 implements KCS only. The other transports attach to the same vtable in subsequent commits.

Usage

#include <axl/axl-ipmi.h>

AXL_AUTOPTR(AxlIpmiSession) ipmi = axl_ipmi_session_new();
if (ipmi == NULL) {
    axl_error("No IPMI transport available");
    return -1;
}

// Get Device ID: NetFn=0x06 (App), Cmd=0x01
uint8_t resp[16];
size_t  resp_len = sizeof(resp);
if (axl_ipmi_raw(ipmi, 0x06, 0x01, NULL, 0, resp, &resp_len) != 0) {
    axl_error("IPMI send failed");
    return -1;
}

if (resp[0] != 0x00) {
    axl_warning("IPMI completion code 0x%02x", resp[0]);
    return -1;
}
// resp[1]..resp[resp_len-1] carry the response body.

AXL_AUTOPTR frees the session and closes the underlying transport at scope exit. No explicit axl_ipmi_session_free() needed on the happy path.

Layout

src/ipmi/
├── axl-ipmi.c            core dispatcher + session + auto-detect
├── axl-ipmi-internal.h   transport vtable + session layout
├── axl-ipmi-kcs.c        KCS transport (Phase 2)
└── axl-ipmi-{ssif,edkii,dell,cmd,format}.c   upcoming phases

Typed command wrappers (get_device_id, chassis_status, sdr, sel, fru, sensor) and the IpmiFormat.c-equivalent enum-to-string helpers land in Phase 5, together with their unit tests.

Transport hazards

These are documented upstream in the uefi-ipmitool port the module is based on; recording them here as well since they shape the module’s internals:

  • KCS FSM is blocking, polled, 5-second timeout per request. Safe in the current call sites (same pattern as AxlSmbios) but not usable inside AxlLoop without yielding.

  • SSIF 60 ms inter-command delay is enforced at the transport layer. Bulk operations (listing 150+ SDR records on iDRAC/Nvidia Grace) hang without it.

  • SSIF multi-part framing required for responses > 32 bytes.

  • Dell protocol synthesizes a CC=0x00 completion code byte because the vendor firmware drops it from the response buffer.

API Reference

Typedefs

typedef struct AxlIpmiSession AxlIpmiSession

AxlIpmiSession:

Opaque handle for a selected IPMI transport. Created via axl_ipmi_session_new() and freed with axl_ipmi_session_free().

typedef int (*AxlIpmiSendRaw)(void *user_data, uint8_t netfn, uint8_t cmd, const uint8_t *req, size_t req_len, uint8_t *resp, size_t *resp_len)

Send-raw callback. Same shape as axl_ipmi_raw() minus the session argument; the caller’s user_data is passed through instead.

Enums

enum AxlIpmiTransport

Transport kind currently driving a session.

Values:

enumerator AXL_IPMI_TRANSPORT_UNKNOWN

No transport available.

enumerator AXL_IPMI_TRANSPORT_KCS

Keyboard Controller Style (x86 I/O ports)

enumerator AXL_IPMI_TRANSPORT_SSIF

SMBus System Interface (I2C)

enumerator AXL_IPMI_TRANSPORT_EDKII

EDKII IPMI_PROTOCOL (firmware provides)

enumerator AXL_IPMI_TRANSPORT_DELL

Dell EFI_IPMI_TRANSPORT.

enum AxlIpmiChassisAction

Chassis Control action (Chassis 0x02).

Values:

enumerator AXL_IPMI_CHASSIS_POWER_DOWN
enumerator AXL_IPMI_CHASSIS_POWER_UP
enumerator AXL_IPMI_CHASSIS_POWER_CYCLE
enumerator AXL_IPMI_CHASSIS_HARD_RESET
enumerator AXL_IPMI_CHASSIS_PULSE_DIAG
enumerator AXL_IPMI_CHASSIS_SOFT_SHUTDOWN

Functions

AxlIpmiSession *axl_ipmi_session_new(void)

Open an IPMI session against the local BMC.

Auto-detects the best available transport in priority order:

  1. EDKII IPMI_PROTOCOL (gIpmiProtocolGuid)

  2. Dell EFI_IPMI_TRANSPORT (gDellIpmiProtocolGuid)

  3. SMBIOS Type 38 — KCS on x86 (I/O at 0x0CA2/0x0CA3 by default) or SSIF on ARM (slave 0x20 by default)

Returns:

session handle, or NULL if no transport is available.

void axl_ipmi_session_free(AxlIpmiSession *session)

Free an IPMI session. NULL-safe.

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

AxlIpmiTransport axl_ipmi_session_transport(const AxlIpmiSession *session)

Return the transport kind this session is using.

Parameters:
  • session – session (NULL returns UNKNOWN)

uint8_t axl_ipmi_session_last_cc(const AxlIpmiSession *session)

Return the IPMI completion code from the most recent typed command call on any session.

Lets callers distinguish an IPMI-level failure (non-zero CC from the BMC) from a transport-level failure (no response, timeout, LocateProtocol fail) when a typed wrapper returns -1. On a successful call the completion code is 0x00.

UEFI is single-threaded so the tracked CC is process-global; the session parameter is accepted for API-shape consistency but the value does not vary per-session in this implementation.

Parameters:
  • session – session (accepted but ignored)

Returns:

last-observed completion code, or 0x00 if no typed command has been invoked yet.

int axl_ipmi_raw(AxlIpmiSession *session, uint8_t netfn, uint8_t cmd, const uint8_t *req, size_t req_len, uint8_t *resp, size_t *resp_len)

Send a raw IPMI command and receive its response.

Every typed wrapper is built on top of this. netfn is unshifted (0x06 for App, 0x00 for Chassis, etc.). The response buffer receives the completion code as its first byte.

resp_len is in/out: on entry holds the buffer capacity; on success receives the number of bytes actually written.

Parameters:
  • session – session returned by axl_ipmi_session_new

  • netfn – network function (unshifted)

  • cmd – command byte

  • req – request data bytes (may be NULL if req_len == 0)

  • req_len – request data length in bytes

  • resp – (out) response bytes; resp[0] is completion code

  • resp_len – in/out: buffer size -> actual bytes written

Returns:

0 on success (transport completed the transaction; check resp[0] for the IPMI completion code). -1 on transport error or invalid arguments.

AxlIpmiSession *axl_ipmi_session_new_with_callback(AxlIpmiTransport transport_kind, AxlIpmiSendRaw send_raw, void *user_data)

Open an IPMI session backed by a caller-supplied transport callback.

Useful for unit tests (inject canned responses) and for pluggable out-of-process transports (IPMI-over-LAN, test rigs, etc.) that aren’t baked into the auto-detect chain.

Parameters:
  • transport_kind – label reported by axl_ipmi_session_transport

  • send_raw – callback invoked for each command

  • user_data – passed through to send_raw

Returns:

session handle, or NULL on allocation failure.

int axl_ipmi_get_device_id(AxlIpmiSession *session, AxlIpmiDeviceId *out)
int axl_ipmi_get_chassis_status(AxlIpmiSession *session, AxlIpmiChassisStatus *out)
int axl_ipmi_chassis_control(AxlIpmiSession *session, AxlIpmiChassisAction action)
int axl_ipmi_bmc_cold_reset(AxlIpmiSession *session)

Send a BMC Cold Reset (App 0x02).

Full BMC reboot. The BMC is unresponsive to further IPMI for 20–30 seconds afterward; callers should plan a retry delay.

Returns:

0 on success.

int axl_ipmi_bmc_warm_reset(AxlIpmiSession *session)

Send a BMC Warm Reset (App 0x03).

Resets BMC state without full reboot. Not all BMCs implement this — expect CC 0xC1 “Invalid command” on some platforms.

Returns:

0 on success.

int axl_ipmi_sel_info(AxlIpmiSession *session, AxlIpmiSelInfo *out)
int axl_ipmi_sel_get_entry(AxlIpmiSession *session, uint16_t record_id, AxlIpmiSelEntry *out)
Parameters:
  • record_id – 0x0000 = first, 0xFFFF = last

int axl_ipmi_sdr_info(AxlIpmiSession *session, AxlIpmiSdrInfo *out)
int axl_ipmi_sdr_get(AxlIpmiSession *session, uint16_t record_id, uint16_t *next_record_id, uint8_t *buf, size_t *len)

Fetch one SDR record’s bytes.

len is in/out: buffer capacity in, actual record size out. The typed SDR layouts (full sensor / compact sensor / entity / etc.) are defined in the IPMI spec; callers decode the returned bytes based on the SDR header’s record type.

Returns:

0 on success; *next_record_id receives the record id to pass on the next call (0xFFFF after the last record).

int axl_ipmi_get_sensor_reading(AxlIpmiSession *session, uint8_t sensor_num, AxlIpmiSensorReading *out)
int axl_ipmi_fru_info(AxlIpmiSession *session, uint8_t fru_id, AxlIpmiFruInfo *out)
int axl_ipmi_fru_read(AxlIpmiSession *session, uint8_t fru_id, uint16_t offset, uint8_t *buf, size_t *len)

Read a chunk of FRU data.

FRU areas are typically hundreds to a few thousand bytes; callers loop this helper with a 16-byte chunk size (SSIF sweet spot).

len is in/out: buffer capacity on entry, bytes actually read on return.

Returns:

0 on success.

const char *axl_ipmi_completion_code_string(uint8_t cc)

Human-readable name for an IPMI completion code.

Covers the standard codes from IPMI v2.0 Table 5-2. Unknown codes fall through to a generic “device-specific / OEM” label.

Parameters:
  • cc – completion code byte (resp[0])

Returns:

static string; never NULL.

const char *axl_ipmi_sensor_type_string(uint8_t sensor_type)

Human-readable name for an IPMI sensor type.

Covers type codes 0x01-0x2C from IPMI v2.0 Table 42-3. Unknown codes return “Unknown”.

Returns:

static string; never NULL.

int axl_ipmi_probe(AxlIpmiProbe *out)

Populate out with a snapshot of IPMI-related firmware state. Always succeeds (fields are zero-filled when the corresponding feature isn’t present).

Returns:

0 on success, -1 only on NULL argument.

const char *axl_ipmi_entity_id_string(uint8_t entity_id)

Human-readable name for an IPMI entity ID.

Covers the common entity IDs from IPMI v2.0 Table 43-13.

Returns:

static string; never NULL.

struct AxlIpmiDeviceId
#include <axl-ipmi.h>

Decoded Get Device ID response (App 0x01).

Public Members

uint8_t device_id
uint8_t device_revision

bits 0-3 revision, bit 7 = SDR support

uint8_t firmware_major

bits 0-6 major rev; bit 7 = device busy

uint8_t firmware_minor

BCD minor rev.

uint8_t ipmi_version

BCD: bits 3:0 major, bits 7:4 minor.

uint8_t device_support

bitmask of functions supported

uint32_t manufacturer_id

24-bit IANA manufacturer ID

uint16_t product_id
uint32_t aux_firmware_rev

0 if not provided

struct AxlIpmiChassisStatus
#include <axl-ipmi.h>

Decoded Get Chassis Status response (Chassis 0x01).

Public Members

uint8_t current_power_state
uint8_t last_power_event
uint8_t misc_state
uint8_t front_panel_caps

0 if not reported

struct AxlIpmiSelInfo
#include <axl-ipmi.h>

Decoded Get SEL Info response (Storage 0x40).

Public Members

uint8_t version
uint16_t entries
uint16_t free_space_bytes
uint32_t most_recent_addition

seconds since 1970-01-01 UTC

uint32_t most_recent_erase
uint8_t op_support

reservation / delete / etc. bitmask

struct AxlIpmiSelEntry
#include <axl-ipmi.h>

One SEL entry (Storage 0x43).

The record field is filled with the raw 16 bytes per IPMI spec Table 32-1; decoding the specific event type is up to the caller (or the format helpers that land in a later phase).

Public Members

uint16_t record_id

this entry’s ID

uint16_t next_record_id

0xFFFF marks last entry

uint8_t record[16]

raw entry bytes

struct AxlIpmiSdrInfo
#include <axl-ipmi.h>

Decoded Get SDR Repository Info response (Storage 0x20).

Public Members

uint8_t version
uint16_t record_count
uint16_t free_space_bytes
uint32_t most_recent_addition
uint32_t most_recent_erase
uint8_t op_support
struct AxlIpmiSensorReading
#include <axl-ipmi.h>

Decoded Get Sensor Reading response (Sensor 0x2D).

Raw reading conversion into engineering units requires the sensor’s SDR record (for m, b, exponent); callers that want scaled values look up the SDR once and compute (reading * m + b) * 10^exp per IPMI spec 36.3.

Public Members

uint8_t reading

raw linear value

uint8_t event_status

bit 7: scanning disabled

uint8_t reading_fields

discrete reading bits (optional)

uint8_t threshold_fields

threshold status (optional)

struct AxlIpmiFruInfo
#include <axl-ipmi.h>

Decoded FRU Inventory Area Info response (Storage 0x10).

Public Members

uint16_t size_bytes
bool word_access

true: offsets are word-addressed

struct AxlIpmiProbe
#include <axl-ipmi.h>

Result of axl_ipmi_probe().

Describes which IPMI-adjacent firmware protocols are present, what SMBIOS Type 38 says about the BMC interface, and how many I2C Master handles are available. Intended for diagnostics when axl_ipmi_session_new() can’t find a working transport — lets a tool print a summary of “what the firmware exposes” without needing direct access to UEFI protocol lookup.

Public Members

bool edkii_ipmi_protocol

gIpmiProtocolGuid

bool dell_ipmi_transport

gDellIpmiProtocolGuid

bool ami_dxe_ipmi_transport

AMI IpmiPkg DXE variant.

bool ami_smm_ipmi_transport

AMI IpmiPkg SMM variant.

bool intel_sm_ipmi_transport

Intel ServerManagementPkg.

bool mu_ipmi_transport2

Microsoft Project Mu.

bool smbus_hc_protocol

EFI_SMBUS_HC_PROTOCOL.

bool i2c_master_protocol

EFI_I2C_MASTER_PROTOCOL (any)

bool dell_idrac_interface

Dell iDRAC (non-IPMI)

bool smbios_type38_present
uint8_t smbios_interface_type

1=KCS 2=SMIC 3=BT 4=SSIF

uint8_t smbios_i2c_slave

8-bit wire address from SMBIOS

uint64_t smbios_base_address

KCS: low bit = I/O vs memory.

size_t i2c_master_handle_count