Linux DRM - Code Flow of open() and ioctl()

The code path of open() and ioctl() in Linux DRM v6.19: drm_stub_open, drm_file_alloc, drm_ioctl dispatch and permission checks, plus a libdrm probe example.
October 28, 2026

Linux DRM - Code Flow of open() and ioctl()

Reqruies
  • Kernel: Based on Linux v6.19 source
  • Library: libdrm 2.4.129
  • Prerequisites: Linux DRM Part 2: Core Initialization and Driver Registration

Introduction

PartTopic
Part 1Architecture and the Big Picture
Part 2Core Initialization and Driver Registration
In Part 2, we looked at the process of creating nodes such as /dev/dri/card0. This post traces the kernel functions executed the moment a user uses that node—specifically through open() and ioctl(). Finally, we will look at an example performing the same tasks in user space using libdrm.

Code Flow 3: open()

When a user calls open("/dev/dri/card0"), the kernel looks up the stub fops previously registered with major number 226. The stub does not perform real work itself; instead, it swaps out the file operations table with the driver's own.
drivers/gpu/drm/drm_drv.c - drm_stub_open() (L1188-1216)
c
open("/dev/dri/card0") Processing Flow
Mermaid
sequenceDiagram participant U as User Process participant V as VFS (chrdev 226) participant S as drm_stub_open participant O as drm_open / drm_open_helper U->>V: open("/dev/dri/card0") V->>S: drm_stub_fops.open S->>S: drm_minor_acquire(iminor(inode))<br/>→ struct drm_minor S->>S: replace_fops(filp, driver->fops) S->>O: filp->f_op->open() (= drm_open) O->>O: drm_file_alloc() → struct drm_file O->>O: If primary, drm_master_open() O-->>U: fd
The .open hook held in the driver's fops is drm_open(). This comes from a single macro line.
include/drm/drm_gem.h - #define DRM_GEM_FOPS (L464-473)
c
Source include/drm/drm_gem.h L464-L473 (Linux v6.19)
Inside this macro lie all the entry points we will see throughout this series: .unlocked_ioctl = drm_ioctl (ioctl), .read = drm_read (reading events), .mmap = drm_gem_mmap (buffer mapping), and .poll = drm_poll. Here are drm_open() and drm_open_helper():
drivers/gpu/drm/drm_file.c - drm_open() (L369-403)
c
drm_open() resolves the minor and forwards it to drm_open_helper().
drivers/gpu/drm/drm_file.c - drm_open_helper() (L316-355)
c
drm_file_alloc() allocates the state corresponding to a single open(). The object_idr mentioned earlier, the event queue, and other fields are initialized here.
drivers/gpu/drm/drm_file.c - drm_file_alloc() (L132-197)
c
drm_gem_open() initializes the handle table, which internally calls idr_init_base(&file_private->object_idr, 1). This is the reason why handle numbers start from 1 (leaving 0 to represent "none"). Thanks to filp->private_data = priv, subsequent ioctl, read, and mmap calls can directly obtain drm_file from file->private_data.

Code Flow 4: ioctl()

ioctl(fd, DRM_IOCTL_xxx, arg) enters into drm_ioctl(), which serves as fops->unlocked_ioctl.
Dispatch Flow in drm_ioctl()
Mermaid
flowchart TD A["ioctl(fd, DRM_IOCTL_xxx, arg)"] --> B["drm_ioctl()"] B --> C{"nr ≥ DRM_COMMAND_BASE<br/>(0x40) ?"} C -->|"Yes : Driver ioctl"| D["dev->driver->ioctls[nr - 0x40]"] C -->|"No : Core ioctl"| E["drm_ioctls[nr]"] D --> F["copy_from_user → kdata"] E --> F F --> G["drm_ioctl_kernel()"] G --> H{"drm_ioctl_permit()<br/>AUTH · MASTER · ROOT_ONLY · RENDER_ALLOW"} H -->|"Denied"| I["-EACCES"] H -->|"Permitted"| J["func(dev, kdata, file_priv)"] J --> K["copy_to_user → result"]
drivers/gpu/drm/drm_ioctl.c - drm_ioctl() (L821-919)
c
Summarizing the core steps:
  1. Checks whether the type of the ioctl number is 'd' (DRM_IOCTL_BASE).
  2. If the number is 0x40 or greater, it looks up the entry in the driver table (dev->driver->ioctls); otherwise, it looks in the core table (drm_ioctls[]).
  3. Rather than trusting the size provided by the user, it calculates the buffer size using the kernel definition (ioctl->cmd) (Do not trust userspace).
  4. After copy_from_user(), drm_ioctl_kernel() checks permissions and invokes the actual function.
drivers/gpu/drm/drm_ioctl.c - drm_ioctl_kernel() (L787-805)
c
The core ioctl table is an array constructed using macros. Selecting only the entries we will encounter in this series yields the following list. The flags on the far right correspond to the permission rules mentioned earlier.
drivers/gpu/drm/drm_ioctl.c - drm_ioctls (L632-735)
c

libdrm: The View from User Space

Now let's trace the flow above directly from user space. The following program opens a DRM device, prints driver details, and lists KMS objects.
drm_probe.c - List driver info and KMS objects
c
Each libdrm function maps directly to a single kernel ioctl. You can match the exact names from the drm_ioctls[] table in the code above:
libdrm FunctionioctlKernel HandlerFlags
drmGetVersion()DRM_IOCTL_VERSIONdrm_version()RENDER_ALLOW
drmGetCap()DRM_IOCTL_GET_CAPdrm_getcap()RENDER_ALLOW
drmSetClientCap()DRM_IOCTL_SET_CLIENT_CAPdrm_setclientcap()None
drmModeGetResources()DRM_IOCTL_MODE_GETRESOURCESdrm_mode_getresources()None
drmModeGetConnector()DRM_IOCTL_MODE_GETCONNECTORdrm_mode_getconnector()None
drmModeGetPlaneResources()DRM_IOCTL_MODE_GETPLANERESOURCESdrm_mode_getplane_res()None
drmModeObjectGetProperties()DRM_IOCTL_MODE_OBJ_GETPROPERTIESdrm_mode_obj_get_properties_ioctl()None
This program has been verified to compile with libdrm 2.4.129. Running it requires access permissions to /dev/dri/card0 (usually available in a desktop login session, or requires membership in the video/render group). Running strace -e trace=openat,ioctl ./drm_probe shows the sequence of ioctls invoked by the user (depending on the strace version, it will also display decoded names such as DRM_IOCTL_VERSION).

Code Locations

StepFunctionLocation (Linux v6.19)
open: Create file statedrm_open_helper()drivers/gpu/drm/drm_file.c
open: Allocate drm_filedrm_file_alloc()drivers/gpu/drm/drm_file.c
open: Set masterdrm_master_open()drivers/gpu/drm/drm_auth.c
ioctl: Entry and dispatchdrm_ioctl()drivers/gpu/drm/drm_ioctl.c
ioctl: Check permissionsdrm_ioctl_permit()drivers/gpu/drm/drm_ioctl.c
ioctl: Core ioctl tabledrm_ioctlsdrivers/gpu/drm/drm_ioctl.c

Summary

Here is what we covered in this post:
  • open: The stub replaces the fops with the driver's own, and drm_file_alloc() creates the state (handle table, event queue) for a single open().
  • ioctl: drm_ioctl() locates the entry in the core or driver table, and drm_ioctl_permit() validates permissions before calling the handler.
  • Most libdrm functions are thin wrappers around these ioctl calls.
Next up is rendering. First, we will examine how GEM handles stored in drm_file's object_idr are created, and how their buffers are read and written by the CPU.
Jooojub
System S/W engineer
Explore Tags
Series
    Recent Post
    © 2026. jooojub. All right reserved.