Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Smithay/drm-rs/llms.txt

Use this file to discover all available pages before exploring further.

The DRM subsystem is accessed through ioctl calls on an open file descriptor that points to a device node — typically something like /dev/dri/card0. drm-rs does not open these nodes for you. Instead, you bring your own file descriptor by wrapping it in a type that implements AsFd, then implement the drm::Device trait on that wrapper to unlock the full drm-rs API. This design lets you obtain the file descriptor however is most appropriate for your program — directly via std::fs::File, through a seat manager such as logind, or via udev.
Device nodes under /dev/dri/ are typically owned by the video group (or managed by udev rules tied to seat ownership). If you open the node directly without a seat manager, make sure your user is a member of the video group. Alternatively, use a session manager such as logind or seatd, which will hand you an authenticated file descriptor without requiring group membership.
Opening a primary node (/dev/dri/cardN) and performing modesetting requires the DRM Master lock. Only one process can hold the master lock at a time. If a compositor or X server is already running, it will hold the lock and your set_crtc or atomic_commit calls will fail with EACCES. Use a render node (/dev/dri/renderDN) for GPU rendering work that does not require modesetting.

Step 1 — Create a Card wrapper struct

The minimal wrapper needs a std::fs::File (or any type that is itself AsFd) and an AsFd implementation that delegates to it.
use std::fs::{File, OpenOptions};
use std::os::unix::io::{AsFd, BorrowedFd};

/// A simple wrapper for a DRM device node.
pub struct Card(File);

/// Implementing `AsFd` is a prerequisite for all drm-rs traits.
impl AsFd for Card {
    fn as_fd(&self) -> BorrowedFd<'_> {
        self.0.as_fd()
    }
}

impl Card {
    /// Open the device node at `path` with read/write access.
    pub fn open(path: &str) -> Self {
        let mut options = OpenOptions::new();
        options.read(true);
        options.write(true);
        Card(options.open(path).expect("Could not open device node"))
    }
}
The file must be opened with both read and write access — DRM ioctls require a writable file descriptor.

Step 2 — Implement drm::Device

drm::Device is a trait with no required methods; every method has a default implementation backed by the underlying ioctl. All you need is an empty impl block:
impl drm::Device for Card {}
If you also need modesetting (KMS) functionality, implement drm::control::Device the same way:
use drm::control::Device as ControlDevice;

impl ControlDevice for Card {}

Step 3 — Open the device

fn main() {
    let card = Card::open("/dev/dri/card0");
    println!("Opened DRM device successfully");
}

Step 4 — Query driver information

Once you have a Card, you can inspect the kernel driver that manages the hardware:
let driver = card.get_driver().unwrap();
println!("Driver name:        {:?}", driver.name());
println!("Driver date:        {:?}", driver.date());
println!("Driver description: {:?}", driver.description());

let (major, minor, patch) = driver.version;
println!("Driver version: {}.{}.{}", major, minor, patch);
get_driver() returns a Driver struct. Its name(), date(), and description() methods all return &OsStr, reflecting the fact that driver metadata may not always be valid UTF-8.

Step 5 — Set client capabilities

Client capabilities opt the process into newer kernel APIs. You must enable them before using features that depend on them:
// Expose all plane types (cursor, overlay, primary), not just the legacy ones.
card.set_client_capability(drm::ClientCapability::UniversalPlanes, true)
    .expect("Could not enable UniversalPlanes capability");

// Unlock the atomic modesetting API.
card.set_client_capability(drm::ClientCapability::Atomic, true)
    .expect("Could not enable Atomic capability");
Available ClientCapability variants:
VariantPurpose
Stereo3DExpose stereo 3D display modes
UniversalPlanesExpose all hardware plane types
AtomicEnable the atomic commit API
AspectRatioInclude aspect ratio data in mode info
WritebackConnectorsExpose writeback connector objects (requires Atomic)
CursorPlaneHotspotEnable cursor hotspot properties (requires Atomic)

Step 6 — Check driver capabilities

Driver capabilities are hardware or driver-level features you can query at runtime:
let has_dumb = card
    .get_driver_capability(drm::DriverCapability::DumbBuffer)
    .unwrap();
println!("Dumb buffer support: {}", has_dumb != 0);

let has_syncobj = card
    .get_driver_capability(drm::DriverCapability::SyncObj)
    .unwrap();
println!("SyncObj support: {}", has_syncobj != 0);

let has_timeline = card
    .get_driver_capability(drm::DriverCapability::TimelineSyncObj)
    .unwrap();
println!("Timeline SyncObj support: {}", has_timeline != 0);
Key DriverCapability variants:
VariantPurpose
DumbBufferCPU-mappable dumb buffer allocation
PrimePRIME buffer sharing (dmabuf)
ASyncPageFlipAsynchronous page flips (legacy API)
AtomicASyncPageFlipAsynchronous page flips (atomic API)
SyncObjBinary sync object support
TimelineSyncObjTimeline sync object support
AddFB2ModifiersFramebuffer tiling modifiers
CursorWidth / CursorHeightMaximum cursor plane dimensions

DRM Master lock

The DRM Master lock is the kernel-enforced exclusive right to perform modesetting on a device. It is granted automatically when you open a primary node (/dev/dri/cardN) — as long as no other process already holds it. You can explicitly acquire and release the lock:
// Acquire the DRM Master lock.
card.acquire_master_lock()
    .expect("Could not acquire DRM master lock");

// ... perform modesetting ...

// Release the lock so another process can use it.
card.release_master_lock()
    .expect("Could not release DRM master lock");
acquire_master_lock() requires CAP_SYS_ADMIN privileges (i.e. root) or that your process already owns the primary node via session management. If a display server is running and holds the lock, you must use a mechanism like logind’s TakeDevice D-Bus call to receive a properly authenticated descriptor instead of opening the node directly.

Build docs developers (and LLMs) love