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.

Every DRM-capable GPU in a Linux system appears in userspace as one or more character device files under /dev/dri/. These files — called device nodes — are the entry points for all DRM communication. Opening a device node returns a file descriptor, and that file descriptor becomes the handle through which every subsequent ioctl is issued. Which operations are available on that descriptor depends on the type of node you opened. drm-rs models this with the DrmNode struct and NodeType enum in the drm::node module. A DrmNode carries both the underlying device identifier (dev_t) and the node type, giving you a typed handle that prevents accidentally using a render node where a primary node is required — or vice versa.

The Three Node Types

The DRM kernel ABI distinguishes three node types, each occupying a distinct range of minor device numbers.

Primary Node (/dev/dri/cardN, minors 0–63)

The primary node is the oldest and most capable node type. Opening it grants access to both modesetting (KMS) and buffer allocation (GEM). It is the right node to open when your process needs to configure display outputs, flip framebuffers, or enumerate connectors and CRTCs. However, modesetting operations on a primary node require holding the DRM Master lock. The DRM Master is acquired automatically when a compositor opens the node and calls acquire_master_lock(). On most systems, only one process can be DRM Master at a time.
/dev/dri/card0   ← first GPU, primary node
/dev/dri/card1   ← second GPU (if present)

Control Node (/dev/dri/controlDN, minors 64–127)

The control node was originally reserved for modesetting-only operations, separate from the rendering pipeline. In practice, no DRM API for control nodes has been finalized as of the time of writing — the kernel exposes these nodes but provides no useful ioctls through them. They exist as a placeholder for a future API.
/dev/dri/controlD64   ← first GPU, control node
Do not open control nodes expecting modesetting functionality. No stable API exists for them. Use the primary node (/dev/dri/cardN) for modesetting, and the render node for unprivileged GPU access.

Render Node (/dev/dri/renderDN, minors 128–255)

Render nodes were introduced to solve the privilege problem of the primary node. A render node allows unprivileged processes to allocate GEM buffers and submit rendering commands to the GPU — without ever needing DRM Master status. This is the preferred node type for clients (e.g., a Wayland application that wants to render using GPU acceleration) that do not own display outputs.
/dev/dri/renderD128   ← first GPU, render node
/dev/dri/renderD129   ← second GPU (if present)
If your code only needs to allocate GPU buffers or perform GPU-accelerated rendering (and does not need to control display outputs), always prefer the render node. It requires fewer privileges and is safer to use in multi-process environments. The node.has_render() method on DrmNode tells you whether a render node exists for a given device.

Working with DrmNode in Rust

drm-rs provides drm::node::DrmNode to represent a typed reference to any of the three node kinds. It does not itself open or own a file descriptor — it is a lightweight value type (two words) used for introspection, path lookups, and type-safe node identification.

Creating a DrmNode

use drm::node::{DrmNode, NodeType};

// From a filesystem path — stat(2) is called internally
let node = DrmNode::from_path("/dev/dri/card0").unwrap();
println!("Node type: {:?}", node.ty()); // NodeType::Primary

// From an already-open file descriptor (e.g., your Card wrapper)
use std::fs::OpenOptions;
let file = OpenOptions::new().read(true).write(true).open("/dev/dri/card0").unwrap();
let node = DrmNode::from_file(&file).unwrap();

// From a raw dev_t (major:minor device number)
// let node = DrmNode::from_dev_id(dev).unwrap();

Querying Node Properties

use drm::node::{DrmNode, NodeType};

let node = DrmNode::from_path("/dev/dri/card0").unwrap();

// Node type
println!("Type: {:?}", node.ty()); // NodeType::Primary

// Device numbers (from stat(2))
println!("Major: {}", node.major());
println!("Minor: {}", node.minor());

// Does this device have a matching render node?
println!("Has render node: {}", node.has_render());

Path Lookups and Cross-Type Navigation

A common pattern is to open a primary node for modesetting and then locate the corresponding render node for client buffer allocation. DrmNode makes this straightforward:
use drm::node::{DrmNode, NodeType};

let primary = DrmNode::from_path("/dev/dri/card0").unwrap();

// Get the path of this node itself
if let Some(path) = primary.dev_path() {
    println!("Primary path: {}", path.display()); // /dev/dri/card0
}

// Get the path to the matching render node for the same GPU
if let Some(render_path) = primary.dev_path_with_type(NodeType::Render) {
    println!("Render node: {}", render_path.display()); // /dev/dri/renderD128
}

// Create a DrmNode directly for the render counterpart
if let Some(Ok(render_node)) = primary.node_with_type(NodeType::Render) {
    println!("Render node type: {:?}", render_node.ty()); // NodeType::Render
}

Free Functions

The drm::node module also exposes a set of free functions for lower-level or path-oriented operations:
FunctionDescription
is_device_drm(dev)Returns true if the given dev_t (major:minor pair) is a DRM device. On Linux this checks /sys/dev/char/.
dev_path(dev, ty)Returns the filesystem path for a node of the given type on the DRM device described by dev_t.
node_path(node, ty)Like dev_path, but takes an existing &DrmNode instead of a raw dev_t.
path_to_type(path, ty)Given any DRM node path, returns the path for a different node type on the same device.
use drm::node::{is_device_drm, path_to_type, NodeType};
use rustix::fs::{stat, major, minor};

// Check whether an arbitrary device is DRM
let s = stat("/dev/dri/card0").unwrap();
println!("Is DRM: {}", is_device_drm(s.st_rdev));

// Translate a path to its render-node equivalent
let render_path = path_to_type("/dev/dri/card0", NodeType::Render).unwrap();
println!("Render node: {}", render_path.display());

Build docs developers (and LLMs) love