Skip to main content

<alp/backend.h> — Backend Registration & Selection

The mechanism behind every alp_<class>_open(). Each peripheral subsystem (ADC, SPI, inference, …) carries a class name; backends register themselves into a per-class linker section, and the class dispatcher walks that section once, picks a winner by silicon reference + priority, caches the choice, and dispatches through an ops vtable thereafter.

Most applications never call this header directly — it is the plumbing under the portable surfaces. You reach for it when you are adding a backend (a vendor port, a test double) or when you need to ask at runtime whether a class has any backend linked in at all.

#include <alp/backend.h>

Selection rules

alp_backend_select() filters the class's section to rows whose silicon_ref either exactly matches the active SoC or is the "*" wildcard, then applies this tiebreaker in order:

  1. Higher priority wins.
  2. At equal priority, an exact silicon_ref match beats "*".
  3. At equal priority and the same match type, the lower vendor string (strcmp) wins.

Rule 3 exists so the choice is pinned deterministically regardless of linker object order.

:::caution A high-priority wildcard beats a low-priority exact match Priority is the first key, not the second. A "*" row at priority 100 outranks an exact "alif:ensemble:e7" row at priority 50. Exactness only breaks ties at equal priority. This is what lets a portable Zephyr backend (wildcard, priority 100) serve every SoM while a software fallback (wildcard, priority 0) sits underneath it. :::

The active SoC reference comes from ALP_SOC_REF_STR in <alp/soc_caps.h> — e.g. "alif:ensemble:e7".

Priority conventions

PriorityUsed by
255Reserved for alp/testing doubles. Gated behind CONFIG_ALP_SDK_TESTING (itself gated on CONFIG_ZTEST) so a production build can never silently pick one up. Do not register a non-test backend at 255.
200Proxies
100zephyr_drv — the portable Zephyr driver backends
0sw_fallback — portable software catch-alls

Real vendor backends slot between these — for example the GPU2D D/AVE 2D rows register at priority 100 against exact Alif silicon refs so they outrank the priority-0 wildcard software fallback on those parts.

Functions

CallReturns
alp_backend_select(const char *class_name, const char *silicon_ref)const alp_backend_t * — the best-ranked matching backend, or NULL when none match. Pass ALP_SOC_REF_STR as silicon_ref.
alp_backend_select_next(const char *class_name, const char *silicon_ref, const alp_backend_t *prev)const alp_backend_t * — the best-ranked backend ranking strictly below prev in the same order, or NULL when no lower-ranked candidate remains. prev == NULL behaves exactly like alp_backend_select().
alp_backend_count(const char *class_name)size_t — number of entries registered for the class, on any silicon (no silicon_ref filter).

Fall-through with alp_backend_select_next

select_next exists so a dispatcher can fall through to the next candidate when the selected backend declines an open() with ALP_ERR_NOSUPPORT — e.g. an algorithm the hardware path does not implement — instead of surfacing the decline as a hard application error. It is a dispatcher-side facility; whether a given class uses it is per-class. Not every dispatcher does: the TMU dispatcher, for instance, resolves once with alp_backend_select() and does not fall through, which is why a NOSUPPORT from its hardware path reaches the caller.

ALP_BACKEND_AVAILABLE

#define ALP_BACKEND_AVAILABLE(class) (alp_backend_count(#class) > 0u)

Resolves at runtime via alp_backend_count. Use it in if (...)not in preprocessor #if, since the count is a function call, not a constant expression.

For compile-time pruning, use ALP_HAS(<CAP>) instead — it queries the SoC-level capability table and is a constant expression. The two answer different questions: ALP_BACKEND_AVAILABLE asks "did anything get linked for this class", ALP_HAS asks "does this silicon have the block".

alp_backend_t

FieldTypeNotes
silicon_refconst char *Exact SoC reference ("alif:ensemble:e7") or "*" wildcard. A wildcard matches any non-NULL active silicon_ref.
vendorconst char *Vendor tag; also the deterministic strcmp tiebreaker at equal priority + match type.
base_capsuint32_tInstance-cap flags the backend advertises before probing (see alp_instance_cap_t).
priorityuint8_tSelection key #1. See the conventions table above.
opsconst void *The class's ops vtable.
probeint (*)(uint32_t instance_id, uint32_t *refined_caps)Optional. Refines base_caps at open time; may be NULL.

Registration macros

A backend registers itself with ALP_BACKEND_REGISTER, which expands to a static const struct in the alp_backends_<class> linker section. The linker collects every such entry into a contiguous array:

ALP_BACKEND_REGISTER(gpu2d,
sw_fallback,
{
.silicon_ref = "*",
.vendor = "sw",
.base_caps = 0u,
.priority = 0,
.ops = &_ops,
.probe = NULL,
});

The class dispatcher instantiates ALP_BACKEND_DEFINE_CLASS(<class>) once — typically in the file implementing alp_<class>_open() — which emits the class-range table entry telling the selector where the section starts and stops.

MacroRole
ALP_BACKEND_REGISTER(class, name, ...)One backend row. class becomes part of the section name and must be a valid C identifier; name must be unique within the class.
ALP_BACKEND_DEFINE_CLASS(class)Emits the per-class section-range descriptor. Once per class.
ALP_BACKEND_ANCHOR_DEFINE(class) / ALP_BACKEND_ANCHOR(class)Static-archive link anchors — see below.

Static-archive anchors

On a plain-CMake static libalp_sdk.a, the archive member carrying a class's registration (typically its sw_fallback translation unit) is nothing but data in a linker section — no code references it. A static-library link only pulls a member when an already-included object needs one of its symbols, so that member never joins the link, the section is absent, and the __start_/__stop_alp_backends_<class> bounds go undefined.

The anchor closes that gap: the section-carrying backend exports one global via ALP_BACKEND_ANCHOR_DEFINE(class), and the always-linked dispatcher takes its address via ALP_BACKEND_ANCHOR(class), forcing the member into the link.

Both macros are inert unless the top-level CMake defines ALP_BACKEND_STATIC_ANCHORS (the non-Zephyr build). Zephyr links whole objects, so they compile to nothing there — which is why they can sit unconditionally next to the ALP_BACKEND_REGISTER / ALP_BACKEND_DEFINE_CLASS they pair with.

Adding a new registry class that ships a portable catch-all backend reachable from the plain-CMake path: call ALP_BACKEND_ANCHOR_DEFINE once in that backend's TU and ALP_BACKEND_ANCHOR once in the dispatcher.

ABI status

[ABI-EXPERIMENTAL] — new in v0.7. Promoted to [ABI-STABLE] after three vendor families exercise the registry. Pin your SDK to a specific commit if you depend on this surface.

See also

Questions about this page? Discuss in Community Forum