Linux DRM - Architecture and the Big Picture
An overview of the Linux Direct Rendering Manager (DRM): why it exists, the user and kernel stack, the /dev/dri device nodes, and the core structures drm_driver, drm_device and drm_file in Linux v6.19.
October 26, 2026
Linux DRM - Architecture and the Big Picture
Reqruies
- Kernel: Based on Linux v6.19 source
- Library: libdrm 2.4.129
Introduction
Drawing anything on the screen in Linux goes through a kernel subsystem called DRM (Direct Rendering Manager). Desktop Wayland or Xorg, Android's SurfaceFlinger, and even the Raspberry Pi console display all operate on top of the same DRM.
This has nothing to do with Digital Rights Management (copyright protection), which shares a similar name; this article deals exclusively with the Linux kernel's graphics subsystem.
In this post, we explore what DRM is and how it connects to the kernel. We will delve deeply into the actual code starting in the next post.
Why DRM Was Needed
Before DRM existed, there were two primary approaches:
- fbdev (
/dev/fb0): A straightforward method thatmmaps a single chunk of display memory to directly write pixels. It lacks features such as 3D acceleration, multi-monitor support, and resolution changes. - Direct hardware control by the X server: The X server ran with root privileges to directly access graphics card registers, configuring resolutions (modes) and sending 3D commands.
The second approach had several critical problems:
- For 3D applications to use the GPU directly, the kernel needed to arbitrate GPU access, but no such mechanism existed (concurrent GPU access by multiple processes caused mutual corruption).
- Because resolution configuration (mode setting) lived in user space, switching between console and X caused screen flickering, and controlling early boot splash screens was impossible.
- The requirement for the X server to run as root posed severe security vulnerabilities.
DRM solved these issues by establishing a model where "the kernel manages the GPU and display, while user space accesses them strictly through safe interfaces." The major milestones were roughly as follows:
| Feature | Role | Approximate Era |
|---|---|---|
| DRI / DRM | Applications send commands directly to the GPU under kernel mediation | Late 1990s ~ around 2000 |
| GEM | Kernel manages GPU buffers (memory) | Around Linux 2.6.28 |
| KMS | Resolution/display configuration moved into kernel | Around Linux 2.6.29 |
| dma-buf (PRIME) | Shares buffers across processes and devices | Around Linux 3.3 |
| render node | Dedicated nodes for GPU compute without display control | Around Linux 3.12 |
| atomic modeset | Atomically verifies and applies display configuration changes | Linux 3.19 (Kernel doc Documentation/gpu/todo.rst) |
The structures covered in this article are the results of this history. Keep in mind that a single subsystem handles both rendering (drawing with GPU) and display (outputting to screen).
The Full Stack
Placement of DRM in the Linux Graphics StackMermaidflowchart TB subgraph US["User space"] APP["3D Apps / Games<br/>OpenGL · Vulkan"] COMP["Display Server<br/>Wayland compositor · Xorg"] MESA["Mesa<br/>user-mode GPU driver"] LIBDRM["libdrm"] end subgraph KS["Kernel space"] NR["/dev/dri/renderD128<br/>render node"] NC["/dev/dri/card0<br/>primary node"] CORE["DRM core<br/>drm_drv.c · drm_ioctl.c · drm_gem.c ..."] HELP["DRM helpers<br/>atomic · gem_shmem · gpu scheduler"] DRV["GPU / display driver<br/>i915 · amdgpu · v3d · vkms ..."] end HW["GPU + display controller + monitor"] APP --> MESA --> LIBDRM COMP --> LIBDRM LIBDRM -->|"ioctl · mmap"| NR LIBDRM -->|"ioctl · read"| NC NR --> CORE NC --> CORE CORE --> HELP --> DRV CORE --> DRV DRV --> HW
- Applications draw using OpenGL/Vulkan. Mesa, which implements these APIs, generates GPU-specific commands (shaders, state setup). This part is called the user-mode driver (UMD).
- libdrm is a thin C library wrapping
ioctl(). In other words, user space communicates with DRM solely by opening /dev/dri/* files and issuingioctl,mmap, andread. - The kernel side is divided into the common DRM core, helpers shared across various drivers, and hardware-specific drivers.
Two pathways are visible in the diagram. 3D applications use the render node solely for GPU compute, while display configuration is handled by the display server (compositor) through the primary node (card0). This separation is at the heart of DRM's design.
Two Jobs of DRM
| Rendering | Display | |
|---|---|---|
| Objective | Generates images using the GPU | Outputs generated images to the screen |
| Kernel Features | Memory management (GEM), sharing (dma-buf), synchronization (fence), job submission (scheduler) | KMS (framebuffer, plane, CRTC, encoder, connector) |
| Accessible Nodes | Available via render node (renderD128) | Available exclusively via primary node (card0) |
Device Nodes: /dev/dri and sysfs
First, let's verify this on an actual PC (author's PC: kernel 7.0.0, Intel i915 driver).
Verifying DRM Device Nodesshell
- The major number is 226 and the name is
drm. This value is registered by the kernel during boot (verified in the code below). - card0 has minor number 0, while renderD128 has 128. This indicates that minor number ranges vary depending on the node type.
- Connectors also appear in sysfs, such as
card0-DP-1andcard0-HDMI-A-1.
This rule resides in the kernel's
drm_drv.c.drivers/gpu/drm/drm_drv.c - #define DRM_MINOR_LIMIT (L136-139)c
Source drivers/gpu/drm/drm_drv.c L136-L139 (Linux v6.19)
include/drm/drm_file.h - enum drm_minor_type (L61-66)c
Source include/drm/drm_file.h L61-L66 (Linux v6.19)
- Primary (
DRM_MINOR_PRIMARY = 0) spans minor numbers 0–63, while render (DRM_MINOR_RENDER = 2) begins at64 * 2 = 128. The226, 128in thelsoutput above reflects this exact calculation. - The name is assigned in
drm_sysfs_minor_alloc().
drivers/gpu/drm/drm_sysfs.c - drm_sysfs_minor_alloc() (L551-591)c
Source drivers/gpu/drm/drm_sysfs.c L551-L591 (Linux v6.19)
You can see the names
"renderD%d", "card%d", and MKDEV(DRM_MAJOR, minor->index). Once this struct device is later registered via device_add(), udev and devtmpfs create /dev/dri/card0 and /dev/dri/renderD128.How card0 Differs from renderD128
The primary node (card0) has access to all capabilities (including KMS) and can hold DRM master, the privilege required for display control. The render node (renderD128), while unable to configure displays, is free from permission constraints. Anyone with file access permissions can open it to run GPU computations.
This distinction is baked into each ioctl via flags and enforced by
drm_ioctl_permit().include/drm/drm_ioctl.h - enum drm_ioctl_flags (L80-122)c
Source include/drm/drm_ioctl.h L80-L122 (Linux v6.19)
drivers/gpu/drm/drm_ioctl.c - drm_ioctl_permit() (L599-621)c
Source drivers/gpu/drm/drm_ioctl.c L599-L621 (Linux v6.19)
Reading through the code makes the rules clear:
- An ioctl flagged with
DRM_MASTERcan only be invoked by the current master. A prime example is DRM_IOCTL_MODE_ATOMIC. - Render nodes (
drm_is_render_client) can only execute ioctls whereDRM_RENDER_ALLOWis explicitly designated. Hence, whileDRM_IOCTL_VERSIONandDRM_IOCTL_PRIME_HANDLE_TO_FDwork on render nodes,DRM_IOCTL_MODE_CREATE_DUMBhas flags set to 0 and is rejected on render nodes.
Then who becomes the master? The first client to open the primary file automatically acquires master status.
drivers/gpu/drm/drm_auth.c - drm_master_open() (L317-335)c
Source drivers/gpu/drm/drm_auth.c L317-L335 (Linux v6.19)
Therefore, if the desktop environment has already opened
card0 and holds master privileges, any other application attempting display configuration (atomic commit) on that same card0 will receive -EACCES.Struct Relationships Inside DRM Core
There are three core DRM structures:
drm_driver (a constant template defined by the driver), drm_device (representing a single device), and drm_file (representing an individual open()).Relationships between drm_driver · drm_device · drm_minor · drm_fileMermaidflowchart TB DRIVER["struct drm_driver<br/>const template defined by driver<br/>name · driver_features · fops · ioctls · dumb_create ..."] DEV["struct drm_device<br/>instance of 1 physical device<br/>filelist · mode_config · vblank ..."] PRI["drm_minor : primary<br/>card0 · 226:0"] REN["drm_minor : render<br/>renderD128 · 226:128"] F1["drm_file<br/>Process A's open()"] F2["drm_file<br/>Process B's open()"] PF["Per-open state<br/>object_idr : GEM handle table · fbs : created framebuffers<br/>event_list : event queue · master · atomic settings"] DRIVER -->|"dev->driver"| DEV DEV --> PRI DEV --> REN PRI --> F1 REN --> F2 F1 --- PF
drm_driver: The Template Drivers Fill In
A driver defines a single
struct drm_driver as const. Inside are feature flags, file operations, a driver-specific ioctl table, and several callbacks.include/drm/drm_drv.h - struct drm_driver (L181-434)c
Source include/drm/drm_drv.h L181-L434 (Linux v6.19)
driver_features: Bit flags such as DRIVER_GEM, DRIVER_MODESET, DRIVER_RENDER, and DRIVER_ATOMIC. The DRM core inspects this value to decide which features to activate (for instance,DRIVER_RENDERmust be present to instantiate a render node).fops: Thefile_operationsfor/dev/dri/cardN. Most drivers use a standard macro for this (seen below).ioctls,num_ioctls: Driver-specific ioctl table. The number range is0x40through0xA0(DRM_COMMAND_BASEtoDRM_COMMAND_END).dumb_create,gem_prime_import_sg_table, etc.: Driver callbacks invoked by the core. The core handles common tasks and delegates hardware-dependent behavior to these callbacks. This pattern permeates all of DRM.
include/drm/drm_drv.h - enum drm_driver_feature (L57-170)c
Source include/drm/drm_drv.h L57-L170 (Linux v6.19)
drm_file: One per open()
Each
open() of /dev/dri/card0 spawns a struct drm_file. All per-process (or per-fd) states reside here.include/drm/drm_file.h - struct drm_file (L164-420)c
Source include/drm/drm_file.h L164-L420 (Linux v6.19)
- object_idr: A table mapping user-visible GEM handles to kernel objects. This implies handles are isolated per fd (drm_file).
- event_list,
pending_event_list: Queues buffering events such as page flip completion. User space retrieves them by callingread()on this fd. - atomic,
universal_planes: Client capabilities enabled viadrmSetClientCap(). Atomic ioctls require these to be turned on. master,is_master: Indicate whether this file holds display control authority.
Source Tree Map
When exploring DRM code, the first question to answer is "what is located where."
| Location | Contents |
|---|---|
include/uapi/drm/ | ABI shared between user space and kernel (drm.h, drm_mode.h, driver-specific *_drm.h) |
include/drm/ | Kernel-internal headers (drm_device.h, drm_drv.h, drm_gem.h ...) |
drivers/gpu/drm/drm_drv.c, drm_file.c, drm_ioctl.c | Core backbone including devices, files, ioctl |
drivers/gpu/drm/drm_gem*.c, drm_prime.c, drm_syncobj.c | Buffer management, sharing, and synchronization |
drivers/gpu/drm/drm_atomic*.c, drm_crtc.c, drm_plane.c, drm_vblank.c | KMS and atomic modesetting |
drivers/gpu/drm/scheduler/ | GPU job scheduler |
drivers/dma-buf/ | dma-buf, dma-fence |
drivers/gpu/drm/<driver>/ | Individual drivers like i915, amdgpu, v3d, vkms |
Documentation/gpu/ | Official documentation (drm-kms.rst, drm-mm.rst, drm-uapi.rst, drm-internals.rst) |
In this series, we will frequently refer to vkms (Virtual Kernel Mode Setting) when illustrating driver structures. Being a virtual driver devoid of hardware dependencies, its codebase is compact (
drivers/gpu/drm/vkms/, roughly 6,000 lines) and cleanly reveals the minimal skeleton required by DRM.Code Locations
| Stage | Function / Struct | Location (Linux v6.19) |
|---|---|---|
| Struct: Driver template | struct drm_driver | include/drm/drm_drv.h |
| Struct: Single open() state | struct drm_file | include/drm/drm_file.h |
Summary
Here is what we covered in this post:
- DRM is a subsystem through which the kernel arbitrates rendering (GPU) and display (KMS); user space interfaces with it solely via
ioctl,mmap, andreadon/dev/dri/*. - Device nodes use major number 226 and include primary (card) and render (renderD) nodes, with permission distinctions enforced through ioctl flags (
DRM_MASTER,DRM_RENDER_ALLOW). - The hierarchy is structured as
drm_driver(template) →drm_device(device) →drm_minor(node) →drm_file(open), with the core handling common tasks and delegating hardware-specific routines to driver callbacks.
In the next post, we will walk through the code of the vkms driver to trace the process by which this architecture is materialized: core initialization (registering 226) and driver registration (
drm_dev_register).