Event Primitives — AxlEvent, AxlCancellable, AxlWait

The foundational synchronization primitives. All three compose: an AxlCancellable is an AxlEvent with stop-token semantics; the wait helpers drive a throwaway AxlLoop until an event fires, a condition holds, a timeout elapses, or Ctrl-C is received.

  • AxlEvent — one-shot latch wrapping a UEFI event. Replaces the older AxlCompletion (same mechanical behavior, UEFI-native name).

  • AxlCancellable — typed stop token shared across async ops; cancel it once and every op observing it aborts with AXL_CANCELLED.

  • AxlWait — interruptible wait helpers (axl_wait_for, axl_wait_for_flag, axl_wait_ms, …) built on AxlLoop.

A note on naming: “event” appears three times in AXL docs — the event loop (the dispatcher), an event source (a thing registered with the loop), and AxlEvent (one kind of source). UEFI carries the same overload; an AxlEvent is a one-shot latch backed by a UEFI event, and the event loop dispatches them.

Headers: <axl/axl-event.h>, <axl/axl-cancellable.h>, <axl/axl-wait.h>.

API Reference

AxlEvent

Defines

axl_event_new()

Captures the caller’s file/line for leak reporting via the tier-1 resource registry. See docs/AXL-Lifecycle.md §4.2.1.

Typedefs

typedef struct AxlEvent AxlEvent

axl-event.h:

Foundational one-shot latch. Wraps a UEFI event with signalled / reset state. The building block for producer-waiter rendezvous across the library. AxlCancellable is a typed contract on top: same mechanical behavior, stop-token semantics.

Typical use: an async callback signals the event; the main thread waits for it. Internally wraps a UEFI event — waits are driven by AxlLoop, so they idle the CPU (not busy-wait) and are interrupted by Ctrl-C.

Ordering: AXL targets UEFI BSP, which is single-threaded and cooperative. Event ops are safe from any context that single thread naturally reaches (protocol notifications, nested callbacks, interrupt-like handlers). No cross-core ordering guarantee between signal and is_set on platforms with real parallelism — use an explicit AP-to-BSP channel (AxlAsync) if you need one.

AxlEvent *e = axl_event_new();
start_async_op(on_done, e);
if (axl_event_wait_timeout(e, NULL, 5000000) != 0) {
    // -1 timeout, AXL_CANCELLED on Ctrl-C / cancel
}
axl_event_free(e);

static void on_done(void *user) { axl_event_signal(user); }

Relationship to the event loop: the “event loop” (AxlLoop) dispatches events — timer expirations, key presses, raw UEFI event signals. An AxlEvent is one such event (a one-shot latch). The name is overloaded but consistent: AXL is a thin layer over UEFI events, and the loop is the dispatcher.

typedef struct AxlCancellable AxlCancellable
typedef void *AxlEventHandle

AxlEventHandle:

Raw UEFI event handle (EFI_EVENT). Used where firmware owns the event — protocol completion tokens, protocol-notify events. For AXL-managed events use AxlEvent and pass axl_event_handle(e) where a handle is required (e.g., axl_loop_add_event).

Functions

AxlEvent *axl_event_new_impl(const char *file, int line)

Create a new, unsignalled event.

Returns:

new AxlEvent, or NULL on failure. Free with axl_event_free().

void axl_event_free(AxlEvent *e)

Free an event. NULL-safe.

Parameters:
  • e – event (NULL-safe)

void axl_event_signal(AxlEvent *e)

Signal the event. Idempotent, NULL-safe.

Safe from any context — protocol notifications, nested callbacks, interrupt-like handlers.

Parameters:
  • e – event (NULL-safe)

void axl_event_reset(AxlEvent *e)

Reset the event to an unsignalled state. NULL-safe.

Drops any pending signal so the same event can be reused across multiple wait cycles.

Parameters:
  • e – event (NULL-safe)

bool axl_event_is_set(const AxlEvent *e)

Fast check: is this event currently signalled?

Reads an internal flag without driving the loop. Transitions:

  • axl_event_signal(e) → is_set becomes true

  • axl_event_reset(e) → is_set becomes false

  • successful axl_event_wait[_timeout] → is_set becomes false (the wait consumed the signal via CheckEvent in the loop dispatch; the flag mirrors the backend state)

For the full wait-for-signal behavior with timeout and cancel support, use axl_event_wait_timeout().

Parameters:
  • e – event (NULL-safe)

Returns:

true between signal and the next reset / successful wait, else false. Returns false for NULL.

AxlEventHandle axl_event_handle(const AxlEvent *e)

Get the raw UEFI event handle wrapped by this AxlEvent.

Used when registering the event with the loop via axl_loop_add_event, which takes a handle so the same entry point serves AXL-managed events and firmware-owned ones alike.

Parameters:
  • e – event (NULL-safe, returns NULL)

Returns:

the wrapped handle, or NULL for NULL / uninitialized.

AxlStatus axl_event_wait(AxlEvent *e, AxlCancellable *cancel)

Wait indefinitely for the event to be signalled.

The CPU idles between events. Returns early on Ctrl-C or a signalled cancellable. Equivalent to axl_event_wait_timeout(e, cancel, 0).

Parameters:
  • e – event

  • cancel – optional cancel token (NULL = only Ctrl-C)

Returns:

AXL_OK on signal, AXL_ERR on invalid arg, AXL_CANCELLED on Ctrl-C or cancel.

AxlStatus axl_event_wait_timeout(AxlEvent *e, AxlCancellable *cancel, uint64_t timeout_us)

Wait for the event with a timeout.

The CPU idles between events. A timeout_us of 0 means wait forever. Returns early on Ctrl-C or a signalled cancellable.

Parameters:
  • e – event

  • cancel – optional cancel token

  • timeout_us – timeout in microseconds (0 = forever)

Returns:

AXL_OK on signal, AXL_TIMEOUT on deadline, AXL_ERR on invalid arg, AXL_CANCELLED on Ctrl-C or cancel.

AxlCancellable

Defines

axl_cancellable_new()

Captures the caller’s file/line for leak reporting via the tier-1 resource registry. See docs/AXL-Lifecycle.md §4.2.1.

Typedefs

typedef struct AxlCancellable AxlCancellable

axl-cancellable.h:

Generic cancellation primitive for async operations. Parallels GLib’s GCancellable, mapped onto AXL’s single-threaded event loop.

A cancellable is an optional “stop token” that any async operation can accept. The caller holds it; the op observes it. Signaling axl_cancellable_cancel aborts every op currently referencing it.

Typical use:

AxlCancellable *cancel = axl_cancellable_new();

axl_tcp_connect_async(host, port, loop, cancel, on_connected, ctx);
axl_loop_add_timeout(loop, 5000, cancel_on_timeout, cancel);
axl_loop_run(loop);

axl_cancellable_free(cancel);

static bool cancel_on_timeout(void *data) {
    axl_cancellable_cancel(data);
    return AXL_SOURCE_REMOVE;
}

// The op's callback fires exactly once, with status either AXL_OK
// on success, or AXL_CANCELLED if the cancellable fired first.
static void on_connected(AxlTcp *sock, AxlStatus status, void *ctx) {
    if (status == AXL_CANCELLED) { axl_tcp_close(sock); return; }
    // sock is ready to use
}

Group cancellation — one cancellable covers many ops:

axl_tcp_connect_async(h, p, loop, app->shutdown, cb1, c1);
axl_http_get_async   (u,   loop, app->shutdown, cb2, c2);

// Cancels both ops on app shutdown — each callback fires with
// AXL_CANCELLED.
axl_cancellable_cancel(app->shutdown);

Ownership rule: the cancellable must outlive every async op that observes it. Same discipline as AxlLoop outliving its sources.

Functions

AxlCancellable *axl_cancellable_new_impl(const char *file, int line)

Create a new, unsignalled cancellable.

Returns:

new AxlCancellable, or NULL on failure. Free with axl_cancellable_free().

void axl_cancellable_free(AxlCancellable *c)

Free a cancellable. NULL-safe.

Must only be called after every async op that observes this cancellable has completed (via its callback) or is otherwise no longer holding a reference. Freeing while an op still references it results in a dangling event handle.

Parameters:
  • c – cancellable (NULL-safe)

void axl_cancellable_cancel(AxlCancellable *c)

Cancel every async op currently observing this cancellable.

Idempotent — calling more than once is safe and has no additional effect. Safe to call from any context (protocol notifications, nested callbacks). NULL-safe.

Parameters:
  • c – cancellable (NULL-safe)

bool axl_cancellable_is_cancelled(const AxlCancellable *c)

Check whether the cancellable has been signalled.

Parameters:
  • c – cancellable (NULL-safe)

Returns:

true if axl_cancellable_cancel() was called, else false. Returns false for NULL.

void axl_cancellable_reset(AxlCancellable *c)

Reset the cancellable to an unsignalled state. NULL-safe.

Drops any pending cancel signal so the same cancellable can be reused for a fresh batch of async ops. Only call once all ops that might have observed the prior signal have completed.

Parameters:
  • c – cancellable (NULL-safe)

AxlWait

Typedefs

typedef struct AxlCancellable AxlCancellable

axl-wait.h:

Interruptible wait helpers built on AxlLoop.

These replace the common “busy-poll with axl_backend_stall” idiom with event-driven waits that idle the CPU between checks and return early on Ctrl-C. Every function returns AxlStatus:

AXL_OK         — condition met / elapsed
AXL_TIMEOUT    — deadline elapsed before condition
AXL_ERR        — invalid arg / internal failure
AXL_CANCELLED  — interrupted (Ctrl-C / shell break / cancel token)
// Wait for a hardware status flag, CPU idle between checks:
if (axl_wait_for_word(&mmio->status, 0, NULL, 500000) != AXL_OK) {
    return AXL_ERR;
}

// Interruptible sleep:
(void)axl_wait_ms(NULL, 100);

typedef bool (*AxlCondFn)(void *ctx)

AxlCondFn:

Condition predicate. Returns true when the wait should end.

typedef void (*AxlTickFn)(void *ctx)

AxlTickFn:

Periodic side-effect called between waits. Typical use is to drive a UEFI protocol state machine forward (e.g. call protocol->Poll) so the condition can become true.

Functions

void axl_sleep(uint64_t seconds)

Sleep for the specified number of seconds. CPU idles; Ctrl-C returns early.

void axl_msleep(uint64_t milliseconds)

Sleep for the specified number of milliseconds. CPU idles; Ctrl-C returns early.

void axl_usleep(uint64_t microseconds)

Sleep for the specified number of microseconds (rounded up to ms granularity). CPU idles; Ctrl-C returns early.

AxlStatus axl_wait_for_flag(volatile const bool *flag, AxlCancellable *cancel, uint64_t timeout_us)

Wait until *flag becomes true, with optional cancel + timeout.

CPU idles between 1ms checks. Returns 0 immediately if *flag is already true, or AXL_CANCELLED if cancel was already signalled.

Parameters:
  • flag – flag to observe

  • cancel – optional cancel token (NULL = only Ctrl-C)

  • timeout_us – timeout in microseconds (0 = forever)

Returns:

AXL_OK on true, AXL_TIMEOUT on deadline, AXL_ERR on invalid arg, AXL_CANCELLED on Ctrl-C or an observed cancellable.

AxlStatus axl_wait_for_word(volatile const uint64_t *word, uint64_t not_ready_value, AxlCancellable *cancel, uint64_t timeout_us)

Wait until *word stops matching not_ready_value.

Covers UEFI completion-token Status polls, DMA flags, and any “keep checking this memory word until it changes” pattern. CPU idles between 1ms checks.

Parameters:
  • word – memory word to observe

  • not_ready_value – value that means “keep waiting”

  • cancel – optional cancel token

  • timeout_us – timeout in microseconds (0 = forever)

Returns:

AXL_OK on change, AXL_TIMEOUT on deadline, AXL_ERR on invalid arg, AXL_CANCELLED on Ctrl-C or an observed cancellable.

AxlStatus axl_wait_ms(AxlCancellable *cancel, uint64_t ms)

Interruptible sleep with cancellable support.

The long form of axl_msleep — use this when you need to inspect the return code (Ctrl-C vs elapsed) or pass a shared AxlCancellable. The CPU idles for the duration.

Parameter order note: the rest of the wait family places cancel between the subject and the timeout. Sleep has no subject, so cancel comes first. The relative position (cancel immediately before the duration/timeout) is consistent with the other helpers.

Parameters:
  • cancel – optional cancel token (NULL = only Ctrl-C)

  • ms – milliseconds to sleep (0 returns immediately)

Returns:

AXL_OK on elapsed, AXL_CANCELLED on Ctrl-C or cancel.

AxlStatus axl_wait_for(AxlCondFn cond_fn, void *cond_ctx, AxlCancellable *cancel, uint64_t timeout_us)

Wait until cond_fn returns true, with timeout + optional cancel.

cond_fn is evaluated immediately and then every 1ms. CPU idles between evaluations.

Parameters:
  • cond_fn – predicate

  • cond_ctx – opaque context passed to cond_fn

  • cancel – optional cancel token

  • timeout_us – timeout in microseconds (0 = forever)

Returns:

AXL_OK on cond_fn true, AXL_TIMEOUT on deadline, AXL_ERR on invalid arg, AXL_CANCELLED on Ctrl-C or cancel.

AxlStatus axl_wait_for_with_tick(AxlCondFn cond_fn, void *cond_ctx, AxlTickFn tick_fn, void *tick_ctx, uint64_t tick_us, AxlCancellable *cancel, uint64_t timeout_us)

Wait until cond_fn returns true, running tick_fn each period.

cond_fn is evaluated immediately and then each time tick_fn runs. Use this form when the condition only becomes true after an external state machine is advanced (e.g. calling protocol->Poll on a UEFI driver).

Parameters:
  • cond_fn – predicate (required)

  • cond_ctx – opaque context for cond_fn

  • tick_fn – periodic side-effect (may be NULL)

  • tick_ctx – opaque context for tick_fn

  • tick_us – tick period in microseconds (minimum 1ms)

  • cancel – optional cancel token

  • timeout_us – timeout in microseconds (0 = forever)

Returns:

AXL_OK on cond_fn true, AXL_TIMEOUT on deadline, AXL_ERR on invalid arg, AXL_CANCELLED on Ctrl-C or cancel.