AxlLoop – Event Loop

Event loop with timer, keyboard, idle, protocol notification, and raw event sources. GLib-inspired main loop with FUSE-style primitives.

Also includes the deferred work queue (AxlDefer) and the publish/subscribe event bus (AxlSignal), both integrated with the loop.

Headers:

  • <axl/axl-loop.h> – Event loop core

  • <axl/axl-defer.h> – Deferred work queue (ring buffer)

  • <axl/axl-signal.h> – Publish/subscribe event bus

Overview

UEFI applications are single-threaded and event-driven. The event loop is the central dispatcher: it waits for events (timers, keyboard input, network I/O, custom events) and calls registered callbacks.

Basic Pattern

#include <axl.h>

static bool on_timer(void *data) {
    axl_printf("tick\n");
    return AXL_SOURCE_CONTINUE;  // keep firing
}

static bool on_timeout(void *data) {
    axl_loop_quit(data);
    return AXL_SOURCE_REMOVE;    // one-shot, auto-removed
}

int main(int argc, char **argv) {
    AXL_AUTOPTR(AxlLoop) loop = axl_loop_new();

    axl_loop_add_timer(loop, 1000, on_timer, NULL);    // every 1s
    axl_loop_add_timeout(loop, 5000, on_timeout, loop); // quit after 5s

    axl_loop_run(loop);  // blocks until axl_loop_quit
    return 0;
}

Source Types

Source

Purpose

Callback returns

Timer

Repeating at fixed interval

CONTINUE or REMOVE

Timeout

One-shot after delay

Always REMOVE (auto)

Idle

Runs when no other events pending

CONTINUE or REMOVE

Keypress

Console keyboard input

CONTINUE or REMOVE

Raw Event

Any EFI_EVENT

CONTINUE or REMOVE

Protocol Notify

Fires when a protocol is installed

CONTINUE or REMOVE

Run vs. Next+Dispatch

axl_loop_run blocks until axl_loop_quit is called. For manual control (e.g., FUSE-style drivers), use the step API:

while (running) {
    axl_loop_next_event(loop, 100);   // wait up to 100ms
    axl_loop_dispatch_event(loop);     // fire callbacks
    // ... do other work between iterations ...
}

AxlDefer

Deferred work queue – schedules a function to run on the next loop iteration. Useful in constrained contexts (protocol notifications, nested callbacks) where complex work isn’t safe.

// Called from a protocol notification (can't do complex work here)
void on_protocol_installed(void *ctx) {
    axl_defer(initialize_new_protocol, ctx);
}

// Runs safely on the next main loop tick
void initialize_new_protocol(void *ctx) {
    // Boot Services calls are safe here
    locate_and_configure(ctx);
}

The queue is a fixed-capacity ring buffer with no dynamic allocation in the hot path. axl_defer_cancel can remove pending work before it fires.

AxlSignal

Publish/subscribe event bus for decoupling modules. Modules emit named signals; other modules subscribe with callbacks. Delivery is deferred (via AxlDefer) so handlers run in a safe context.

// Producer (network module)
axl_signal_emit("ip-changed", &new_ip);

// Consumer 1 (splash screen)
axl_signal_connect("ip-changed", update_splash_ip, NULL);

// Consumer 2 (REST API)
axl_signal_connect("ip-changed", update_api_endpoint, NULL);

// Adding a third consumer requires zero changes to the producer.

Signals are auto-created on first connect or emit. axl_signal_disconnect removes a specific subscription.

API Reference

AxlLoop

Defines

AXL_SOURCE_CONTINUE

Return from callback to keep the source active.

AXL_SOURCE_REMOVE

Return from callback to remove the source from the loop.

Typedefs

typedef struct AxlLoop AxlLoop
typedef void *AxlEvent

Opaque event handle for raw event sources.

typedef bool (*AxlLoopCallback)(void *data)

AxlLoopCallback:

Generic event callback. Return AXL_SOURCE_CONTINUE to keep the source active, or AXL_SOURCE_REMOVE to remove it. To quit the loop, call axl_loop_quit() from inside the callback.

typedef bool (*AxlKeyCallback)(AxlInputKey key, void *data)

AxlKeyCallback:

Key press callback. Return AXL_SOURCE_CONTINUE to keep the source active, or AXL_SOURCE_REMOVE to remove it. To quit the loop, call axl_loop_quit() from inside the callback.

Enums

enum AxlSourceType

AxlSourceType:

Identifies the kind of event source in the loop.

Values:

enumerator AXL_SOURCE_TIMER

repeating timer

enumerator AXL_SOURCE_TIMEOUT

one-shot timer (auto-removed after firing)

enumerator AXL_SOURCE_KEYPRESS

console keyboard input

enumerator AXL_SOURCE_IDLE

fires every iteration before blocking wait

enumerator AXL_SOURCE_PROTOCOL

UEFI protocol install notification.

enumerator AXL_SOURCE_EVENT

raw EFI event handle (caller-owned)

Functions

AxlLoop *axl_loop_new(void)

Create a new event loop.

Returns:

new AxlLoop, or NULL on failure.

void axl_loop_free(AxlLoop *loop)

Free an event loop and close all internal events.

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

void axl_loop_quit(AxlLoop *loop)

Signal the loop to quit. Safe to call from callbacks.

Parameters:
  • loop – loop to quit

bool axl_loop_is_running(AxlLoop *loop)

Check if the loop is running.

Parameters:
  • loop – loop to check

Returns:

true if running and not quit-requested.

void axl_loop_add_cleanup(AxlLoop *loop, AxlLoopCallback cb, void *data)

Add a cleanup callback fired on exit (FIFO order).

Parameters:
  • loop – loop

  • cb – callback fired on exit (FIFO order)

  • data – opaque data

int axl_loop_next_event(AxlLoop *loop, bool blocking)

Wait for (or check) the next event.

Parameters:
  • loop – event loop

  • blocking – true to block until event, false to return immediately

Returns:

0 if event pending (call axl_loop_dispatch_event), 1 if non-blocking and nothing ready, -1 if Ctrl-C detected (loop should exit).

void axl_loop_dispatch_event(AxlLoop *loop)

Dispatch the pending event from the last axl_loop_next_event call.

Parameters:
  • loop – event loop

int axl_loop_dispatch(AxlLoop *loop, bool blocking)

Single iteration: axl_loop_next_event + axl_loop_dispatch_event.

Parameters:
  • loop – event loop

  • blocking – true to block, false for non-blocking

Returns:

0 on event dispatched, 1 if not ready, -1 on Ctrl-C.

int axl_loop_run(AxlLoop *loop)

Run the event loop until quit. Fires cleanup callbacks on exit.

Parameters:
  • loop – event loop

Returns:

0 on normal exit, -1 on Ctrl-C.

size_t axl_loop_add_timer(AxlLoop *loop, size_t interval_ms, AxlLoopCallback cb, void *data)

Add a repeating timer.

Parameters:
  • loop – event loop

  • interval_ms – timer interval in milliseconds

  • cb – callback fired each interval

  • data – opaque data

Returns:

source ID for axl_loop_remove_source, or 0 on failure.

size_t axl_loop_add_timeout(AxlLoop *loop, size_t delay_ms, AxlLoopCallback cb, void *data)

Add a one-shot timeout (auto-removed after firing).

Parameters:
  • loop – event loop

  • delay_ms – timeout delay in milliseconds

  • cb – callback fired on timeout (one-shot, auto-removed)

  • data – opaque data

Returns:

source ID for axl_loop_remove_source, or 0 on failure.

size_t axl_loop_add_key_press(AxlLoop *loop, AxlKeyCallback cb, void *data)

Add a key press handler.

Parameters:
  • loop – event loop

  • cb – key press callback

  • data – opaque data

Returns:

source ID for axl_loop_remove_source, or 0 on failure.

size_t axl_loop_add_idle(AxlLoop *loop, AxlLoopCallback cb, void *data)

Add an idle callback (fired every iteration before wait).

Parameters:
  • loop – event loop

  • cb – idle callback (fired every iteration before wait)

  • data – opaque data

Returns:

source ID for axl_loop_remove_source, or 0 on failure.

size_t axl_loop_add_protocol_notify(AxlLoop *loop, void *guid, AxlLoopCallback cb, void *data)

Add a protocol install notification.

Parameters:
  • loop – event loop

  • guid – protocol GUID to watch (void* to avoid EFI_GUID in header)

  • cb – callback on protocol install

  • data – opaque data

Returns:

source ID for axl_loop_remove_source, or 0 on failure.

size_t axl_loop_add_event(AxlLoop *loop, AxlEvent event, AxlLoopCallback cb, void *data)

Add a raw event handle to the loop.

Fires cb when the event is signaled. The caller owns the event — the loop does NOT close it on removal. Use this to integrate TCP completion tokens, custom protocol events, or any EFI_EVENT into the main loop without polling.

Parameters:
  • loop – event loop

  • event – event handle from axl_event_create

  • cb – callback when event is signaled

  • data – opaque data

Returns:

source ID for axl_loop_remove_source, or 0 on failure.

void axl_loop_remove_source(AxlLoop *loop, size_t source_id)

Remove an event source by ID.

Parameters:
  • loop – event loop

  • source_id – ID returned by axl_loop_add_*

int axl_event_create(AxlEvent *event)

Create a raw event handle.

Use with axl_loop_add_event to integrate custom events into the loop. Caller owns the event — free with axl_event_close.

Parameters:
  • event – [out] receives event handle

Returns:

0 on success, -1 on error.

void axl_event_signal(AxlEvent event)

Signal a raw event handle.

Parameters:
  • event – event to signal

void axl_event_close(AxlEvent event)

Close a raw event handle. NULL-safe.

Parameters:
  • event – event to close

struct AxlInputKey
#include <axl-loop.h>

AxlInputKey:

Keyboard input. Mirrors UEFI EFI_INPUT_KEY layout.

Public Members

uint16_t scan_code

function/arrow key scan code (0 for printable chars)

uint16_t unicode_char

printable character (0 for special keys)

AxlDefer

Typedefs

typedef void (*AxlDeferCallback)(void *data)

AxlDeferCallback:

Deferred work function. Runs on the BSP main loop thread.

Functions

void axl_defer_init(void)

Initialize (or reset) the deferred work queue.

Clears all pending work and resets internal state. Call once before using axl_defer(). The loop drains the queue automatically at the start of each iteration. Safe to call again to reset between test runs or on reinit.

size_t axl_defer(AxlDeferCallback fn, void *data)

Schedule deferred work for the next loop tick.

Safe to call from protocol notifications, nested callbacks, or any context where complex work should not run immediately.

Parameters:
  • fn – work function

  • data – opaque data passed to fn

Returns:

handle for axl_defer_cancel(), or 0 if the queue is full.

bool axl_defer_cancel(size_t handle)

Cancel pending deferred work before it fires.

No-op if the handle is invalid or already fired.

Parameters:
  • handle – handle from axl_defer()

Returns:

true if the work was cancelled, false if already fired or invalid.

void axl_defer_drain(void)

Drain all pending deferred work.

Called automatically by axl_loop_next_event(). Manual loop users (FUSE-style next_event/dispatch_event) get this for free. Only call explicitly if bypassing the loop entirely.

AxlSignal

Typedefs

typedef void (*AxlSignalCallback)(void *event_data, void *user_data)

AxlSignalCallback:

Signal handler. Runs on the BSP main loop thread (via AxlDefer).

Functions

bool axl_signal_new(const char *name)

Explicitly register a named signal.

Optional — signals are auto-created on first connect or emit. Use this to reserve a signal slot early or to validate the name.

Parameters:
  • name – signal name (pointer stored, not copied)

Returns:

true if registered (or already exists), false if table full.

void axl_signal_reset(void)

Reset the signal system — free all subscribers and signals.

Call on shutdown or between test runs to release resources.

size_t axl_signal_connect(const char *name, AxlSignalCallback cb, void *data)

Subscribe to a named signal.

The callback fires (via AxlDefer) each time the signal is emitted. Auto-creates the signal if it doesn’t exist yet.

Parameters:
  • name – signal name

  • cb – callback (fires on emit, via AxlDefer)

  • data – opaque data passed to cb

Returns:

handle for axl_signal_disconnect, or 0 on failure.

bool axl_signal_disconnect(size_t handle)

Unsubscribe from a signal.

Parameters:
  • handle – handle from axl_signal_connect

Returns:

true if disconnected, false if handle invalid or already removed.

bool axl_signal_emit(const char *name, void *event_data)

Emit a named signal.

Schedules all subscribers’ callbacks via axl_defer(). Safe to call from constrained contexts (protocol notifications, nested callbacks).

The caller must ensure event_data remains valid until the next loop tick (when deferred callbacks fire).

Parameters:
  • name – signal name

  • event_data – data passed to all subscribers (may be NULL)

Returns:

true if signal exists and had subscribers, false otherwise.