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 that mmaps 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:
FeatureRoleApproximate Era
DRI / DRMApplications send commands directly to the GPU under kernel mediationLate 1990s ~ around 2000
GEMKernel manages GPU buffers (memory)Around Linux 2.6.28
KMSResolution/display configuration moved into kernelAround Linux 2.6.29
dma-buf (PRIME)Shares buffers across processes and devicesAround Linux 3.3
render nodeDedicated nodes for GPU compute without display controlAround Linux 3.12
atomic modesetAtomically verifies and applies display configuration changesLinux 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 Stack
Mermaid
flowchart 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 issuing ioctl, mmap, and read.
  • 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

RenderingDisplay
ObjectiveGenerates images using the GPUOutputs generated images to the screen
Kernel FeaturesMemory management (GEM), sharing (dma-buf), synchronization (fence), job submission (scheduler)KMS (framebuffer, plane, CRTC, encoder, connector)
Accessible NodesAvailable 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 Nodes
shell
  • 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-1 and card0-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
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 at 64 * 2 = 128. The 226, 128 in the ls output 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
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
drivers/gpu/drm/drm_ioctl.c - drm_ioctl_permit() (L599-621)
c
Reading through the code makes the rules clear:
  • An ioctl flagged with DRM_MASTER can 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 where DRM_RENDER_ALLOW is explicitly designated. Hence, while DRM_IOCTL_VERSION and DRM_IOCTL_PRIME_HANDLE_TO_FD work on render nodes, DRM_IOCTL_MODE_CREATE_DUMB has 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
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_file
Mermaid
flowchart 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_RENDER must be present to instantiate a render node).
  • fops: The file_operations for /dev/dri/cardN. Most drivers use a standard macro for this (seen below).
  • ioctls, num_ioctls: Driver-specific ioctl table. The number range is 0x40 through 0xA0 (DRM_COMMAND_BASE to DRM_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
  • 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 calling read() on this fd.
  • atomic, universal_planes: Client capabilities enabled via drmSetClientCap(). 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."
LocationContents
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.cCore backbone including devices, files, ioctl
drivers/gpu/drm/drm_gem*.c, drm_prime.c, drm_syncobj.cBuffer management, sharing, and synchronization
drivers/gpu/drm/drm_atomic*.c, drm_crtc.c, drm_plane.c, drm_vblank.cKMS 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

StageFunction / StructLocation (Linux v6.19)
Struct: Driver templatestruct drm_driverinclude/drm/drm_drv.h
Struct: Single open() statestruct drm_fileinclude/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, and read on /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).
Jooojub
System S/W engineer
Explore Tags
Series
    Recent Post
    © 2026. jooojub. All right reserved.