AxlSys — System Utilities
System operations, environment variables, time, NVRAM storage, boot-option management, x86 I/O port access, driver lifecycle, hex dump, configuration framework (including command-line parsing), and path manipulation.
Headers:
<axl/axl-sys.h>— System operations (reset, GUID, device map refresh)<axl/axl-env.h>— Environment variables and working directory<axl/axl-time.h>— Wall-clock time and monotonic timestamps<axl/axl-nvstore.h>— Portable NVRAM key-value storage<axl/axl-boot.h>— Boot-option management (Boot####/BootOrder/BootNext/BootCurrent)<axl/axl-port.h>— x86 I/O port access (in/out)<axl/axl-driver.h>— Driver binding and lifecycle<axl/axl-image.h>— Executable-image lifecycle (load/start/unload)<axl/axl-mem-phys.h>— Physical-memory map/unmap + one-shot read/write<axl/axl-watchdog.h>— Boot-services watchdog control<axl/axl-rng.h>— Cryptographic random bytes<axl/axl-diag.h>— Tool diagnostic helpers (-voutput)<axl/axl-hexdump.h>— Hex/ASCII dump formatting<axl/axl-config.h>— Unified configuration + command-line parsing<axl/axl-path.h>— Path manipulation
The event/cancellable/wait primitives previously listed here now
live in src/event/ — see
src/event/README.md.
System Utilities
GUIDs
UEFI identifies protocols, variables, and services by 128-bit GUIDs.
AXL provides AxlGuid (standard C types, no UEFI headers needed)
and the AXL_GUID macro for initialization:
AxlGuid my_guid = AXL_GUID(0x12345678, 0xabcd, 0xef01,
0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01);
if (axl_guid_cmp(&a, &b) == 0) {
// GUIDs are equal
}
Firmware Globals
After AXL_APP or axl_driver_init, these globals are available
(typed when <uefi/axl-uefi.h> is included):
gST— System Table (EFI_SYSTEM_TABLE *)gBS— Boot Services (EFI_BOOT_SERVICES *)gRT— Runtime Services (EFI_RUNTIME_SERVICES *)gImageHandle— handle of the running application or driver
NVRAM Variables
Portable key-value storage backed by firmware variables, organized
by namespace. Built-in namespaces are "global" (spec UEFI Global
Variable GUID, e.g. SecureBoot, BootOrder, Boot####) and "app"
(per-app GUID for application settings).
// Read
uint8_t secure_boot;
size_t sz = sizeof(secure_boot);
if (axl_nvstore_get("global", "SecureBoot", &secure_boot, &sz) == 0) {
axl_printf("SecureBoot: %s\n", secure_boot ? "on" : "off");
}
// Write
axl_nvstore_set("app", "last-run", timestamp, timestamp_len,
AXL_NV_PERSISTENT | AXL_NV_BOOT);
For variable-length values (NV strings, OEM blobs of unknown size),
axl_nvstore_get_alloc does the probe-allocate-read dance for you
and hands back a heap buffer the caller frees with axl_free. The
buffer is allocated needed + 1 bytes with the trailing byte zeroed,
so a string-shaped variable can be dereferenced as NUL-terminated
even if the wire payload omitted the NUL:
void *buf;
size_t sz;
if (axl_nvstore_get_alloc("global", "PlatformLang", &buf, &sz) == 0) {
axl_printf("PlatformLang = '%s' (%zu bytes)\n", (char *)buf, sz);
axl_free(buf);
}
get_alloc returns -1 for missing variables (and for backends that
allow 0-byte values, succeeds with sz == 0 and a 1-byte NUL
allocation; UEFI’s SetVariable(size=0) means “delete” so the
empty path doesn’t surface there).
Vendor Namespaces
Vendor variables (Dell/HPE/Lenovo OEM keys) plug in via namespace
registration so consumer call sites stay UEFI-free — they reference
namespaces by name only. The backend token is opaque (a const AxlGuid * on UEFI; on a future Linux backend it could be a path
prefix):
extern const AxlGuid AXL_OEM_VENDOR_GUID; // declared per-vendor
axl_nvstore_register_namespace("oem", &AXL_OEM_VENDOR_GUID);
axl_nvstore_get("oem", "AssetTag", buf, &sz);
Other operations: axl_nvstore_delete, axl_nvstore_iter (walk
all keys in a namespace), axl_nvstore_get_attrs (read AXL_NV_*
flags without reading the value).
Boot Options
Typed wrappers over the Boot####/BootOrder/BootNext/BootCurrent
firmware-variable family. The EFI_LOAD_OPTION wire codec stays
internal to AxlBoot — consumers operate on AxlBootOption structs:
AxlBootOption opt;
if (axl_boot_option_get(0x0001, &opt) == 0) {
axl_printf("Boot0001: %s\n path: %s\n",
opt.description, opt.device_path ?: "(unknown)");
axl_boot_option_free(&opt);
}
uint16_t *order;
size_t n;
if (axl_boot_order_get(&order, &n) == 0) {
for (size_t i = 0; i < n; i++) {
axl_printf(" %zu: Boot%04X\n", i, order[i]);
}
axl_free(order);
}
Set/delete options (_option_set, _option_delete), reorder boot
sequence (_order_set), or arm a one-shot (_next_set / _next_clear).
Encoding device paths to/from text uses the firmware’s
EFI_DEVICE_PATH_TO_TEXT_PROTOCOL / _FROM_TEXT_PROTOCOL —
_set returns -1 if the from-text protocol isn’t published.
x86 I/O Ports
Public wrappers around in/out for legacy hardware that hasn’t
moved to MMIO (CMOS, SuperIO, IPMI KCS, port-based ACPI PM blocks):
#if defined(__x86_64__) || defined(__i386__)
uint8_t v = axl_io_port_read8(0x70);
axl_io_port_write8(0x71, v | 0x80);
#endif
Build-gated to x86 — calls compile out on AArch64, so wrong-arch usage surfaces as a link error rather than a silent runtime no-op. 8/16/32-bit variants for read and write.
Physical-Memory Access
For tools that scan ROM regions, peek at MMIO control registers,
or search firmware tables. The _map/_unmap pair is the held
abstraction; one-shot _read{8,16,32,64} / _write{8,16,32,64}
helpers cover the typical “I just want one byte” case without
boilerplate. UEFI is identity-mapped so map is effectively a
no-op; the abstraction exists for portability — a future Linux
backend would mmap("/dev/mem") on the way in.
// Held mapping over multiple accesses.
void *va;
if (axl_mem_phys_map(0xFEE00000, 4096, &va) == 0) {
uint32_t apic_id = *(volatile uint32_t *)((uint8_t *)va + 0x20);
axl_mem_phys_unmap(va, 4096);
}
// One-shot read.
uint32_t signature;
axl_mem_phys_read32(0xE0000, &signature);
axl_mem_phys_search does a byte-by-byte scan for a needle
within a mapped region — useful for finding signatures inside
firmware blobs.
Watchdog
UEFI starts every loaded image with a 5-minute boot-services watchdog (UEFI 2.11 §7.5). Long-running diagnostics get killed without warning unless they take action:
// Disable entirely (typical for diagnostics that exceed 5 min).
axl_watchdog_disarm();
// Or extend without disabling protection.
axl_watchdog_set(900); // 15 minutes
// ... long-running work ...
axl_watchdog_pet(); // re-arm to the same window
Random Bytes
Thin wrapper over EFI_RNG_PROTOCOL (UEFI 2.11 §37.5). The
protocol is published by most modern firmware on platforms with
an entropy source (RDRAND on x86, an SBSA TRNG on aa64). Returns
-1 if the protocol isn’t installed — consumers that need a
deterministic fallback layer their own.
uint8_t nonce[16];
if (axl_rng_bytes(nonce, sizeof(nonce)) != 0) {
// RNG not available — bail or fall back
}
Driver Lifecycle
Build DXE drivers with axl-cc --type driver. The driver entry point
is DriverEntry (not main). Call axl_driver_init to set up
the AXL runtime:
EFI_STATUS EFIAPI DriverEntry(EFI_HANDLE ImageHandle,
EFI_SYSTEM_TABLE *SystemTable) {
axl_driver_init(ImageHandle, SystemTable);
axl_printf("Driver loaded\n");
// ...
}
See sdk/examples/driver.c for a complete example.
For long-running services (cross-binary marshalling, structured setup/teardown, foreground or driver-tick deployment), see AxlService — the lifecycle wrapper over AxlLoop that composes axl-driver + axl-config + axl-loop.
Image Lifecycle
For loading and running arbitrary EFI images (not DXE drivers),
use axl_image_*:
AxlImage *img;
if (axl_image_load("fs0:\\boot\\hello.efi", &img) == 0) {
int exit_code = 0;
axl_image_start(img, &exit_code);
axl_image_unload(img);
}
The handle is opaque — EFI_HANDLE and EFI_LOADED_IMAGE_PROTOCOL
never cross the public API. axl_image_* is a thin wrapper over
axl_driver_* (which already handles path-to-device-path
construction and the device-path / buffer load fallback); the only
distinct piece is axl_image_start, which captures the image’s
exit status (axl_driver_start discards it because drivers aren’t
expected to exit cleanly). Forward slashes in the path are
normalized to backslashes.
Image Signature Inspection
For “is this PE file signed and does its signature validate?”
checks without committing to launching the image, use
<axl/axl-image-verify.h>:
AxlImageSignatureInfo info = {0};
if (axl_image_verify_signature("fs0:\\boot.efi",
/* consult_db = */ true,
&info) == 0) {
if (!info.has_signature) {
axl_print("UNSIGNED\n");
} else if (info.consulted_db && !info.signature_valid) {
axl_print("SIGNATURE INVALID against current Secure Boot db\n");
} else {
axl_print("SIGNED%s by '%s' (issued by '%s')\n",
info.consulted_db ? " (db-validated)" : " (presence only)",
info.subject_cn != NULL ? info.subject_cn : "(unknown)",
info.issuer_cn != NULL ? info.issuer_cn : "(unknown)");
}
axl_image_signature_info_free(&info);
}
The presence axis (has_signature) is a pure file-bytes parse of
the PE Certificate Table — no firmware dependencies. The validity
axis (signature_valid + consulted_db) opts into a firmware
dry-run via LoadImage(SourceBuffer) + immediate UnloadImage,
which fires EFI_SECURITY2_ARCH_PROTOCOL callbacks (audit logs,
PCR measurement, dbx notifications) as a side effect — pass
consult_db = false when those side effects matter. The
subject_cn / issuer_cn fields populate from the first
certificate in the PKCS#7 SignedData bundle via an in-tree
DER walker; they’re best-effort diagnostic strings (the formal
way to identify the Authenticode signer is via SignerInfo’s
IssuerAndSerial — out of scope for diagnostic CN output).
Protocol Registry
What UEFI Means by “Protocol”
A UEFI protocol is not a wire protocol or a network spec — it’s the closest thing UEFI has to an object or a vtable. Concretely, a protocol is:
a C struct of function pointers (and sometimes inline state),
identified by a 128-bit GUID,
installed on a handle (a firmware-allocated opaque token that represents some logical entity — a disk, a network port, a driver image, a service endpoint, etc.).
Consumers find a protocol by GUID via the LocateProtocol or
LocateHandleBuffer Boot Services calls; the firmware returns the
struct pointer; the consumer calls the function pointers it carries.
Mental-model translation if you come from elsewhere:
Java / C# / Swift: a UEFI protocol is roughly an interface bound to a specific instance — except identity is a GUID instead of a class type, and instances are handles instead of objects.
COM: very similar to a COM interface — IID-keyed vtable on a handle. UEFI’s design lineage is COM-via-IntelBIOS.
POSIX: there’s no clean parallel. The closest analog is “a device-driver
struct file_operationsregistered in a kobject hierarchy keyed by a UUID instead of a path.”
The naming is awkward and we’re stuck with it because that’s what
the UEFI spec writes everywhere. AXL’s protocol registry is a
name-keyed wrapper over this UEFI-native concept: instead of
shipping a GUID literal at every call site, consumers pass a string
name and the registry resolves it. Internally it still calls
InstallProtocolInterface / LocateProtocol — there is no extra
runtime cost, just less boilerplate and fewer GUIDs to copy-paste.
Using the Registry
Built-in well-known names cover the spec-defined protocols a
portable consumer typically reaches for: "smbios", "shell",
"simple-network", "simple-fs", "device-path", "loaded-image",
"ram-disk", the IPv4 networking family ("tcp4", "tcp4-sb",
"ip4", "ip4-config2", "dhcp4", "dhcp4-sb", "dns4",
"dns4-sb"), and "tcg2".
// Find a protocol (consumer side)
EFI_SMBIOS_PROTOCOL *smbios;
if (axl_protocol_find("smbios", (void **)&smbios) == AXL_OK) {
// ...
}
// Enumerate all handles publishing a protocol
void **handles;
size_t count;
if (axl_protocol_enumerate("simple-fs", &handles, &count) == AXL_OK) {
for (size_t i = 0; i < count; i++) { /* ... */ }
axl_free(handles);
}
Custom Protocol Names
Drivers can publish their own protocols under a project-defined name.
By default axl_protocol_register("my-protocol", &iface, &handle)
synthesizes a deterministic GUID from the name string (FNV-1a).
That works for single-image use, but the GUID is unstable across
typos and not directly usable for cross-image discovery via raw
LocateProtocol. Pin a published vendor GUID once at startup:
static const AxlGuid kMySvcGuid =
AXL_GUID(0xdead0001, 0xbeef, 0xcafe,
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0);
// In DriverEntry
axl_protocol_register_name("my-protocol", &kMySvcGuid);
axl_protocol_register("my-protocol", &mInterface, &mHandle);
External consumers can either use axl_protocol_find("my-protocol", …) (after a matching register_name of their own — names are
per-image) or LocateProtocol(&kMySvcGuid, …) directly against the
published GUID. register_name is idempotent for the same
(name, guid) pair, refuses to shadow built-in well-known names,
and returns AXL_ERR if the name is already pinned to a different
GUID.
Driver-Image Lifecycle
Registered protocols are NOT auto-released when a driver image is
unloaded — the AXL protocol registry never owned the install. Walk
your protocols in axl_driver_set_unload’s callback:
static EFI_STATUS EFIAPI MyUnload(EFI_HANDLE image) {
if (mHandle != NULL) {
axl_protocol_unregister(mHandle, "my-protocol", &mInterface);
}
return EFI_SUCCESS;
}
sdk/examples/driver.c is the canonical reference. Forgetting to
unregister leaves dangling handle entries pointing at freed driver
memory; subsequent LocateProtocol calls hand consumers a stale
vtable and the next dispatch faults.
TPL Contract
All entry points (_register, _register_name, _register_multiple,
_find, _enumerate, _unregister) bottom out in UEFI Boot
Services calls (InstallProtocolInterface, LocateProtocol,
LocateHandleBuffer, UninstallProtocolInterface) plus
axl_malloc, all of which require TPL ≤ TPL_NOTIFY per UEFI 2.11
§7.3. Callers running from a timer handler at TPL_NOTIFY are
fine; callers at TPL_HIGH_LEVEL must lower first. Callbacks
dispatched through AxlLoop (defer-drain, pubsub delivery, source
handlers) all run at TPL_APPLICATION — the loop calls
WaitForEvent, which mandates that level — so consumers writing
protocol-event-driven code don’t need to worry about TPL inside
handlers.
Protocol-Event Subscriptions
For protocols that need to publish events (“client connected”,
“upload complete”), reuse axl_pubsub_* from <axl/axl-pubsub.h>
rather than rolling a protocol-internal callback list. Topics are
string-keyed; multiple consumers can subscribe; delivery is
deferred via the loop’s defer queue, so handlers always fire at
TPL_APPLICATION. Pubsub is AxlLoop-scoped: subscribers must run
on the same loop instance as the publisher to receive events.
Auto-Loading Driver Dependencies
Tools that need a protocol provided by a DXE driver (e.g. a RAM-disk
manager that needs EFI_RAM_DISK_PROTOCOL from RamDiskDxe.efi)
can call axl_driver_ensure to short-circuit when the protocol is
already registered, or to find and load the driver themselves
otherwise:
if (axl_driver_ensure(&EfiRamDiskProtocolGuid,
"RamDiskDxe.efi") != 0) {
axl_printf("RamDiskDxe.efi not available\n");
return 1;
}
/* Protocol is now usable. */
The search walks drivers/<arch>/<name> on the running image’s own
volume first, then the image’s own directory, then the volume root,
and finally every other mounted FAT volume. The first match is
loaded and started; if it doesn’t end up registering the requested
protocol, the image is unloaded and the search continues. This
lets tools work whether they’re invoked from a bare UEFI shell, a
boot menu, or a startup.nsh that has already eager-loaded the
driver.
Tool Diagnostics
When investigating “why doesn’t my tool work on this firmware?”, call
axl_diag_startup(argc, argv) from your -v / --verbose handler.
It prints six labelled sections in one block:
POSIX argc = 3
POSIX argv[0] = "mkrd.efi"
POSIX argv[1] = "-v"
POSIX argv[2] = "testrd"
LOADOPT: size = 38 bytes
LOADOPT: utf8 = "mkrd.efi -v testrd"
SHELL: protocol OK, Argc = 3
SHELL: Argv[0] = "FS0:\mkrd.efi"
...
IMG: path = \mkrd.efi
VOLUMES: 1 mounted
fs0
POSIX argv shows what reached main after axl-app.c parsed
EFI_LOADED_IMAGE_PROTOCOL.LoadOptions. LOADOPT shows the raw
UCS-2 buffer the firmware passed in. SHELL is the optional
EFI_SHELL_PARAMETERS_PROTOCOL probe — some OEM firmware sometimes
doesn’t publish it for cross-volume invocations, which was the
original “argc=1” bug. IMG and VOLUMES are the search anchors
axl_driver_ensure / axl_driver_locate use.
For protocol-registration questions specifically, pair it with
axl_diag_probe_protocol:
if (verbose) {
axl_diag_startup(argc, argv);
axl_diag_probe_protocol(
(const AxlGuid *)&EFI_RAM_DISK_PROTOCOL_GUID,
"EFI_RAM_DISK_PROTOCOL");
}
/* ... call axl_driver_ensure ... */
if (verbose) {
axl_diag_probe_protocol(
(const AxlGuid *)&EFI_RAM_DISK_PROTOCOL_GUID,
"EFI_RAM_DISK_PROTOCOL (post-ensure)");
}
The two probes around axl_driver_ensure show whether the firmware
already had the driver baked in (both ALREADY REGISTERED) or
whether ensure had to load it from disk (NOT registered → REGISTERED).
Configuration (and Command-Line Parsing)
Unified configuration framework. One descriptor table drives defaults,
typed getters, auto-apply to caller structs via offsetof, callbacks
for custom logic, parent inheritance for cascading defaults, and
command-line argument parsing (short flags, long flags, repeatable
multi-values, positional args, and --).
AxlConfig replaces ad-hoc key-value parsing with a declarative
system. You define a table of option descriptors (name, type, default
value, optional short flag, help text), then populate from any source:
defaults, programmatic set, command-line argv, or a parent config.
Type validation happens automatically.
Defining Options
#include <axl.h>
typedef struct {
size_t port;
bool verbose;
size_t max_connections;
} ServerConfig;
static const AxlConfigDesc opts[] = {
{ "port", AXL_CFG_UINT, "8080", 0, "Listen port",
offsetof(ServerConfig, port), sizeof(size_t) },
{ "verbose", AXL_CFG_BOOL, "false", 0, "Verbose output",
offsetof(ServerConfig, verbose), sizeof(bool) },
{ "max.conn", AXL_CFG_UINT, "16", 0, "Max connections",
offsetof(ServerConfig, max_connections), sizeof(size_t) },
{ 0 }
};
Creating and Querying
ServerConfig sc;
AXL_AUTOPTR(AxlConfig) cfg = axl_config_new(opts);
// Set the auto-apply target -- values are written directly
// into the struct fields via offsetof
axl_config_set_target(cfg, &sc);
// Set values (type-validated)
axl_config_set(cfg, "port", "9090"); // sc.port = 9090
axl_config_set(cfg, "verbose", "true"); // sc.verbose = true
// Query values
size_t port = axl_config_get_uint(cfg, "port");
const char *port_str = axl_config_get(cfg, "port"); // "9090"
Command-Line Parsing
CLI parsing moved to AxlArgs (<axl/axl-args.h>) — see the
Command-Line Parsing (AxlArgs) section below. AxlConfig stays
focused on the live property-bag use case (HTTP client/server
settings, future modules with tunable runtime properties).
Multi-Value Options
For options that can be specified multiple times (e.g., -H "Name: Value"):
size_t count = axl_config_get_multi_count(cfg, "headers");
for (size_t i = 0; i < count; i++) {
const char *hdr = axl_config_get_multi(cfg, "headers", i);
axl_printf(" header: %s\n", hdr);
}
Standard option groups (group injection)
Networked tools repeat the same NIC / local-IP / port
descriptors over and over. axl_config_descs_net emits the
canonical entries into a consumer-owned accumulator, with
descriptor offsets shifted by the consumer’s embedded
AxlNetOpts sub-struct offset:
typedef struct {
AxlNetOpts net; // the standard sub-struct
const char *url;
bool read_only;
} MountOpts;
static const AxlConfigDesc mount_consumer_descs[] = {
{ "url", AXL_CFG_STRING, "", "Server URL",
offsetof(MountOpts, url), sizeof(((MountOpts*)0)->url) },
{ "read-only", AXL_CFG_BOOL, "false", "Mount read-only",
offsetof(MountOpts, read_only), sizeof(bool), 'r' },
{ 0 }
};
static AxlConfigDesc mount_descs[16];
void mount_descs_init(void) {
size_t n = axl_config_descs_net(mount_descs, ARRAY_SIZE(mount_descs),
AXL_NET_OPT_SERVER,
offsetof(MountOpts, net));
n += axl_config_descs_append(mount_descs + n,
ARRAY_SIZE(mount_descs) - n - 1,
mount_consumer_descs);
mount_descs[n] = (AxlConfigDesc){ 0 };
}
AXL_NET_OPT_CLIENT / _SERVER presets cover the common cases;
finer-grained bitmasks (AXL_NET_OPT_NIC | AXL_NET_OPT_PORT)
also work. AXL_NET_OPT_SOURCE_IP and AXL_NET_OPT_LISTEN_IP
both target the same local_ip field (same bind(2) syscall);
they differ only in CLI vocabulary — pick whichever matches your
tool’s role. The emitted descriptors preserve short_name /
choices and route through AxlConfig’s auto-apply machinery
exactly like the consumer’s own table. See <axl/axl-net-opts.h>
for the option-bag types and the matching axl_net_init_from_opts
bring-up helper.
The companion axl_config_descs_append copies a consumer-owned
descriptor fragment (terminated by {0}) onto the accumulator;
the caller writes the final {0} terminator once, after all
fragments have been appended.
Parent Inheritance
Create a child config that inherits defaults from a parent:
AxlConfig *defaults = axl_config_new(opts);
axl_config_set(defaults, "port", "8080");
AxlConfig *override = axl_config_new_with_parent(opts, defaults);
// override inherits "port"="8080" until explicitly set
Command-Line Parsing (AxlArgs)
Declarative CLI parser — the tool declares a static AxlArgsNode
tree, calls axl_args_run from main, and the framework parses
argv, validates types and bounds, generates --help, and dispatches
to the matching leaf handler.
Header: <axl/axl-args.h>.
One node type, three shapes
A single recursive node type (AxlArgsNode) describes the program
root, every inner branch (“category”), and every leaf verb. A node is
exactly one of:
Leaf —
handlerset,verbsNULL. Optionally haspositionals. Handler runs once parsing completes at this level.Branch —
verbsset (NULL-terminated array of child nodes),handlerNULL. Positionals MUST be NULL (the first non-flag argument is the verb name).Single-handler app — root happens to be a leaf (no verbs). The whole tool is one shape.
A node with both, or neither, is a configuration error and the parser exits non-zero before invoking anything.
Single-handler tool
static int do_run(AxlArgs *a) {
const char *path = axl_args_get_string(a, "path");
return process(path);
}
int main(int argc, char **argv) {
return axl_args_run(argc, argv, &(AxlArgsNode){
.name = "mytool", .help = "Process a file",
.positionals = (AxlArgDesc[]){
{ .name = "path", .type = AXL_ARG_STRING, .required = true,
.help = "Input file" },
{0}
},
.handler = do_run,
});
}
Multi-verb tool
static const AxlArgsNode verbs[] = {
{ .name = "show", .handler = do_show, .positionals = slot_pos,
.help = "Decoded fields for one slot" },
{ .name = "list", .handler = do_list,
.help = "List populated slots" },
{0}
};
int main(int argc, char **argv) {
return axl_args_run(argc, argv, &(AxlArgsNode){
.name = "memspd", .help = "Read JEDEC SPD content",
.flags = flags, .verbs = verbs,
});
}
Argument types
AxlArgDesc.type selects the parser. Numeric types
(AXL_ARG_U8..AXL_ARG_S64) accept optional min / max bounds
and a base (0 = auto-detect, 10, or 16). String types get one
more knob:
AXL_ARG_BOOL— presence flag, no valueAXL_ARG_STRING— unconstrained stringAXL_ARG_MULTI— repeatable string (variadic positional, or repeatable flag); accumulates intoaxl_args_get_multiAXL_ARG_U8/AXL_ARG_U16/AXL_ARG_U32/AXL_ARG_U64/AXL_ARG_S64— typed integers with boundsAXL_ARG_CHOICE— string restricted to a caller-supplied set:
static const char *const fields[] = {
"noHdds", "riserCfg", "delRiser", NULL
};
static const AxlArgDesc field_pos[] = {
{ .name = "field", .type = AXL_ARG_CHOICE, .required = false,
.choices = fields,
.default_value = "noHdds",
.help = "field selector" },
{0}
};
The framework rejects values not in choices with a
breadcrumb-prefixed error matching the out-of-range numeric format,
and lists the accepted values as <noHdds|riserCfg|delRiser> in
--help output. Comparison is case-sensitive by default. Setting
choices to NULL or an empty array degrades to AXL_ARG_STRING
(unconstrained); useful when the caller wants <a|b|c> help text
but custom validation in the handler.
For case-insensitive match — useful when migrating CLIs that
already accept mixed-case variants — set .choices_case_insensitive = true:
{ .name = "field", .type = AXL_ARG_CHOICE,
.choices = fields,
.choices_case_insensitive = true,
.help = "field selector" }
--help then renders <noHdds|riserCfg|delRiser> (case-insensitive)
so users know dd_cfg and DD_CFG both work. The value the
handler sees retains the user’s original casing — only validation
folds case. ASCII-only fold (per axl_strcasecmp); non-ASCII bytes
compare byte-equal.
Nested verbs (<top> <category> <verb>)
static const AxlArgsNode bios_verbs[] = {
{ .name = "test", .handler = bios_test, .help = "Run BIOS self-test" },
{ .name = "pci", .handler = bios_pci, .help = "List BIOS-PCI map" },
{0}
};
static const AxlArgsNode top_verbs[] = {
{ .name = "bios", .verbs = bios_verbs,
.help = "BIOS / SMBIOS subcommands" },
{ .name = "load", .handler = do_load, .positionals = load_args,
.help = "Load and run a UEFI image" },
{0}
};
int main(int argc, char **argv) {
return axl_args_run(argc, argv, &(AxlArgsNode){
.name = "do", .help = "Hardware diagnostic CLI",
.flags = root_flags,
.verbs = top_verbs,
});
}
do bios test invokes the leaf with the breadcrumb in scope; if
the user types do bios flarble, the error reads
do bios: unknown verb 'flarble'. do bios --help recurses into
the bios subtree’s auto-generated help.
Branch with a default handler
A node can set BOTH verbs and handler — the handler runs only
when no sub-verb is supplied. Useful for the do bios → “print
summary” pattern where a category has subverbs but also wants a
default action:
static const AxlArgsNode bios_verbs[] = {
{ .name = "info", .handler = bios_info, .help = "Type 0 summary" },
{ .name = "test", .handler = bios_test, .help = "Walk every record" },
{0}
};
static const AxlArgsNode bios_node = {
.name = "bios",
.help = "BIOS / SMBIOS subcommands",
.verbs = bios_verbs,
.handler = bios_info, // fires on 'do bios' with no sub-verb
};
Dispatch is unambiguous: a verb argument that matches a child
recurses into it; a verb argument that matches none errors as
do bios: unknown verb 'flarble' (the handler is not a
catch-all); no verb argument at all invokes the handler with the
branch’s parsed flags. Branch+handler nodes still cannot have
positionals — the first non-flag is structurally the verb name.
In do bios --help output, the verb whose handler matches the
default is annotated (default) so users see which sub-verb the
no-arg form is equivalent to.
Parent-flag visibility
Flags declared on a parent node are visible to descendant handlers
via the same accessors. A --verbose declared on the root is
readable from a leaf two levels deep:
static int bios_test(AxlArgs *a) {
bool verbose = axl_args_get_bool(a, "verbose"); // root flag
uint8_t slot = (uint8_t)axl_args_get_uint(a, "slot"); // leaf positional
/* ... */
}
Same for axl_args_user_data — descendants inherit the nearest
non-NULL value walking up the chain.
Error attribution
Errors are prefixed with the full breadcrumb path so users know exactly which level rejected their input:
do bios test: 'foo' for --slot is not a valid integer
do pci: unknown verb 'flarble'
do: unknown flag --verbosee
Lifetime
AxlArgs and accessor return values live until the leaf handler
returns. String values point into argv (program-lifetime); copy
numeric values, copy variadic-positional pointers if you need them
past handler return. Never call axl_args_get_* from a loop
callback that fires after the handler returns — extract everything
into local state inside the handler first.
Path Manipulation
Path manipulation: basename, dirname, extension, join, resolve. Handles
both / (Unix) and \ (UEFI) path separators. All allocated results
are freed with axl_free().
UEFI uses backslash (\) as the path separator, while most developers
are familiar with forward slash (/). AXL accepts both and normalizes
internally. Paths typically start with a volume name: fs0:/path/to/file.
AXL_AUTO_FREE char *base = axl_path_get_basename("fs0:/logs/app.log");
// base = "app.log"
AXL_AUTO_FREE char *dir = axl_path_get_dirname("fs0:/logs/app.log");
// dir = "fs0:/logs"
AXL_AUTO_FREE char *ext = axl_path_get_extension("app.log");
// ext = "log"
AXL_AUTO_FREE char *full = axl_path_join("fs0:/data", "output.json");
// full = "fs0:/data/output.json"
// Resolve relative paths
char resolved[256];
axl_path_resolve("fs0:/app", "../config/app.cfg",
resolved, sizeof(resolved));
// resolved = "fs0:/config/app.cfg"
Synchronization primitives
AxlCompletion / AxlCancellable / axl_wait_* and the foundational
AxlEvent moved out of AxlUtil into the dedicated
src/event/ module. See
src/event/README.md for the current documentation.
API Reference
AxlSys
Defines
-
AXL_GUID(d1, d2, d3, d4_0, d4_1, d4_2, d4_3, d4_4, d4_5, d4_6, d4_7)
Initialize an AxlGuid from literal values.
Usage: AxlGuid
g = AXL_GUID(0x12345678, 0xABCD, 0xEF01,
0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01);
-
AXL_RESET_COLD
cold reset (full power cycle)
-
AXL_RESET_WARM
warm reset (CPU reset, memory preserved)
-
AXL_RESET_SHUTDOWN
power off
Typedefs
-
typedef void *AxlHandle
Opaque handle for any UEFI-tracked entity.
axl-sys.h:
System operations — reset, device mapping refresh. UEFI-specific, no GLib equivalent.
Binary-compatible with
EFI_HANDLE(bothvoid *). Use in public API and consumer code wherever a UEFI handle would appear — image handles fromDriverEntry, protocol handles returned byaxl_protocol_register, controller handles passed toaxl_driver_connect_handle, handles returned byaxl_protocol_enumerate.AxlHandleis an opaque token; never dereference it. The EFI_* spelling stays available via<uefi/axl-uefi.h>for the rare consumer that needs to call agBS->...(EFI_HANDLE)directly.
-
typedef struct AxlSystemTable AxlSystemTable
Opaque firmware system-table pointer.
Forward-decl for
EFI_SYSTEM_TABLE. Drivers receive anAxlSystemTable *from theAXL_DRIVERadapter and pass it straight toaxl_driver_init; consumers never dereference it. Reach for<uefi/axl-uefi.h>’s typedEFI_SYSTEM_TABLEonly when you need to poke at firmware internals the AXL surface doesn’t cover.
-
typedef int (*AxlDevicePathFn)(uint8_t type, uint8_t subtype, const void *node, void *user)
Per-node callback for axl_device_path_for_each.
Return 0 to continue iteration, any non-zero value to stop — the return value is propagated back from
axl_device_path_for_eachso callbacks can use it as a found-flag, error code, or count.nodepoints at the full device-path node (4-byte header followed by payload); cast it to the corresponding spec struct (e.g.VENDOR_DEVICE_PATH *) oncetypeandsubtypehave confirmed the shape.
Functions
-
static inline bool axl_guid_cmp(const AxlGuid *a, const AxlGuid *b)
Compare two GUIDs for equality.
- Returns:
true if equal.
-
int axl_guid_v5(const AxlGuid *namespace_uuid, const char *name, AxlGuid *out)
Derive a deterministic GUID from a (namespace, name) pair.
Name-based UUID generation in the shape of RFC 4122 §4.3 (UUIDv5): the SHA-1 of
namespace_bytes || name_bytesis truncated to 16 bytes, with the version field set to 5 and the RFC-4122 variant bits set on the result. Same(namespace, name)always yields the same GUID, across binaries / arches / runs.Used by AxlService to derive each service’s identity GUID from
AxlService.nameso consumers don’t have to hand-allocate a UUID per service. Other AXL modules that want stable GUIDs from string keys can use the same primitive.Namespace bytes are fed verbatim — AxlGuid’s storage layout (data1/data2/data3 in host byte order) is what hashes, NOT the RFC-4122 network-byte-order serialization. The 16-byte result is likewise stored as opaque AxlGuid bytes; AXL never reads its data1/data2/data3 fields as host-order ints once derived. This is an internal AXL convention, not strict RFC 4122 — GUIDs derived here won’t match what a UUIDv5 generator on another platform would produce given “the same” namespace UUID written in canonical text form. That’s fine for AXL’s use case (derivation lives entirely inside AXL) and avoids a host-vs-network endian conversion that would otherwise creep into every caller.
- Parameters:
namespace_uuid – namespace UUID (e.g. an AXL module’s identity)
name – NUL-terminated name string
out – [out] derived GUID
- Returns:
AXL_OK on success (
outpopulated); AXL_ERR ifnamespaceornameis NULL oroutis NULL.
-
bool axl_device_path_has_vendor(void *device_path, const AxlGuid *guid)
Check if a device path contains a vendor node with the given GUID.
Walks the device path node chain looking for a hardware vendor node (type 0x01, subtype 0x04) whose GUID matches
guid.- Parameters:
device_path – device path (from “device-path” protocol)
guid – vendor GUID to match
- Returns:
true if a matching vendor node is found.
-
int axl_device_path_for_each(const void *device_path, AxlDevicePathFn fn, void *user)
Walk a device-path node chain with bounded-step safety.
Iterates from
device_paththrough the END node, callingfnon each node with its(type, subtype, node)triple. Stops early whenfnreturns non-zero (and propagates that value), or when a malformed node is hit (length < 4 or the chain doesn’t terminate within an internal step cap).Replaces hand-rolled
while (!EFI_DP_IS_END(node)) ...loops — those used to differ on whether they bounded the walk, leaving malformed firmware data able to runaway.- Parameters:
device_path – device path (from “device-path” protocol)
fn – per-node callback
user – opaque user pointer for the callback
- Returns:
0 on a clean traversal to END, the callback’s non-zero return value if it stopped early, or -1 on malformed input.
-
const void *axl_device_path_find(const void *device_path, uint8_t type, uint8_t subtype)
Find the first device-path node matching (type, subtype).
- Parameters:
device_path – device path (from “device-path” protocol)
type – node type to match
subtype – node subtype to match
- Returns:
pointer to the node (cast to the corresponding spec struct by the caller), or NULL if no match.
-
size_t axl_device_path_size(const void *device_path)
Compute the total byte length of a device path INCLUDING the END node.
Useful when copying / appending device paths, e.g. when building a LoadImage argument out of an existing volume DP plus a file suffix. Bounded by the same step cap as the iterator.
- Parameters:
device_path – device path (from “device-path” protocol)
- Returns:
size in bytes, or 0 on malformed input.
-
char *axl_device_path_to_text(const void *device_path)
Render a device path as the firmware’s canonical text form.
Wraps the EFI_DEVICE_PATH_TO_TEXT_PROTOCOL the firmware exposes (
ConvertDevicePathToText) and converts the resulting UCS-2 string to UTF-8. The output is the same formatdh -dandbcfg boot dumpproduce — e.g.PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1).Returns NULL when the firmware doesn’t expose EFI_DEVICE_PATH_TO_TEXT_PROTOCOL (some vintage UEFI 2.0 builds omit it) or when
device_pathis NULL.- Parameters:
device_path – device path (from “device-path” protocol)
- Returns:
UTF-8 string allocated with
axl_malloc, or NULL on failure. Caller frees withaxl_free.
-
void axl_reset(int type)
Reset or shut down the system.
Does not return on success.
- Parameters:
type – AXL_RESET_COLD, AXL_RESET_WARM, or AXL_RESET_SHUTDOWN
-
int axl_map_refresh(void)
Rescan device-to-filesystem mappings.
Equivalent to the Shell “map -r” command. Call after hot-plugging a USB drive or after a driver installs a new filesystem.
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_sys_get_firmware_info(AxlFirmwareInfo *info)
Get firmware information (vendor, revision, spec version).
- Parameters:
info – [out] receives firmware info
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_sys_get_memory_size(uint64_t *total_bytes)
Get total usable memory size in bytes.
Queries the firmware memory map and sums all usable regions.
- Parameters:
total_bytes – [out] receives total usable RAM
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_handle_get_protocol(void *handle, const char *name, void **interface)
Get a protocol interface from a specific handle.
- Parameters:
handle – handle from axl_protocol_enumerate
name – protocol name (e.g., “device-path”, “simple-fs”)
interface – [out] protocol interface pointer
- Returns:
AXL_OK on success, AXL_ERR if not found.
-
int axl_protocol_register_name(const char *name, const AxlGuid *guid)
Pin a stable vendor GUID to a custom protocol name.
By default
axl_protocol_register("custom-name", ...)synthesizes a deterministic GUID from the name string via FNV-1a. That works for single-image use, but the GUID is unstable across name spelling (a typo gives a different GUID) and isn’t usable for cross-image discovery via rawLocateProtocolbecause external consumers can’t reproduce it without the same name string.Calling
axl_protocol_register_name(name, guid)once at startup pinsnametoguidin the per-process registry, so subsequentaxl_protocol_register/_find/_enumerate/_unregistercalls for that name install or look up againstguidinstead. Other consumers can publish the GUID in their own headers andLocateProtocolagainst it without going through the AXL protocol-registry layer at all.Idempotent: re-registering the same
(name, guid)pair returnsAXL_OK. Re-registering a name with a different GUID, or registering a name already in the built-in well-known table (e.g. “smbios”, “simple-fs”), returnsAXL_ERR. Names are copied internally;namedoes not need to outlive the call.Process-lifetime: the registration persists for the lifetime of the running image. There is no
axl_protocol_unregister_name; unregistering a handle withaxl_protocol_unregisterdoes not remove the name pinning, since other consumers may still want to reuse the same name → GUID mapping. The custom-name table is fixed-capacity (16 entries per image) and statically linked into each image — consumers loading and unloading drivers on a tight loop should pin once at first init, not on every iteration.Cross-image discovery: each image has its own copy of the AXL protocol-registry layer (via static linkage of libaxl.a), so a consumer in image B that wants to find a protocol published by image A must either (a) call
axl_protocol_register_nameitself with the same(name, guid)pair before callingaxl_protocol_find, or (b) callLocateProtocoldirectly against the published GUID without going through the AXL protocol-registry layer.- Parameters:
name – protocol name (copied internally)
guid – vendor GUID to bind to
name
- Returns:
AXL_OK on success or idempotent re-register; AXL_ERR if
nameis NULL/empty,guidis NULL, the name shadows a built-in well-known name, the name is already pinned to a different GUID, or the registry is full.
-
int axl_protocol_find(const char *name, void **interface)
Find a system protocol by name.
Looks up a named protocol in the platform protocol registry. Well-known names: “smbios”, “shell”, “simple-network”, “simple-fs”. Custom names work too — names registered via
axl_protocol_register_nameresolve to their pinned GUID; unregistered custom names fall back to a deterministic FNV-1a GUID derived from the name string.Cross-image gotcha: the name → GUID table is per-image. A consumer that wants to find a custom-named protocol published by a different image must first call
axl_protocol_register_namewith the same(name, guid)pair, or skip the name layer andLocateProtocolagainst the published GUID directly.- Parameters:
name – protocol name
interface – [out] service interface pointer
- Returns:
AXL_OK on success, AXL_ERR if not found.
-
int axl_protocol_enumerate(const char *name, void ***handles, size_t *count)
Enumerate all handles providing a named protocol.
Caller frees the returned handles array with axl_free().
- Parameters:
name – protocol name
handles – [out] array of handles
count – [out] number of handles
- Returns:
AXL_OK on success (count may be 0), AXL_ERR on error.
-
int axl_protocol_register(const char *name, void *interface, void **handle)
Register a protocol on a handle.
Creates a new handle if *handle is NULL.
- Parameters:
name – protocol name
interface – protocol interface to install
handle – [in/out] handle (NULL to create new)
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_protocol_unregister(void *handle, const char *name, void *interface)
Unregister a protocol from a handle.
- Parameters:
handle – handle from axl_protocol_register
name – protocol name
interface – interface to remove
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_protocol_find_guid(const AxlGuid *guid, void **interface)
Locate a protocol interface by GUID directly.
GUID-keyed counterpart to axl_protocol_find. Skips the name-registry name → GUID lookup; useful for consumers that already hold a GUID (notably AxlService, whose identity is the name-derived GUID from axl_service_guid). Returns the first interface registered for the GUID via
LocateProtocolsemantics.- Parameters:
guid – protocol GUID to look up (must be non-NULL)
interface – [out] service interface pointer
- Returns:
AXL_OK on success (
interfacepopulated); AXL_ERR if no handle publishes the GUID or arguments are NULL.
-
int axl_protocol_enumerate_guid(const AxlGuid *guid, void ***handles, size_t *count)
Enumerate all handles publishing a protocol by GUID directly.
GUID-keyed counterpart to axl_protocol_enumerate. Returns an
axl_malloc'darray of handles publishingguid; caller frees withaxl_free. Used byaxl_service_stopto discover every driver image that registered the service’s identity GUID (typically one, but the contract handles N for symmetry with the underlyingLocateHandleBuffer).Empty result (no handle publishes the GUID) is success:
handlesis set to NULL andcountto 0.- Parameters:
guid – protocol GUID to look up (must be non-NULL)
handles – [out] axl_malloc’d handle array (may be NULL on empty)
count – [out] number of handles
- Returns:
AXL_OK on success (count may be 0); AXL_ERR on bad arguments or firmware allocation failure.
-
int axl_protocol_register_guid(const AxlGuid *guid, void *interface, void **handle)
Register a protocol on a handle by GUID directly.
Skips the name-registry name → GUID lookup. Used by AxlService’s AXL_SERVICE_DRIVER macro to publish a sentinel handle for the service’s identity GUID without going through
axl_protocol_register_name(which would pollute the per-image name table). Also useful for consumers that already have a GUID and don’t need the name layer.Creates a new handle if
*handleis NULL.- Parameters:
guid – protocol GUID
interface – protocol interface to install
handle – [in/out] handle (NULL to create new)
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_protocol_unregister_guid(void *handle, const AxlGuid *guid, void *interface)
Unregister a protocol by GUID directly.
GUID-based counterpart to axl_protocol_unregister.
- Parameters:
handle – handle from axl_protocol_register_guid
guid – protocol GUID
interface – interface to remove
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_protocol_register_multiple(void **handle, ...)
Register multiple protocols on a handle atomically.
Installs one or more protocols on the same handle in one operation. If any fails, none are installed. Creates a new handle if *handle is NULL. Pass name/interface pairs followed by NULL:
void *h = NULL; axl_protocol_register_multiple(&h, "simple-fs", &my_fs, "device-path", &my_dp, NULL);
- Parameters:
handle – [in/out] handle (NULL to create new)
- Param :
name, interface pairs, terminated by NULL
- Returns:
AXL_OK on success, AXL_ERR on error.
-
struct AxlGuid
- #include <axl-sys.h>
UEFI-compatible GUID in standard C types.
Binary-compatible with EFI_GUID. Use in public API so consumer apps don’t need
<uefi/axl-uefi.h>for GUID operations.
-
struct AxlFirmwareInfo
- #include <axl-sys.h>
Firmware information.
AxlEnv
Functions
-
char *axl_getenv(const char *name)
Get a shell environment variable.
axl-env.h:
Shell environment variable access.
char *home = axl_getenv("path"); if (home != NULL) { axl_printf("path=%s\n", home); axl_free(home); } axl_setenv("myvar", "hello", true);
Returns a UTF-8 copy of the variable’s value. Caller frees with axl_free().
- Parameters:
name – variable name (UTF-8)
- Returns:
value string, or NULL if not found.
-
int axl_setenv(const char *name, const char *value, bool overwrite)
Set a shell environment variable.
- Parameters:
name – variable name (UTF-8)
value – value (UTF-8)
overwrite – if false, don’t replace existing value
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_unsetenv(const char *name)
Remove a shell environment variable.
- Parameters:
name – variable name (UTF-8)
- Returns:
AXL_OK on success, AXL_ERR on error.
AxlTime
Defines
-
AXL_TIME_TZ_UNSPECIFIED
Sentinel for
AxlRealtime.timezone_minuteswhen the firmware does not report a timezone (corresponds to UEFI’sEFI_UNSPECIFIED_TIMEZONE= 2047).
-
AXL_TIME_FLAG_DAYLIGHT
Bit set in
AxlRealtime.flagsif the time is currently in DST.
Functions
-
size_t axl_time_format(char *buf, size_t buf_size)
Format current time as ISO 8601 with microseconds.
axl-time.h:
Time utilities: ISO 8601 formatting and a monotonic counter.
Sleep and wait primitives live in <axl/axl-wait.h> — that’s where to find axl_sleep / axl_msleep / axl_usleep (ergonomic void-return sleep) and the axl_wait_* family (condvar-style blocking with cancel + timeout).
Example: “2026-03-27T14:05:32.123456”
- Parameters:
buf – destination buffer (at least 28 bytes)
buf_size – size of buffer
- Returns:
characters written (excluding NUL), 0 on error.
-
uint64_t axl_time_get_ms(void)
Get a monotonic millisecond counter.
Based on firmware time — not wall-clock accurate but monotonically increasing within a boot session. Useful for measuring elapsed time.
- Returns:
milliseconds since an arbitrary epoch (typically boot).
-
uint64_t axl_time_get_us(void)
Get a high-resolution monotonic microsecond counter.
Reads the architecture’s cycle counter (x86 TSC / aarch64 CNTPCT_EL0). The first call calibrates against a brief firmware stall and returns 0; subsequent calls are cheap (one counter read + a multiply) and return microseconds since the calibration call. No defined relationship to wallclock time — pair with axl_time_get_ms when you need both elapsed-microsecond resolution and a wallclock anchor.
Use this instead of axl_time_get_ms when the measurement window is on the order of milliseconds or shorter (firmware stall calibration, network round-trips, parser benchmarking).
- Returns:
microseconds since the implicit calibration epoch. Returns 0 on the very first call (calibration tick) and on architectures with no usable cycle counter.
-
int axl_time_realtime(AxlRealtime *out)
Read the firmware real-time clock.
Backend-neutral wrap of
EFI_RUNTIME_SERVICES.GetTime. Allocation- free; safe to call from CPU exception context. The returnedAxlRealtimecarries the full GetTime payload (year/month/day/ hour/minute/second/nanosecond/timezone/dst) — consumers that only need a packed timestamp pack the fields themselves.- Returns:
AXL_OK on success, AXL_ERR if the firmware reports a failure or
outis NULL. On failureoutis unmodified.
-
struct AxlRealtime
- #include <axl-time.h>
Wallclock time snapshot as exposed by the firmware real-time clock.
Layout-stable; the firmware-side
EFI_TIMEstruct is private to the SDK. Consumers see UTF-8-friendly snake_case field names and a sentinel-based timezone encoding rather than UEFI’sEFI_UNSPECIFIED_TIMEZONEmagic value.Public Members
-
uint16_t year
full year (e.g. 2026)
-
uint8_t month
1-12
-
uint8_t day
1-31
-
uint8_t hour
0-23
-
uint8_t minute
0-59
-
uint8_t second
0-60 (UEFI allows leap-second 60)
-
uint8_t flags
AXL_TIME_FLAG_* bits.
-
uint32_t nanosecond
0-999,999,999
-
int16_t timezone_minutes
UTC offset, or AXL_TIME_TZ_UNSPECIFIED.
-
uint16_t year
AxlNvStore
Defines
-
AXL_NV_VOLATILE
lost on reboot
axl-nvstore.h:
Platform-agnostic non-volatile key-value storage.
Provides persistent storage that survives reboots. On UEFI, this maps to firmware variables (GetVariable/SetVariable). Variables are organized by namespace.
Built-in namespaces: “global” — standard firmware variables (e.g., SecureBoot, BootOrder) “app” — application-specific persistent settings
Vendor namespaces (Dell, HPE, Lenovo OEM variables) plug in via axl_nvstore_register_namespace() with a backend-specific token. On UEFI the token is a
const AxlGuid *(vendor-GUID pointer); on a Linux backend it might be a path prefix. Access sites stay UEFI-free — they reference namespaces by name only.uint8_t secure_boot; size_t sz = sizeof(secure_boot); if (axl_nvstore_get("global", "SecureBoot", &secure_boot, &sz) == 0) { axl_printf("SecureBoot: %s\n", secure_boot ? "enabled" : "disabled"); } extern const AxlGuid AXL_OEM_VENDOR_GUID; axl_nvstore_register_namespace("oem", &AXL_OEM_VENDOR_GUID); axl_nvstore_get("oem", "AssetTag", buf, &sz);
-
AXL_NV_PERSISTENT
survives reboot (non-volatile)
-
AXL_NV_BOOT
accessible during boot services
-
AXL_NV_RUNTIME
accessible at runtime
Typedefs
-
typedef int (*AxlNvstoreIterFn)(const char *key, void *ctx)
Iterator callback for axl_nvstore_iter.
- Return:
0 to continue iteration, non-zero to stop. The non-zero value is returned to the iter() caller.
Functions
-
int axl_nvstore_register_namespace(const char *name, const void *backend_token)
Register a namespace name and bind it to a backend token.
The backend token is opaque to consumers. On UEFI it is a
const AxlGuid *(vendor-GUID pointer); on other backends it may be a path prefix or other identifier. The pointer must remain valid for the lifetime of the program — the table stores the pointer, not a copy.Built-in namespaces “global” and “app” are pre-registered and do not need to be registered explicitly.
- Parameters:
name – namespace name (UTF-8, copied)
backend_token – opaque per-backend token
- Returns:
AXL_OK on success, AXL_ERR if the namespace table is full or the name is already registered with a different token.
-
int axl_nvstore_get(const char *ns, const char *key, void *buf, size_t *size)
Read a value from non-volatile storage.
- Parameters:
ns – namespace (e.g., “global”, “app”)
key – variable name (UTF-8)
buf – output buffer
size – [in/out] buffer size / bytes read
- Returns:
AXL_OK on success, AXL_ERR on error (variable not found, buffer too small, namespace not registered, etc.). On buffer-too-small, size is updated to the required size.
-
int axl_nvstore_get_alloc(const char *ns, const char *key, void **out_buf, size_t *out_size)
Read a value from non-volatile storage into a heap buffer.
Like axl_nvstore_get, but the buffer is allocated for you. Useful when reading variable-length blobs (NV strings, OEM settings) where the caller doesn’t know the size up front and picking a fixed stack buffer either over-allocates or risks truncation. On success,
*out_bufis a heap pointer of*out_sizebytes that the caller frees with axl_free.On failure,
*out_bufis set to NULL and*out_sizeis set to 0. The buffer is allocated with one extra byte beyond*out_sizeand zeroed there, so callers that read string variables can dereference(char *)*out_buf as a NUL-terminated C string when the variable’s payload doesn’t already include a trailing NUL.- Parameters:
ns – namespace (e.g., “global”, “app”)
key – variable name (UTF-8)
out_buf – [out] heap buffer, caller frees with axl_free
out_size – [out] payload size in bytes (excluding the trailing NUL)
- Returns:
AXL_OK on success, AXL_ERR on any error (variable not found, allocation failed, namespace not registered, etc.).
-
int axl_nvstore_set(const char *ns, const char *key, const void *buf, size_t size, uint32_t flags)
Write a value to non-volatile storage.
Passing
flags== 0 (orAXL_NV_VOLATILEalone) defaults toAXL_NV_BOOT— UEFI rejects SetVariable with attribute mask 0 for non-delete writes, so the implementation substitutes boot-services access as the minimal sensible default.- Parameters:
ns – namespace
key – variable name (UTF-8)
buf – data to write
size – data size in bytes
flags – AXL_NV_* flags (0 → AXL_NV_BOOT)
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_nvstore_set_str(const char *ns, const char *key, const char *str, uint32_t flags)
Write a NUL-terminated string to non-volatile storage.
Convenience over axl_nvstore_set: the payload size is
axl_strlen(str)+ 1, including the trailing NUL, so callers don’t need to repeat the+1ritual at every site that stores a string. The empty string""writes a single NUL byte.To delete a string variable, use axl_nvstore_delete — this function does not double as a deleter;
str== NULL is an error so the two operations stay distinct.- Parameters:
ns – namespace
key – variable name (UTF-8)
str – NUL-terminated string to write (must be non-NULL)
flags – AXL_NV_* flags (0 → AXL_NV_BOOT)
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_nvstore_get_str(const char *ns, const char *key, char **out_str)
Read a string-valued variable into a heap buffer.
Convenience over axl_nvstore_get_alloc for string variables. On success,
*out_stris a NUL-terminated heap-allocated C string the caller frees with axl_free. The trailing NUL is guaranteed even if the firmware payload omitted one (the alloc path zero-extends by one byte).On failure,
*out_stris set to NULL.No content validation is performed — if the variable holds binary data with embedded NULs, the returned string ends at the first NUL.
- Parameters:
ns – namespace
key – variable name (UTF-8)
out_str – [out] heap C string, caller frees with axl_free
- Returns:
AXL_OK on success, AXL_ERR on error (variable not found, allocation failed, namespace not registered, etc.).
-
int axl_nvstore_delete(const char *ns, const char *key)
Delete a variable from non-volatile storage.
- Parameters:
ns – namespace
key – variable name (UTF-8)
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_nvstore_get_attrs(const char *ns, const char *key, uint32_t *attrs)
Get a variable’s attribute flags.
- Parameters:
ns – namespace
key – variable name (UTF-8)
attrs – [out] AXL_NV_* flags
- Returns:
AXL_OK on success, AXL_ERR on error (variable not found, namespace not registered, etc.).
-
int axl_nvstore_iter(const char *ns, AxlNvstoreIterFn cb, void *ctx)
Iterate all keys in a namespace.
Walks all variables whose backend token matches the registered namespace’s token, invoking
cbfor each. Stops early ifcbreturns non-zero.- Parameters:
ns – namespace
cb – callback, called once per key
ctx – passed unchanged to
cb
- Returns:
0 if the walk completed, the callback’s non-zero value if it stopped early, or -1 if the namespace is not registered or the iterator failed.
AxlDriver
Typedefs
-
typedef void *AxlDriverHandle
Opaque handle to a loaded driver image.
axl-driver.h:
UEFI driver lifecycle — load, start, connect, disconnect, unload. No GLib equivalent (UEFI-specific).
AxlDriverHandle drv; if (axl_driver_load("fs0:\\MyDriver.efi", &drv) == 0) { axl_driver_start(drv); axl_driver_connect(drv); // ... driver is active ... axl_driver_disconnect(drv); axl_driver_unload(drv); }
Functions
-
int axl_driver_load(const char *path, AxlDriverHandle *handle)
Load a driver image from a file path.
Loads the .efi file into memory but does not start it.
- Parameters:
path – path to .efi driver (UTF-8)
handle – [out] receives driver handle
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_driver_load_buffer(const unsigned char *buf, size_t len, AxlDriverHandle *out_handle)
Load a driver image from a memory buffer.
Buffer-source counterpart to axl_driver_load. Calls
gBS->LoadImagewithSourceBuffer/SourceSizeand noDevicePath, returning the resulting handle for use with axl_driver_set_load_options, axl_driver_start, and axl_driver_unload.Used by tools that
.incbina companion driver into the app to ship as a single binary. For the higher-level AxlService case use axl_service_start_embedded — this primitive is for non-AxlService drivers that still need per-call LoadOptions or explicit handle tracking.The driver is loaded but NOT started. The image’s
LoadedImage->FilePathis left NULL; drivers that read FilePath at startup (some Driver-Binding-style drivers do, notably iPXE) will not work via this entry point — load them by path instead.- Parameters:
buf – driver image bytes (must be non-NULL)
len – length in bytes (must be > 0)
out_handle – [out] driver handle for set_load_options/start/unload
- Returns:
AXL_OK on success (
*out_handleis set); AXL_ERR on argument validation failure or LoadImage failure (*out_handleis set to NULL).
-
int axl_driver_start(AxlDriverHandle handle)
Start a loaded driver image.
Calls the driver’s entry point. The driver registers its binding protocol(s) but does not yet bind to devices.
- Parameters:
handle – driver handle from axl_driver_load
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_driver_connect(AxlDriverHandle handle)
Connect a driver to all matching device handles.
Triggers the driver’s Supported/Start sequence for each compatible device. Call after axl_driver_start.
- Parameters:
handle – driver handle
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_driver_disconnect(AxlDriverHandle handle)
Disconnect a driver from all devices.
Triggers the driver’s Stop sequence for each bound device.
- Parameters:
handle – driver handle
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_driver_unload(AxlDriverHandle handle)
Unload a driver image from memory.
The driver must be disconnected first. Also frees any load-options copy installed via axl_driver_set_load_options() on this handle — the firmware retains the LoadOptions pointer for the loaded-image lifetime, so the AXL-side copy must be released here. Release runs BEFORE gBS->UnloadImage so a UnloadImage failure still doesn’t leak the copy.
- Parameters:
handle – driver handle
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_driver_set_load_options(AxlDriverHandle handle, const void *data, size_t size)
Set load options on a loaded driver image.
Provides configuration data (e.g., a URL) that the driver reads from EFI_LOADED_IMAGE_PROTOCOL.LoadOptions during startup. The data is copied internally — caller’s buffer can be freed after. The copy is owned by AXL and freed by axl_driver_unload() (or by a subsequent set on the same handle, which replaces the previous copy). Pass NULL data to clear load options and free any previous copy. Call between axl_driver_load and axl_driver_start.
AXL tracks at most 16 outstanding driver handles with load options — a 17th call returns AXL_ERR and frees the would-be copy without touching firmware state. Sequential load/unload is the realistic case; if you legitimately need more concurrent driver instances with load options, bump LOAD_OPTIONS_TABLE_SIZE in src/util/axl-driver.c.
- Parameters:
handle – driver handle from axl_driver_load
data – option data (copied; NULL to clear)
size – option data size in bytes
- Returns:
AXL_OK on success, AXL_ERR on bad arguments, alloc failure, HandleProtocol failure, or tracking-table-full.
-
void axl_driver_init(AxlHandle image_handle, AxlSystemTable *system_table)
Initialize the AXL runtime for a DXE driver.
Drivers don’t use AXL_APP / int main(). Call this from DriverEntry to set up firmware table pointers (gST/gBS/gRT) and I/O streams so axl_printf, axl_malloc, etc. work.
Most drivers don’t need to call this directly — the
AXL_DRIVER(entry, unload)macro in<axl.h>emits the DriverEntry stub and wiresaxl_driver_initautomatically. Use this manual path only when your driver publishes spec-defined UEFI protocols (EFI_SIMPLE_FILE_SYSTEM_PROTOCOL,EFI_BLOCK_IO_PROTOCOL, etc.) and you’ve opted into<uefi/axl-uefi.h>for the spec types — in that case cast the firmware-suppliedEFI_HANDLE/EFI_SYSTEM_TABLE *to the AXL parameter types at the call site (the underlying pointers are bit-identical; the cast is a typing-only formality).// AXL-only driver: static int my_main(AxlHandle image, AxlSystemTable *st); static int my_unload(AxlHandle image); AXL_DRIVER(my_main, my_unload) // Spec-protocol publisher (tier 2): EFI_STATUS EFIAPI DriverEntry(EFI_HANDLE h, EFI_SYSTEM_TABLE *st) { axl_driver_init((AxlHandle)h, (AxlSystemTable *)st); ... }
- Parameters:
image_handle – image handle from DriverEntry
system_table – system table from DriverEntry
-
int axl_driver_set_unload(void *unload_fn)
Set the unload callback for the current driver image.
Call from DriverEntry to register a cleanup function that runs when the driver is unloaded. The callback has EFIAPI calling convention — declare it as: EFI_STATUS EFIAPI MyUnload(EFI_HANDLE ImageHandle)
Cleanup contract — what the firmware does NOT do for you:
Services registered via
axl_protocol_register/axl_protocol_register_multipleare NOT auto-released. The AXL protocol registry never owned the install; it issued agBS->InstallProtocolInterfaceand forgot. The unload callback must walk every protocol the driver published and callaxl_protocol_unregisterfor each. Forgetting leaves dangling handle entries that point at freed driver memory — subsequentLocateProtocolcalls hand consumers a stale vtable and the next dispatch faults.Heap allocations made via
axl_mallocare not auto-freed.axl_mem_dump_leaks(DEBUG builds) prints what was missed.Events / timers created via the AxlLoop or backend layer stay live; close them with the matching
_closecalls.
sdk/examples/driver.cshows the canonical shape.- Parameters:
unload_fn – EFIAPI unload function pointer
- Returns:
AXL_OK on success, AXL_ERR on error.
-
char *axl_driver_get_load_options(void)
Get the load options that were passed to the current image.
Returns a UTF-8 copy of the load options string. Caller frees with axl_free(). Useful for drivers that receive configuration (e.g., a URL) via LoadOptions.
- Returns:
options string, or NULL if no options or on error.
-
int axl_driver_get_load_options_raw(const void **out_buf, size_t *out_size)
Get the LoadOptions buffer as raw bytes (no encoding conversion).
UEFI shell launches pass
LoadOptionsas a UCS-2 string — axl_driver_get_load_options is the right entry point for that. Programmatic loaders (axl_driver_set_load_options) pass arbitrary bytes; this entry point hands them back unchanged.Used by AxlService to read its UTF-8 axl_config_to_string payload without misinterpreting it as UCS-2.
- Parameters:
out_buf – [out] borrowed pointer into LoadedImage
out_size – [out] LoadOptionsSize in bytes
- Returns:
AXL_OK on success (out params populated with a borrowed pointer into the firmware’s LoadedImage struct — do NOT free), AXL_ERR if the image has no LoadOptions or HandleProtocol failed.
-
char *axl_driver_get_image_path(void)
Get the filesystem path the current image was loaded from.
Returns a UTF-8 path like “fs0:\drivers\MyDriver.efi”. Useful for finding companion files next to the driver. Caller frees with axl_free().
- Returns:
path string, or NULL if unavailable.
-
int axl_driver_connect_handle(void *handle)
Connect controllers on a specific handle.
Triggers driver binding for one handle (e.g., after installing a filesystem protocol on a new handle). More targeted than axl_driver_connect which reconnects all handles.
- Parameters:
handle – handle to connect (from axl_protocol_register, etc.)
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_driver_locate(const char *driver_name, char *out, size_t out_size)
Find a driver file on disk without loading it.
Walks the same search order axl_driver_ensure() uses (image’s
drivers/<arch>/, image’s own directory, image’sdrivers/, other volumes’drivers/<arch>/) and writes the first matching existing path toout.Useful when the caller needs to control the LoadImage / StartImage lifecycle directly — for example, to set load options between the two for a driver that takes per-invocation configuration:
char path[256]; if (axl_driver_locate("axl-webfs-dxe.efi", path, sizeof(path)) != 0) { axl_printf("axl-webfs-dxe.efi not found\n"); return 1; } AxlDriverHandle h; axl_driver_load(path, &h); axl_driver_set_load_options(h, url_w, url_size); axl_driver_start(h);
Same trust caveat as axl_driver_ensure: searches every mounted FAT volume. Don’t pass attacker-controlled
driver_name.- Parameters:
driver_name – driver filename (e.g. “axl-webfs-dxe.efi”)
out – [out] receives full path on success
out_size – capacity of
outin bytes
- Returns:
AXL_OK on success (path written to
out), AXL_ERR if the driver wasn’t found oroutis too small to hold the result.
-
int axl_driver_ensure(const AxlGuid *protocol_guid, const char *driver_name)
Ensure a protocol-providing driver is loaded.
If
protocol_guidis already registered (LocateProtocol succeeds), returns 0 immediately. Otherwise searches fordriver_nameand loads + starts the first match found, in this order:drivers/<arch>/<driver_name>on the volume the running image booted from<image_dir>/<driver_name>in the running image’s own directorydrivers/<driver_name>at the running image’s volume rootdrivers/<arch>/<driver_name>on every other mounted FAT volume
The arch suffix is “x64” or “aa64”, matching the running image’s architecture. After load+start, LocateProtocol is re-checked; if the protocol still isn’t registered, the driver is unloaded and the function returns -1.
Safe to call multiple times — repeats short-circuit at step 1. EFI_ALREADY_STARTED on StartImage is treated as success.
Typical use, before touching a driver-provided protocol. Note the cast:
EFI_RAM_DISK_PROTOCOL_GUIDis anEFI_GUIDfrom the generated UEFI headers; AxlGuid is layout-compatible, so a const cast lets callers pass it through without including any UEFI headers in their own public surface:if (axl_driver_ensure((const AxlGuid *)&EFI_RAM_DISK_PROTOCOL_GUID, "RamDiskDxe.efi") != 0) { axl_printf("RamDiskDxe.efi not available\n"); return 1; }
Trust model. This function will load the first matching .efi file off any mounted FAT volume — including a USB stick the user just plugged in. UEFI executes loaded drivers with full firmware privileges. Only call this with driver names you trust, and don’t call it with attacker-controlled
driver_namevalues.- Parameters:
protocol_guid – protocol GUID to look up (must be non-NULL)
driver_name – driver filename (e.g. “RamDiskDxe.efi”)
- Returns:
AXL_OK if the protocol is registered (was already, or after loading the driver); AXL_ERR if the driver wasn’t found, failed to load/start, or didn’t register the protocol after starting.
-
int axl_driver_ensure_with_embedded(const AxlGuid *protocol_guid, const char *driver_name, const unsigned char *embedded_buf, size_t embedded_len, const char *override_name, const void *load_options, size_t load_options_size)
Ensure a protocol-providing driver is loaded, with embedded fallback and optional caller override.
Generalizes axl_driver_ensure() for tools that ship a driver blob baked into the .efi binary itself, so they work as self-contained binaries on minimal firmware that omits the corresponding optional UEFI 2.6+ DXE module.
Resolution order:
LocateProtocol(protocol_guid)— short-circuit if firmware already provides the protocol. Most OEM firmware (Dell, HP, Supermicro) ships RamDiskDxe and similar in their firmware volume; this is the common path.If
override_nameis non-NULL, search disk for that name only using axl_driver_ensure()’s 4-path search. The embedded blob is NOT used as a fallback — caller explicitly opted into a specific external driver.Otherwise, search disk for
driver_nameusing the same 4-path search. If found, load + start it.If still not registered and
embedded_bufis non-NULL, callLoadImage(SourceBuffer=embedded_buf, SourceSize=embedded_len)followed byStartImage. No filesystem access.
The embedded path is the safety net — it lets the tool work on firmware that ships neither the protocol nor a user-staged copy.
axl_driver_ensure(g, n)is exactly.axl_driver_ensure_with_embedded(
g, n, NULL, 0, NULL, NULL, 0)
LoadOptions (
load_options/load_options_size): when non-NULL, AXL installs the bytes into the loaded image’sEFI_LOADED_IMAGE_PROTOCOL.LoadOptionsBEFOREStartImageis called, via the same axl_driver_set_load_options path (so unload-time release is automatic). Applied on BOTH the disk-load path (steps 2/3) and the embedded path (step 4). Skipped on the step-1 short-circuit — the firmware-provided protocol implies a driver instance the consumer doesn’t own. Used by AxlService to ship a foreground process’s options through to the driver image (typically via axl_config_to_string).Trust caveat (same as axl_driver_ensure): step 3 will load the first matching .efi off any mounted FAT volume. Don’t pass attacker- controlled
driver_nameoroverride_name. The embedded buffer is whatever the build system baked in — caller’s responsibility to verify provenance.- Parameters:
protocol_guid – protocol GUID to look up (must be non-NULL)
driver_name – canonical driver filename, e.g. “RamDiskDxe.efi”
embedded_buf – embedded .efi bytes (may be NULL)
embedded_len – length of embedded_buf in bytes (0 if NULL)
override_name – user-provided override name (may be NULL)
load_options – LoadOptions to install pre-Start (may be NULL)
load_options_size – size of
load_optionsin bytes (0 if NULL)
- Returns:
AXL_OK if the protocol is registered (was already, or after loading); AXL_ERR if all four steps failed.
-
int axl_driver_load_dir(const char *dir_path, const char *pattern, size_t *loaded_count)
Load, start, and connect all .efi drivers in a directory.
Scans
dir_pathfor files matchingpattern(glob, e.g. “*.efi”). Each matching file is loaded, started, and connected. On return,loaded_countreceives the number of drivers successfully started. Pass NULL forpatternto match all .efi files.- Parameters:
dir_path – directory to scan (UTF-8)
pattern – glob pattern (NULL = “*.efi”)
loaded_count – [out] number of drivers loaded (may be NULL)
- Returns:
AXL_OK on success (even if no drivers found), AXL_ERR on error.
AxlEmbed
Defines
-
AXL_EMBED_DECLARE(name)
Declare extern symbols for a link-time embedded blob.
axl-embed.h:
Helper macros for declaring and using arbitrary binary blobs embedded into a UEFI image at link time. The framework is content-agnostic — driver
.efiimages are the canonical use case (paired with axl_service_start_embedded or axl_driver_load_buffer) but anything works:Trust material (CA bundles, public keys) parsed at startup
Static config files (JSON5, key=value) parsed at startup
HTML / CSS / JS for an embedded HTTP server
Lookup tables, license text, calibration data — anything
The blob is supplied at link time by
axl-cc --embed PATH[=symbol]— axl-cc generates a.sfile with.incbinand links it for you. (AXL’s own build system uses an internalEMBED_BLOBMakefile function for the same purpose; the symbol convention is shared.)Either way, in C the consumer writes:
AXL_EMBED_DECLARE(greeting); axl_printf("%.*s", (int)AXL_EMBED_SIZE(greeting), (const char *)AXL_EMBED_DATA(greeting));
For binary blobs (e.g. an embedded driver image), pass
AXL_EMBED_DATA/AXL_EMBED_SIZEdirectly toAxlServiceDeploy.driver_blob/axl_driver_load_buffer— no cast needed.The bare
nameargument names the blob in C terms (e.g.greeting); the macros prepend theaxl_embedded_prefix that the linker symbols actually use, so the prefix appears in exactly one place (here).See
sdk/examples/embed-asset.cfor a non-driver worked example andsdk/examples/service-demo/launch.cfor the driver case.Emits the
axl_embedded_<name>andaxl_embedded_<name>_endextern declarations the linker resolves against the.incbinsidecar (or the.Sthataxl-cc --embedgenerates).Use at file scope. Pair with AXL_EMBED_DATA and AXL_EMBED_SIZE for read access.
-
AXL_EMBED_DATA(name)
Pointer to the first byte of an embedded blob.
Result type is
const unsigned char *. Pair with AXL_EMBED_DECLARE.
-
AXL_EMBED_SIZE(name)
Size of an embedded blob in bytes.
Result type is
size_t. Pair with AXL_EMBED_DECLARE.
AxlDiag
Functions
-
void axl_diag_startup(int argc, char **argv)
Dump image-launch state to axl_printf if
AXL_DIAGis set.axl-diag.h:
Diagnostic helpers for tools — dump image-launch state, probe protocol registration. Intended to be called from a tool’s -v / —
verbose code path so users can answer “what does my tool
see at startup on this firmware?” without having to recompile.
if (verbose) { axl_diag_startup(argc, argv); axl_diag_probe_protocol(&MY_PROTOCOL_GUID, "MY_PROTOCOL"); }
Prints six labelled sections covering everything a tool typically wants to know on first boot of unfamiliar firmware:
POSIX argc/argv— what reachedmainafter axl-app parsedEFI_LOADED_IMAGE_PROTOCOL.LoadOptions.LOADOPT— the rawLoadOptionsUCS-2 buffer, decoded as UTF-8. Mismatch with POSIX argv would mean axl-app’s tokenizer got confused by quoting or unusual whitespace.SHELL—EFI_SHELL_PARAMETERS_PROTOCOLprobe + its argv if available. Optional protocol; some firmwares (some OEM platforms before fix) don’t publish it for cross-volume invocations.IMG— image path (where the running.efiwas loaded from).VOLUMES— mounted FAT volumes with theirfsNnames. These are the search anchors foraxl_driver_ensure/axl_driver_locate.
Activation: gated on the
AXL_DIAGshell environment variable. Set to any non-empty value (e.g.set AXL_DIAG 1) to enable; unset or empty silences the dump entirely. This frees the-vshort flag in tools to carry their counterpart’s Linux semantics (e.g.grep -v= invert match) instead of being hijacked for cross-tool framework diagnostics.Tools should call this unconditionally near the top of their main handler — the env-var check happens internally. No-op when unset.
Output is plain text via
axl_printf; no allocation beyond the UTF-8 conversion buffers (auto-freed). Safe to call from any application context; not reentrant — don’t call from a log handler.- Parameters:
argc – POSIX argc as received by main
argv – POSIX argv as received by main
-
int axl_diag_probe_protocol(const AxlGuid *protocol_guid, const char *display_name)
Probe whether a protocol is currently registered.
Calls
LocateProtocol(@p protocol_guid)and prints a one-line status toaxl_printf:PROBE: <display_name> ALREADY REGISTERED (LocateProtocol=0x0) PROBE: <display_name> NOT registered (LocateProtocol=0xE0...0E)
Useful around
axl_driver_ensurecalls to show the before/after state of a driver-provided protocol — tells you whether the firmware had it baked in (short-circuit) or whether ensure actually loaded a driver from disk.- Parameters:
protocol_guid – protocol GUID to probe
display_name – short tag for the printed line
- Returns:
AXL_OK if registered, AXL_ERR otherwise.
AxlHexdump
Defines
-
AXL_HEX_GROUP_BYTE
axl-hexdump.h:
Formatted hex+ASCII dump with configurable grouping. Supports direct console output and log integration.
-
AXL_HEX_GROUP_WORD
-
AXL_HEX_GROUP_DWORD
-
AXL_HEX_GROUP_QWORD
-
AXL_HEXDUMP_MAX_SIZE
-
AxlHexDumpLog(Level, Name, Data, Size, BytesPerLine, GroupSize)
Convenience macro that injects _AxlLogDomain, func, LINE.
Requires AXL_LOG_DOMAIN() in the source file.
Functions
-
void axl_hexdump(const char *name, const void *data, size_t size, size_t bytes_per_line, size_t group_size)
Print a hex+ASCII dump to stdout via axl_print().
- Parameters:
name – label printed above the dump (may be NULL)
data – buffer to dump
size – number of bytes
bytes_per_line – columns (0 = default 16, max 64)
group_size – grouping width (AXL_HEX_GROUP_*)
-
void axl_hexdump_to_log(int level, const char *domain, const char *func, int line, const char *name, const void *data, size_t size, size_t bytes_per_line, size_t group_size)
Emit a hex+ASCII dump through axl_log_full().
- Parameters:
level – log level (AXL_LOG_ERROR..AXL_LOG_TRACE)
domain – log domain
func – func
line – LINE
name – label printed above the dump (may be NULL)
data – buffer to dump
size – number of bytes
bytes_per_line – columns (0 = default 16, max 64)
group_size – grouping width (AXL_HEX_GROUP_*)