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.

Kernel Modesetting (KMS) is the part of DRM that controls display hardware. When you configure a monitor’s resolution, refresh rate, or which physical port it is connected to, you are using KMS. The word “modesetting” comes from the concept of a display mode — a specific combination of resolution, pixel clock, and sync timings that a display understands. Before KMS, this configuration happened entirely in userspace (typically the X server), which required direct hardware access and prevented multiple processes from safely sharing the display pipeline. KMS moved that responsibility into the kernel, so the kernel owns the hardware state, arbitrates access, and ensures clean handover between compositors. From a drm-rs perspective, KMS functionality is exposed through the control::Device trait (found in drm::control), which extends the base drm::Device trait. Your device wrapper must implement both to use modesetting.

Why KMS Matters

KMS solves three problems that plagued earlier display server architectures:
  1. Tear-free mode changes — The kernel can defer mode changes to a vblank boundary, preventing visible tearing when switching resolutions or refresh rates.
  2. VT switching — Because the kernel tracks hardware state, it can restore the display pipeline when you switch between virtual terminals without either session needing to reconfigure hardware from scratch.
  3. Multi-process safety — Multiple compositors can exist (though only one is DRM Master at a time), and the kernel ensures they don’t corrupt each other’s hardware state.

The Display Pipeline

Every frame that appears on a screen travels through a fixed pipeline of KMS objects. Understanding this pipeline is essential to using drm-rs effectively:
GEM Buffer → Framebuffer → Plane → CRTC → Encoder → Connector → Display
Each stage in this pipeline corresponds to a distinct resource type in the DRM kernel ABI, and each has a matching Rust type in drm-rs.

Resource Types

Connector — control::connector

A connector represents a physical output port on the GPU or display controller. It corresponds directly to what you can physically plug a cable into: an HDMI port, a DisplayPort receptacle, a VGA socket, an eDP panel on a laptop. The connector::Interface enum lists all recognized output types:
pub enum Interface {
    VGA, DVII, DVID, DVIA, Composite, SVideo,
    LVDS, Component, NinePinDIN,
    DisplayPort, HDMIA, HDMIB,
    TV,
    EmbeddedDisplayPort, // eDP — built-in laptop panels
    DSI, DPI, Writeback, SPI, USB,
    Virtual, Unknown,
}
Each connector has a State (one of Connected, Disconnected, or Unknown) and reports a list of Mode values that the attached display supports. You can enumerate connectors with device.resource_handles() and retrieve detailed information with device.get_connector(handle, force_probe).
use drm::control::Device as ControlDevice;

let resources = device.resource_handles()?;
for handle in resources.connectors() {
    let info = device.get_connector(*handle, false)?;
    println!(
        "{} — {:?} — {} modes",
        info,                 // e.g., "DP-1"
        info.state(),         // Connected / Disconnected / Unknown
        info.modes().len()
    );
    for mode in info.modes() {
        println!("  {:?} @ {} Hz", mode.size(), mode.vrefresh());
    }
}
The force_probe flag asks the kernel to physically re-probe the connector (querying EDID from the display). This can be slow and may cause a brief flicker, so it should only be used on startup or in response to a hotplug event.

Encoder — control::encoder

An encoder is a piece of display controller hardware that converts the raw pixel stream coming out of a CRTC into a signal that a particular connector type can transmit. For example, a TMDS (Transition-Minimized Differential Signaling) encoder converts pixels into the electrical signals used by HDMI and DVI-D. A DAC (Digital-to-Analog Converter) encoder produces the analog signal used by VGA.
pub enum Kind {
    None, DAC, TMDS, LVDS, TVDAC,
    Virtual, DSI, DPMST, DPI,
}
Encoders sit between CRTCs and connectors. Each connector advertises which encoders it is compatible with (info.encoders()), and each encoder advertises which CRTCs can feed it (info.possible_crtcs()). When setting up a display pipeline you need to find a valid combination of CRTC → Encoder → Connector.
let enc_info = device.get_encoder(encoder_handle)?;
println!("Kind: {:?}", enc_info.kind());
println!("Attached CRTC: {:?}", enc_info.crtc());

// Which CRTCs can drive this encoder?
let compatible_crtcs = resources.filter_crtcs(enc_info.possible_crtcs());
In practice, on modern hardware with atomic modesetting the kernel handles encoder assignment automatically when you specify a connector and a mode — you rarely need to select an encoder explicitly.

CRTC — control::crtc

CRTC stands for CRT Controller, a name inherited from the era of cathode-ray tube displays. Today it refers more generally to a scanout engine: a hardware block that reads pixel data from one or more planes and drives an encoder at a specific resolution and refresh rate. Each CRTC has at least one associated Primary Plane.
let crtc_info = device.get_crtc(crtc_handle)?;
println!("Current mode: {:?}", crtc_info.mode());
println!("Active framebuffer: {:?}", crtc_info.framebuffer());
println!("Position: {:?}", crtc_info.position());
A Mode carries all the timing information for a display configuration — resolution, pixel clock, horizontal and vertical sync parameters, and refresh rate. Modes come from the connector’s EDID data and can be queried with device.get_modes(connector_handle) or from connector::Info::modes().

Plane — control::plane

A plane is an attachment point for a framebuffer. Rather than the CRTC scanning out directly from a buffer, it scans from one or more planes that it owns. This indirection enables hardware compositing: the display controller can blend multiple planes together in fixed hardware, at zero CPU cost. There are three plane types, defined in control::PlaneType:
TypeDescription
PrimaryEvery CRTC has exactly one primary plane. This is the main content layer. Attaching a framebuffer to a CRTC in legacy mode implicitly uses the primary plane.
OverlayAdditional content layers that can be positioned and scaled on top of (or behind) the primary plane using fast hardware compositing.
CursorA small plane optimized for displaying a mouse cursor. Hardware cursor planes avoid redrawing the entire framebuffer when the cursor moves.
use drm::control::Device as ControlDevice;

// Enumerate all planes (requires ClientCapability::UniversalPlanes)
let plane_handles = device.plane_handles()?;
for handle in plane_handles {
    let info = device.get_plane(handle)?;
    println!("Plane {:?}: CRTC={:?}, FB={:?}", handle, info.crtc(), info.framebuffer());
    println!("  Supported formats: {:?}", info.formats());
}

Framebuffer — control::framebuffer

A framebuffer wraps a GEM buffer and registers it with the KMS subsystem so it can be attached to a plane. The framebuffer holds metadata about the buffer’s dimensions, pixel format (as a DRM fourcc code), and pitch (bytes per row). You create framebuffers with add_framebuffer (for single-plane, legacy formats) or add_planar_framebuffer (for YUV or tiled formats with modifiers). Framebuffers are per-process objects — they cannot be shared across open file descriptors.
use drm::control::Device as ControlDevice;

// Wrap a dumb buffer as a framebuffer
let fb_handle = device.add_framebuffer(&dumb_buffer, 24, 32)?;

// Or, for tiled/planar formats with a modifier:
use drm::control::FbCmd2Flags;
let fb_handle = device.add_planar_framebuffer(&planar_buffer, FbCmd2Flags::MODIFIERS)?;

// Clean up when done
device.destroy_framebuffer(fb_handle)?;

Legacy vs. Atomic Modesetting

DRM supports two distinct APIs for committing display configuration changes. drm-rs exposes both.

Legacy Modesetting

The legacy API, centered on set_crtc(), predates atomic modesetting. It configures one CRTC (and its connected outputs) at a time with a blocking call.
use drm::control::Device as ControlDevice;

device.set_crtc(
    crtc_handle,
    Some(framebuffer_handle),   // framebuffer to display
    (0, 0),                     // position of the display in a virtual desktop
    &[connector_handle],        // connectors to drive
    Some(mode),                 // display mode
)?;
Legacy modesetting is simple and has wide driver support. However, it has significant limitations: changes are not atomic (partial failures leave hardware in an inconsistent state), there is no way to validate a configuration before committing it, and it cannot express multi-display or multi-plane configurations cleanly. It is deprecated for new code.

Atomic Modesetting

The atomic API, atomic_commit() with AtomicModeReq, treats the entire display pipeline as a property graph. Every KMS object (connector, CRTC, plane, framebuffer) exposes a set of properties, and an atomic commit changes any number of those properties at once — atomically. Either all changes are applied or none are.
use drm::control::{Device as ControlDevice, AtomicCommitFlags, atomic::AtomicModeReq};
use drm::control::property;

let mut req = AtomicModeReq::new();

// Set properties on a CRTC, connector, and plane
req.add_property(connector_handle, mode_id_prop, mode_blob_value);
req.add_property(crtc_handle, active_prop, 1);
req.add_property(plane_handle, fb_id_prop, fb_handle.into());
req.add_property(plane_handle, crtc_id_prop, crtc_handle.into());

// Test the configuration without applying it
device.atomic_commit(AtomicCommitFlags::TEST_ONLY, req.clone())?;

// Apply the configuration
device.atomic_commit(
    AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::ALLOW_MODESET,
    req,
)?;
The AtomicCommitFlags control the commit behavior:
FlagEffect
TEST_ONLYValidate the request without applying changes. Use this to check feasibility.
NONBLOCKReturn immediately rather than waiting for the next vblank.
ALLOW_MODESETPermit changes that require a full modeset (e.g., changing resolution).
PAGE_FLIP_EVENTDeliver a page-flip event on the DRM file descriptor when the flip completes.
PAGE_FLIP_ASYNCRequest the page flip as soon as possible without waiting for vblank.

Client Capabilities

Before using certain advanced features, you must declare your intent to the kernel by setting client capabilities with device.set_client_capability(). Two capabilities are particularly relevant for KMS:
use drm::{Device, ClientCapability};

// Required to see Overlay and Cursor planes (not just Primary planes)
device.set_client_capability(ClientCapability::UniversalPlanes, true)?;

// Required to use the atomic modesetting API
device.set_client_capability(ClientCapability::Atomic, true)?;
Without UniversalPlanes, plane_handles() only returns primary planes. Without Atomic, atomic_commit() will fail.
Practical code examples for configuring a display using both the legacy and atomic APIs can be found in the guides section: see Legacy Modesetting and Atomic Modesetting.

Build docs developers (and LLMs) love