Linux DRM - Sharing Buffers with PRIME and dma-buf
How Linux DRM shares buffers between processes and devices with PRIME and dma-buf: handle to fd export, fd to handle import, the dma_buf_ops table, and a dumb buffer plus PRIME example.
October 30, 2026
Linux DRM - Sharing Buffers with PRIME and dma-buf
Reqruies
- Kernel: Based on Linux v6.19 source
- Library: libdrm 2.4.129
- Prerequisites: Linux DRM Part 4 GEM
Introduction
| Part | Topic |
|---|---|
| Part 1 | Architecture and the Big Picture |
| Part 2 | Core Initialization and Driver Registration |
| Part 3 | open() and ioctl() Code Flow |
| Part 4 | GEM: handle, dumb buffer, mmap |
The GEM handles we saw in Part 4 are only valid within a
drm_file. For a display server (compositor) to receive a buffer rendered by an application, or for a camera or video decoder to read a buffer created by a GPU, a sharing mechanism passable via an fd is required. This article walks through that mechanism, dma-buf, and PRIME, the DRM feature that bridges GEM and dma-buf.PRIME: Sharing Buffers Between Processes and Devices
GEM handles are meaningful only within a
drm_file and cannot be passed directly to other processes. For a display server (compositor) to receive buffers drawn by applications, or for a camera/video decoder to read buffers produced by the GPU, a sharing mechanism that can be passed as an fd is necessary. Linux's answer to this is dma-buf, and the DRM feature bridging GEM and dma-buf is called PRIME.dma-buf: exporter · importer · attachmentMermaidflowchart TB subgraph EXP["Exporter (e.g., GPU Driver A)"] OBJ["drm_gem_object"] --- DB["struct dma_buf<br/>ops = drm_gem_prime_dmabuf_ops"] end DB --> FD["dma-buf fd<br/>Can be passed to another process via SCM_RIGHTS"] FD -->|"dma_buf_attach<br/>dma_buf_map_attachment"| ATT subgraph IMP["Importer (different driver or same driver)"] ATT["dma_buf_attachment"] --> SGT["sg_table<br/>(list of physical pages)"] --> OBJ2["drm_gem_object (import)"] end
- The exporter wraps its buffer into a
struct dma_bufand exposes it as anfd. - The importer calls
dma_buf_attach()with that fd, thendma_buf_map_attachment()to obtain an sg_table (list of physical pages) and creates its own buffer object. - Data is not copied. Both sides view the same physical pages.
export: handle → dma-buf fd
The user space calls
DRM_IOCTL_PRIME_HANDLE_TO_FD. Because this ioctl sets DRM_RENDER_ALLOW in its flags, it can also be called on render nodes (see the ioctl table in Part 3).drivers/gpu/drm/drm_prime.c - drm_prime_handle_to_fd_ioctl() (L533-549)c
Source drivers/gpu/drm/drm_prime.c L533-L549 (Linux v6.19)
drivers/gpu/drm/drm_prime.c - drm_gem_prime_handle_to_fd() (L510-530)c
Source drivers/gpu/drm/drm_prime.c L510-L530 (Linux v6.19)
It reserves an
fd, acquires the dma-buf, and associates it with that fd using fd_install(). The key function responsible for acquiring the dma-buf is drm_gem_prime_handle_to_dmabuf().drivers/gpu/drm/drm_prime.c - drm_gem_prime_handle_to_dmabuf() (L431-494)c
Source drivers/gpu/drm/drm_prime.c L431-L494 (Linux v6.19)
The branching logic inside this function forms the core of this section:
drm_gem_object_lookup(): Looks up the object by handle.- If already exported in this file (
drm_prime_lookup_buf_by_handle), it returns that dma-buf as-is. - If it is an imported object, it returns the original dma-buf (
obj->import_attach->dmabuf). - If
obj->dma_bufalready exists, it is reused. - A new one is created only when none of the above exist (
export_and_register_object).
Thus, exporting the same handle multiple times still yields the exact same dma-buf. This explains why
export #1 and export #2 point to the identical inode in the example below.drivers/gpu/drm/drm_prime.c - export_and_register_object() (L375-407)c
Source drivers/gpu/drm/drm_prime.c L375-L407 (Linux v6.19)
If
obj->funcs->export exists, the backend implementation is used; otherwise, it falls back to the core default drm_gem_prime_export().drivers/gpu/drm/drm_prime.c - drm_gem_prime_export() (L916-931)c
Source drivers/gpu/drm/drm_prime.c L916-L931 (Linux v6.19)
dma_buf_export_info holds ops (the dma-buf operation table) and priv = obj (the GEM object). This serves as the link connecting dma-buf back to the GEM object. The operation table is defined as follows:drivers/gpu/drm/drm_prime.c - drm_gem_prime_dmabuf_ops (L827-836)c
Source drivers/gpu/drm/drm_prime.c L827-L836 (Linux v6.19)
map_dma_buf is invoked when the importer calls dma_buf_map_attachment(). It retrieves the page list via the GEM object's funcs->get_sg_table() and performs DMA mapping for the importer device.drivers/gpu/drm/drm_prime.c - drm_gem_map_dma_buf() (L666-692)c
Source drivers/gpu/drm/drm_prime.c L666-L692 (Linux v6.19)
drm_gem_prime_export() calls drm_gem_dmabuf_export(), which calls dma_buf_export() from the dma-buf core and increments reference counts on both the device and the GEM object (ensuring the device and object are not freed while the dma-buf remains alive).drivers/gpu/drm/drm_prime.c - drm_gem_dmabuf_export() (L239-254)c
Source drivers/gpu/drm/drm_prime.c L239-L254 (Linux v6.19)
The dma-buf core implementation is as follows:
drivers/dma-buf/dma-buf.c - dma_buf_export() (L683-759)c
Source drivers/dma-buf/dma-buf.c L683-L759 (Linux v6.19)
drivers/dma-buf/dma-buf.c - dma_buf_getfile() (L602-632)c
Source drivers/dma-buf/dma-buf.c L602-L632 (Linux v6.19)
Here, the true nature of dma-buf is revealed: dma-buf = a pseudo file backed by an anonymous inode + operation table + private data. As a result, it can be handled via an fd, transmitted to another process via
SCM_RIGHTS, and its reference count drops when calling close(). Because the file name is designated as "dmabuf", it shows up in /proc/<pid>/fd/ as follows:drivers/dma-buf/dma-buf.c - dmabuffs_dname() (L126-140)c
Source drivers/dma-buf/dma-buf.c L126-L140 (Linux v6.19)
Before being named,
/proc/self/fd/N for a dma-buf resolves to /dmabuf:.import: dma-buf fd → handle
The receiving end calls
DRM_IOCTL_PRIME_FD_TO_HANDLE.drivers/gpu/drm/drm_prime.c - drm_gem_prime_fd_to_handle() (L292-359)c
Source drivers/gpu/drm/drm_prime.c L292-L359 (Linux v6.19)
It acquires the dma-buf from the fd via
dma_buf_get(fd). If it is an already-known dma-buf, the existing handle is returned. If it is encountered for the first time, a GEM object is created via drm_gem_prime_import(), and it is registered into this file's handle table with drm_gem_handle_create_tail(). During object creation, two distinct cases exist:drivers/gpu/drm/drm_prime.c - drm_gem_prime_import_dev() (L968-1020)c
Source drivers/gpu/drm/drm_prime.c L968-L1020 (Linux v6.19)
- If the dma-buf was exported by the same DRM device (
drm_gem_is_prime_exported_dma_buf), no new object is created; it simply increments the reference count on the original object (dma_buf->priv). Sharing within the same device incurs virtually zero overhead. - If it was exported by a different device, an sg_table is obtained via
dma_buf_attach()→dma_buf_map_attachment_unlocked(), and the driver callback gem_prime_import_sg_table() instantiates its own GEM object backed by those pages. Becauseobj->import_attachis populated, the object is subsequently treated as an "imported object" (e.g.,drm_gem_dumb_map_offset()shown earlier returns-EINVALfor imported objects).
Hands-on: Dumb Buffers and PRIME
The entire workflow above is demonstrated in a single program. A dumb buffer is created, rendered into, exported via PRIME, and imported by another
open() call (a different drm_file) to read the exact same pixels.drm_dumb_prime.c - dumb buffer and PRIMEc
Here is how each line maps to the kernel code examined earlier:
| User Code | What happens in the kernel |
|---|---|
DRM_IOCTL_MODE_CREATE_DUMB | drm_mode_create_dumb() → dumb_create → drm_gem_handle_create(). The first handle of a new drm_file is typically 1 (the table starts from 1) |
DRM_IOCTL_MODE_MAP_DUMB | drm_gem_dumb_map_offset() → vma_node address becomes the offset |
mmap(..., fd, offset) | drm_gem_mmap() → drm_gem_mmap_obj() → connects pages upon page fault |
drmPrimeHandleToFD ×2 | On the second call, drm_gem_prime_handle_to_dmabuf() returns the same cached dma-buf (hence the same inode) |
readlink("/proc/self/fd/N") | Name of dma-buf file → dmabuffs_dname() → /dmabuf: |
drmPrimeFDToHandle(fd2, ...) | Takes the same-device branch of drm_gem_prime_import_dev() → new handle is created in fd2's table |
This example has been verified to compile againstlibdrm 2.4.129. Because the environment where this post was written lacked/dev/driaccess permissions, runtime output is not included. Running it yourself will demonstrate that bothexportoperations point to the same inode, and the importer reads identical pixel values. (Note: drivers such as i915 use their own GEM implementation rather than the shmem helper, but the core parts covered in this article—the handle table and PRIME branching—proceed in the exact same way.)
Code Locations
| Step | Function | Location (Linux v6.19) |
|---|---|---|
| PRIME: handle → dma-buf | drm_gem_prime_handle_to_dmabuf() | drivers/gpu/drm/drm_prime.c |
| PRIME: create new dma-buf | export_and_register_object() | drivers/gpu/drm/drm_prime.c |
| PRIME: dma-buf operation table | drm_gem_prime_dmabuf_ops | drivers/gpu/drm/drm_prime.c |
| PRIME: export dma-buf (core) | dma_buf_export() | drivers/dma-buf/dma-buf.c |
| PRIME: fd → handle | drm_gem_prime_fd_to_handle() | drivers/gpu/drm/drm_prime.c |
| PRIME: import object | drm_gem_prime_import_dev() | drivers/gpu/drm/drm_prime.c |
Summary
Key takeaways from this post:
- dma-buf = a pseudo file backed by an anonymous inode + operation table + private data. Being an fd, it can be passed to other processes via
SCM_RIGHTS. - export (
drm_gem_prime_handle_to_dmabuf) searches cache first, creating a new instance only when none exists. Exporting the same handle multiple times yields the exact same dma-buf. - import (
drm_gem_prime_import_dev) reuses the original object directly if exported by the same device; if from a different device, it invokesattach→map_attachmentto obtain an sg_table and creates an object via the driver callback. Data is never copied.
Once buffers are shared, coordination over "who is writing when" is essential. Next, we will cover
dma_fence, which handles synchronization, and drm_sched, which queues and schedules GPU workloads.