<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.
Header
#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:
- Higher
prioritywins. - At equal priority, an exact
silicon_refmatch beats"*". - At equal priority and the same match type, the lower
vendorstring (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
| Priority | Used by |
|---|---|
255 | Reserved 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. |
200 | Proxies |
100 | zephyr_drv — the portable Zephyr driver backends |
0 | sw_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
| Call | Returns |
|---|---|
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
| Field | Type | Notes |
|---|---|---|
silicon_ref | const char * | Exact SoC reference ("alif:ensemble:e7") or "*" wildcard. A wildcard matches any non-NULL active silicon_ref. |
vendor | const char * | Vendor tag; also the deterministic strcmp tiebreaker at equal priority + match type. |
base_caps | uint32_t | Instance-cap flags the backend advertises before probing (see alp_instance_cap_t). |
priority | uint8_t | Selection key #1. See the conventions table above. |
ops | const void * | The class's ops vtable. |
probe | int (*)(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.
| Macro | Role |
|---|---|
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
<alp/cap.h>— the compile-timeALP_HAS()counterpart andALP_SOC_REF_STR- Architecture: OS backend selection
<alp/gpu2d.h>and<alp/tmu.h>— two classes whose backend split is documented end-to-end