xtc_accel(3)

---

xtc_accel(3)

park a fiber on a GPU/NPU completion; enumerate accelerators

XTC_ACCEL(3) Library Functions Manual XTC_ACCEL(3)

xtc_accel_probe, xtc_accel_wait_fence, xtc_accel_run_blocking — park a fiber on a GPU/NPU completion; enumerate accelerators

#include <xtc.h>
#include <xtc_accel.h>

int
xtc_accel_probe(xtc_accel_dev_t *out, int max, int *out_n);

int
xtc_accel_wait_fence(int fence_fd, int64_t timeout_ns);

int
xtc_accel_run_blocking(int (*fn)(void *), void *arg, int *out_result);

An accelerator -- a GPU or an NPU (neural / vision processing unit, also called a VPU) -- is, at the layer a concurrency runtime cares about, an asynchronous I/O device: the caller submits an opaque command buffer or compiled graph, the coprocessor runs it, and it signals completion through a fence. On Linux both device kinds are DRM devices (/dev/dri/renderD* for a render GPU, /dev/accel/accel* for an NPU) whose completions are the same kernel object -- a dma-fence exported as a pollable sync_file file descriptor, the identical readiness event the loop already multiplexes for sockets and pipes. A GPU fence fd and an NPU fence fd are indistinguishable to poll(2), so they are handled by one API here.

The xtc_accel facility does exactly one thing: it parks the calling fiber on such a completion and wakes it when the device is done. It is a tensor library, a device-memory allocator, or a vendor-SDK portability layer. libxtc links no GPU/NPU runtime (Level(Zero), CUDA, Vulkan, OpenVINO, ...): the consumer links whichever runtime drives the device, submits the work, and hands xtc_accel_probe the resulting fence fd. The abstraction stops at the fence; submission and the compute itself live above it.

() enumerates the accelerator devices present on the host into out[0 ..max], writing the total number found to *out_n (which may exceed max if the buffer was too small; only the first max are written). Each xtc_accel_dev_t reports a kind (XTC_ACCEL_KIND_GPU or XTC_ACCEL_KIND_NPU), a short name, the device node path, and the backing kernel driver name if cheaply known (for example ‘xe’ or ‘intel_vpu’). These are for device selection and observability by the consumer and its runtime; libxtc opens none of them. The device kind is a tag only -- it never changes how a fence is waited on. Probing opens and submits nothing.

() parks the calling fiber until the completion fence fence_fd signals or timeout_ns elapses (negative waits forever). The fence fd is a pollable sync_file the consumer's runtime produced when it submitted work (from a Level(Zero) event or fence, an exported DRM sync object, a VK_KHR_external_fence_fd Vulkan fence, a CUDA event bridged to an eventfd(2), and so on). While waiting the fiber holds no OS thread -- the wait rides the loop's normal readiness path via xtc_proc_wait_fd(3). libxtc does not own, duplicate, or close fence_fd; the caller retains ownership. It must be called from within a fiber.

The timeout is measured against a deadline, so spurious wakes (for example xtc_proc_wake(3)) never shorten it. Messages in the caller's mailbox neither end the wait nor are consumed by it. While a message is pending the fiber cannot park on the fd, so it checks the fence every 50 microseconds, backing off to every millisecond, sleeping in between; the loop keeps running other fibers, at the cost of up to one millisecond of extra completion latency until the caller drains its mailbox.

() runs the synchronous accelerator call fn(arg) on the xtc_blocking(3) thread pool, parking the calling fiber until it returns and storing fn's return value in *out_result (may be NULL). It is the fallback for runtimes that expose only a blocking submit-and-wait with no pollable fence. It holds one pool thread for the duration of fn, so it suits coarse-grained inference rather than thousands of tiny operations per second; prefer xtc_accel_wait_fence() when a fence fd is available.

xtc_accel_probe() returns XTC_OK on success, including when zero devices are found (absence is not an error), or XTC_E_INVAL on a bad argument.

xtc_accel_wait_fence() returns XTC_OK when the fence signals, XTC_E_AGAIN on timeout (the fence never fired), XTC_E_INVAL on a bad file descriptor, or XTC_E_NOSYS when the library was built without accelerator support.

xtc_accel_run_blocking() returns XTC_OK (fn ran; see *out_result), XTC_E_INVAL for a NULL fn, or an xtc_blocking(3) error if the work could not be dispatched. It works regardless of whether accelerator support was built in.

Accelerator support is selected at configure time with --with-accel=auto|yes|no (default auto: enabled where the Linux DRM/accel subsystem is present). When it is not built in, () reports zero devices and xtc_accel_wait_fence() returns XTC_E_NOSYS, following the same “always linkable, NOSYS when unsupported” convention as the TLS and cryptography modules; xtc_accel_run_blocking() is unaffected.

xtc_blocking(3), xtc_proc(3), xtc_io(3)

The xtc_accel facility appeared in libxtc 1.30.

July 23, 2026 Debian

View the mdoc source