Skip to main content

TECH VEDA

Linux kernel & Device drivers starts on 31st Oct 2026 enrollingCorporate on-site training - Submit proposal Pick your modulesSharpen your kernel skills: deep dives, drivers, Yocto, CVEs, careers — updated daily. Read the blog →
Edge AI

The accel Subsystem: What Linux Standardises About Your NPU, and What It Does Not

The accel subsystem gives every NPU a /dev/accel node and all of DRM's plumbing, but its six in-tree drivers share no ioctls. What that means for a product.

The accel Subsystem: What Linux Standardises About Your NPU, and What It Does Not

Linux has had a home for neural accelerators since 6.2, and the accel subsystem standardises less than its existence suggests. It gives every accelerator a device node under character major 261, a sysfs class, a debugfs directory and the whole of DRM’s file and buffer machinery. It does not give you an interface: the six in-tree drivers at 7.3 define 54 ioctls between them and share not one of them, so the only calls portable across every accelerator are the three that identify the device. For an embedded product that makes the kernel driver a thin, replaceable layer and the userspace runtime the thing you are actually committing to.

If you have brought up an NPU on an embedded board, the sequence is familiar: a vendor BSP kernel, an out-of-tree module, a closed runtime, and whatever kernel version came with them. Upstream now has somewhere for that driver to live. The accel subsystem under drivers/accel holds six drivers as of Linux 7.3, and understanding what it does and does not promise is the difference between planning around it correctly and assuming a portability that is not there. We looked at one of those drivers in detail in the mainline NPU driver on RK3588; this goes a level below that, at the device model all six share.

What the accel subsystem actually standardises

Three things, and they are all namespace. An accelerator gets a character device under its own major number, 261, defined as ACCEL_MAJOR with room for 256 minors. It gets a sysfs class called accel, whose devnode callback puts the node at accel/<name>, which is why you look in /dev/accel/ and not /dev/dri/. And it gets a debugfs directory under an accel root, named after the device’s unique string.

raghu@techveda.org:~$ ls -l /dev/accel/
crw-rw---- 1 root render 261, 0 Nov  3 09:12 accel0
raghu@techveda.org:~$ ls /sys/class/accel/
accel0

What it standardises inside that namespace is almost nothing. The accel core registers exactly one common debugfs file across every accelerator, called name, which prints the driver name and the device it is bound to. That is the complete list of behaviour shared by all accel devices beyond the node itself. There is no common submit call, no common buffer call, no common query.

Why an NPU is a DRM device

The subsystem’s own documentation is unusually direct about the reasoning, and it is worth reading as a design decision rather than an accident of history. Compute accelerators can be IP blocks inside GPUs or look much like them, so rather than write a new framework the authors made the accel core part of DRM and an accel device a new type of DRM device. The stated motivation is leverage: an established code base for buffer objects, file handling, scheduling and command submission, and a set of maintainers who have solved these problems before.

That sharing is literal. accel_core_init() is called from drm_core_init(), so the accel core is not a separate module at all; it is code inside DRM that runs when DRM loads. An accel driver’s file operations come from a macro, DRM_ACCEL_FOPS, which wires release, unlocked_ioctl, compat_ioctl, poll, read and mmap straight to drm_release, drm_ioctl, drm_poll, drm_read and drm_gem_mmap. The only call it replaces is open, which becomes accel_open(), and that function looks the minor up in a separate accel-only xarray and then hands straight off to drm_open_helper(). Once open, the file descriptor is a DRM file in every respect.

The second half of the rationale explains the separate major number, and it is the more interesting half. The documentation says plainly that the goal is to prevent the extensive userspace graphics stack from trying to use an accelerator as a GPU. A GPU driver exposes /dev/dri/card0 and /dev/dri/renderD128, and a long list of userspace components enumerate those paths and assume what they find. Putting accelerators on a different major number and a different path means none of that machinery discovers them by accident. The minor numbering is separate too: DRM allocates primary and render minors in ranges derived from the minor type, while accel minors come from their own allocator starting at zero, so accel0 has no numeric relationship to any card or renderD node.

The flag that forces an architectural decision

A driver joins the accel subsystem by setting one feature flag, DRIVER_COMPUTE_ACCEL, in its drm_driver. That flag is mutually exclusive with DRIVER_RENDER and DRIVER_MODESET, and the exclusion is enforced at device init with an explicit refusal rather than left as documentation:

if (drm_core_check_feature(dev, DRIVER_COMPUTE_ACCEL) &&
            (drm_core_check_feature(dev, DRIVER_RENDER) ||
            drm_core_check_feature(dev, DRIVER_MODESET))) {
        DRM_ERROR("DRM driver can't be both a compute acceleration and graphics driver\n");
        return -EINVAL;
}

Two consequences follow for anyone bringing up silicon. First, an accel device is allocated an accel minor and nothing else, so it has no /dev/dri node whatsoever; tooling that looks there will not find your NPU no matter how the driver is written. Second, if your block is both a GPU and an accelerator, or shares registers with one, upstream expects two drivers connected over the auxiliary bus rather than one driver exposing both kinds of node. That is a structural decision made for you at the first line of the driver, and discovering it late is expensive.

Six drivers, 54 ioctls, nothing shared

Here is where the promise of a subsystem runs out. At 7.3, drivers/accel contains AMD’s XDNA, Arm’s Ethos-U, Habana Labs, Intel’s NPU, Qualcomm’s Cloud AI 100 and Rockchip’s rocket. Counting the ioctls each one defines in its userspace header:

  • ivpu (Intel NPU) — 14, including BO_CREATE, SUBMIT, CMDQ_SUBMIT and four metric-streamer calls.
  • amdxdna (AMD XDNA) — 11, built around a hardware-context object: CREATE_HWCTX, EXEC_CMD, WAIT_CMD.
  • ethosu (Arm Ethos-U65 and U85) — 10, including CMDSTREAM_BO_CREATE, SUBMIT and four performance-monitor calls.
  • qaic (Qualcomm Cloud AI 100) — 9, organised around slicing buffers: ATTACH_SLICE_BO, EXECUTE_BO, PARTIAL_EXECUTE_BO.
  • habanalabs — 6: HL_INFO, HL_CB, HL_CS, HL_WAIT_CS, HL_MEMORY, HL_DEBUG.
  • rocket (Rockchip RK3588) — 4: CREATE_BO, SUBMIT, PREP_BO, FINI_BO.

That is 54 ioctls, and not one name is common to all six. Every one of these devices needs to allocate a buffer the accelerator can reach, and four spell it CREATE_BO with four different argument structures, one spells it BO_CREATE, and one folds it into a general HL_MEMORY call. Every one needs to submit work, and they call it SUBMIT, EXEC_CMD, EXECUTE_BO, CMDQ_SUBMIT and HL_CS. The operations are conceptually the same across the whole subsystem and the interface is different in all six cases.

This is not an oversight that will be tidied up later. The documentation states the position: these devices have runtime stacks tailored to their hardware, usually including a compiler for their own engines, and the common layer in userspace is expected to be the deep-learning framework, not the kernel interface. The subsystem was built to host drivers, not to abstract them.

What is actually portable across the accel subsystem

Because an accel file descriptor is a DRM file, the core DRM ioctls are dispatched for it, but a client of the accel subsystem is never a DRM master, so only the calls marked render-allowed are usable. Of the 77 core ioctl entries in the DRM table at 7.3, 19 carry that flag: seven general ones and the twelve synchronisation-object calls.

The seven are VERSION, GET_CAP, SET_CLIENT_NAME, GEM_CLOSE, GEM_CHANGE_HANDLE, PRIME_HANDLE_TO_FD and PRIME_FD_TO_HANDLE. The twelve syncobj calls only work if the driver advertises DRIVER_SYNCOBJ, and reading the six drivers’ feature flags shows exactly one does: AMD’s XDNA, which also sets the timeline variant. Of the rest, four set DRIVER_GEM and habanalabs sets neither, so it carries only DRIVER_COMPUTE_ACCEL.

Net of all that, the interface guaranteed on every accelerator in the tree is three calls: ask which driver this is, ask a capability, and name your client. Buffer-handle closing and dma-buf import and export work on five of the six. Nothing portable allocates device memory, and nothing portable submits work. If you are writing software meant to run on more than one accelerator, that is the honest size of the common ground.

Which kernels have which accel subsystem drivers

The framework and the drivers you care about arrived at very different times, and for a product the second date is the one that matters. Read from the drivers/accel makefile at each of these exact point releases:

  • 5.15.224 and 6.1.191 — no drivers/accel directory at all. The subsystem landed in 6.2.
  • 6.6.161 and 6.12.115 — the framework plus three drivers: habanalabs, ivpu and qaic. All three are data-centre or laptop parts. There is no driver for an embedded SoC NPU in either of these series.
  • 6.18.60 — five drivers, adding amdxdna and rocket. This is the first longterm series with an upstream NPU driver for an embedded SoC.
  • 7.3.1 — six, adding Arm’s Ethos-U65 and U85, the microNPU found in a large number of embedded parts.

So the two series most embedded products are shipping on today, 6.6 and 6.12, have the accel device model and nothing that drives the accelerator on your board. If your plan depends on an upstream NPU driver, it depends on moving to 6.18 or newer, and for an Arm microNPU on 7.3 or newer.

What enabling it costs on a headless image

The whole of drivers/accel/Kconfig sits inside an if DRM block, so the accel subsystem cannot be built without the DRM core, and the DRM core is not free. Enabling it selects the panel orientation quirks, HDMI, I2C, the dma-buf framework, SYNC_FILE, KCMP and the VIDEO subsystem. On a board with no display controller and no screen, you compile in a graphics core and a video subsystem to make a neural accelerator work.

Individual drivers add to that. The Ethos-U driver selects the DMA GEM helpers, the DRM scheduler and the generic allocator, and depends on Arm or Arm64. None of this is unreasonable given the decision to build on DRM, but it is a real line item on a size-constrained image, and it surprises teams late in a size-reduction exercise.

How to check this on your own board

Start with whether the framework and a driver are configured. Most vendor configs have neither:

raghu@techveda.org:~$ zcat /proc/config.gz | grep -E "CONFIG_DRM_ACCEL"
# CONFIG_DRM_ACCEL is not set

If a driver is loaded, the node and the class entry tell you the major number and the minor, and the one common debugfs file tells you which driver is behind it:

raghu@techveda.org:~$ sudo mount -t debugfs none /sys/kernel/debug
raghu@techveda.org:~$ ls /sys/kernel/debug/accel/
0000:00:0b.0
raghu@techveda.org:~$ sudo cat /sys/kernel/debug/accel/*/name
intel_vpu dev=0000:00:0b.0 unique=0000:00:0b.0

That name file is the single query that behaves the same way everywhere in the accel subsystem, so it is the right first command on unfamiliar hardware. After it you are in driver-specific territory, and the next thing to read is that driver’s header in include/uapi/drm/, the complete definition of what the kernel will let you do.

Two checks worth making before you commit to a plan. Confirm there is no /dev/dri node for the accelerator, because its absence is by design and not a bring-up failure. And read the driver’s driver_features line in its source, because whether it sets DRIVER_GEM and DRIVER_SYNCOBJ decides whether buffer sharing and fences are available to you at all.

What this means for product software

The useful conclusion is about where to put your effort. The kernel driver for an accelerator is small by design, and in the accel subsystem it is deliberately a submission path and nothing more. It is also, from a portability point of view, nearly irrelevant: moving from one accelerator to another means a different set of ioctls with different structures, so no amount of care in your own code makes that transition cheap at the kernel interface.

What does transfer is the layer above. If your application reaches the accelerator through a framework delegate rather than through ioctls, the kernel interface changing underneath is somebody else’s problem. If it talks to a vendor runtime directly, you have taken on the interface difference yourself, and the accel subsystem does nothing to soften it. Reading a driver’s userspace header to work out what you have committed to separates an accurate plan from an optimistic one, and it sits at the centre of TECH VEDA’s Linux kernel and embedded Linux training tracks.

What was checked, and what was not

Everything above was read from source in the released 7.3 tree and at the named point releases: the major number and minor limit in include/drm/drm_accel.h; the class, devnode, debugfs and open paths in drivers/accel/drm_accel.c; the feature-flag exclusion in drivers/gpu/drm/drm_drv.c; the render-allowed ioctl set in drivers/gpu/drm/drm_ioctl.c; every accel driver’s ioctl list in include/uapi/drm/ and its driver_features line; the Kconfig dependencies; and the driver roster in drivers/accel/Makefile at 5.15.224, 6.1.191, 6.6.161, 6.12.115, 6.18.60 and 7.3.1.

Not checked: no board was booted and no accelerator exercised, so the listings above are composed from source rather than captured from hardware. The ioctl counts are of the names each header defines, not of distinct argument structures. Which vendor BSPs enable the accel subsystem was not surveyed, and no judgement is offered on how well any of the six drivers performs.

Key takeaways

  • The accel subsystem standardises where an accelerator appears — major 261, /dev/accel/accel*, /sys/class/accel, a debugfs directory — and one common debugfs file. It does not standardise how you talk to the device.
  • An accel device is a DRM device with a different node. The accel core lives inside the DRM module, and the file descriptor is a DRM file once open.
  • DRIVER_COMPUTE_ACCEL is mutually exclusive with the render and modeset flags, enforced at init. A block that is both GPU and accelerator needs two drivers on the auxiliary bus, and an accel device never gets a /dev/dri node.
  • Six drivers, 54 ioctls, no shared names. Only three core calls work on every accelerator, and none of them allocates memory or submits work.
  • 6.6 and 6.12 carry the framework but no embedded SoC NPU driver. Rocket arrives in 6.18 and Arm’s Ethos-U in 7.3, so upstream NPU support is a 6.18-or-newer plan.
Was this worth your time?

Frequently asked questions

What is the accel subsystem in the Linux kernel?
It is the compute-accelerator framework under drivers/accel, present since Linux 6.2. It exposes accelerators on character major 261 as /dev/accel/accel*, with a matching sysfs class and debugfs directory, and it is implemented as part of the DRM subsystem rather than separately.

Why is my NPU a DRM device and not its own kind of device?
Because accelerators are often IP blocks inside GPUs or resemble them closely, so the kernel reuses DRM’s buffer, file and scheduling code instead of duplicating it. The separate major number exists to stop the userspace graphics stack from discovering an accelerator and treating it as a GPU.

Is there a common userspace API for accelerators on Linux?
No. The six in-tree drivers at 7.3 define 54 ioctls with no shared names, and only three core DRM calls work on every accel device. The kernel documentation expects the common layer to be the deep-learning framework in userspace, not the kernel interface.

Which kernel version do I need for an upstream NPU driver on an embedded SoC?
Linux 6.18 or newer. The 6.6 and 6.12 longterm series contain the accel framework but only the habanalabs, Intel NPU and Qualcomm Cloud AI 100 drivers. Rockchip’s rocket arrives in 6.18 and Arm’s Ethos-U65 and U85 driver in 7.3.

Further reading

RB
Raghu Bharadwaj

Founder, TECH VEDA — 20+ years teaching the Linux kernel, device drivers and embedded systems.

Follow on LinkedIn

Get new posts by email

Kernel, embedded Linux and AI-era engineering — a few sharp reads a month. No spam.

We email occasionally and never share your address.