DRM/KMS Test Pattern (No Window Manager)
Time: 30 min | Prerequisites: Framebuffer Basics
This tutorial shows how to use the modern DRM/KMS graphics API to display test patterns, inspect display hardware, and understand why DRM replaces the legacy framebuffer.
Learning Objectives
By the end of this tutorial you will be able to:
- Explain the difference between legacy framebuffer and DRM/KMS
- Use
modetestto list connectors, CRTCs, planes, and display modes - Display SMPTE and other test patterns on screen using DRM/KMS
- Find the correct connector ID, CRTC ID, and plane ID for your display
- Understand why DRM/KMS exists and what problems it solves
Tearing, VSync, and Why DRM/KMS Exists
Tearing occurs when your application writes new pixel data to the framebuffer while the display controller is still reading the old frame. The monitor ends up showing the top half of one frame and the bottom half of the next -- a visible horizontal tear line. In the framebuffer tutorial, the bouncing square showed this artifact because /dev/fb0 has no synchronisation mechanism.
VSync (vertical synchronisation) solves tearing by coordinating buffer updates with the display's vertical blanking interval -- the brief pause between finishing one frame and starting the next. During this interval, no pixels are being read, so it is safe to swap buffers. Page flipping implements this: you prepare a complete frame in a back buffer, then atomically swap it with the front buffer during VBlank. The display controller always reads a complete, consistent frame.
DRM (Direct Rendering Manager) and KMS (Kernel Mode Setting) provide this capability at the kernel level. DRM manages GPU and display hardware access; KMS handles display timing (resolution, refresh rate, pixel clock) inside the kernel. Together they give you hardware-reported display modes (from EDID), page flipping synchronised to VBlank, multiple hardware planes for compositing, and a stable API that every modern Linux display driver implements. The pixel math (y * stride + x * pixel_bytes) is identical to framebuffer -- only the plumbing changes.
For deeper reading see the Graphics Stack reference page.
Course Source Repository
This tutorial references source files from the course repository. If you haven't cloned it yet on your Pi:
Source files for this tutorial are in ~/embedded-linux/solutions/drm-kms-test/.
1. Install DRM Tools
This installs modetest — a command-line tool that talks directly to the DRM/KMS API without any GUI stack.
2. Discover Your Display Hardware
Before you can display anything, you need to find three things: which connector your display is plugged into, which CRTC drives it, and which plane to draw on. The modetest tool queries all of this from the kernel.
2a. Find the DRM Driver
On Raspberry Pi, the DRM driver is vc4 (Broadcom VideoCore). Every SoC has its own driver (e.g., i915 for Intel, amdgpu for AMD). Verify:
You should see card0 (and possibly card1). If you only see renderD128 but no card*, your kernel may not have KMS enabled.
2b. List Connectors
The output is long — it lists every physical display output with all its properties. Scroll up or pipe to less and look for a connector with status connected:
# Quick way: find only the connected connector(s)
sudo modetest -M vc4 -c 2>/dev/null | grep -A 1 "^[0-9]"
Example output on a Pi 4 with one HDMI cable plugged into the second micro-HDMI port:
id encoder status name size (mm) modes encoders
33 0 disconnected HDMI-A-1 0x0 0 32
--
42 41 connected HDMI-A-2 520x290 40 41
In this example: - Connector 33 (HDMI-A-1) is disconnected — nothing plugged in there - Connector 42 (HDMI-A-2) is connected with 40 available modes — this is where the display is
Note the id of the connected connector — you need it below.
Warning
Pi 4 has two micro-HDMI ports (HDMI-A-1 and HDMI-A-2, labeled 0 and 1 on the board). Your cable is probably in one of them. If HDMI-A-1 shows disconnected, check HDMI-A-2 — that is where the display is. A connector with 0 modes and disconnected status has nothing attached.
Note
To see the available modes (resolutions) for your connected connector, run sudo modetest -M vc4 -c without grep and scroll to the connected connector. Each mode line shows resolution, refresh rate, and pixel clock. You can also use a mode by its index: -s 42:#0 uses the first listed mode (usually the preferred/native resolution).
Checkpoint
You have found the connector ID of your connected display. That is all you need to display test patterns. (CRTCs and planes are explored in the tasks at the end.)
3. Display a Test Pattern
Now use modetest to set a mode and show a built-in test pattern. The syntax is:
The -F flag selects the fill pattern. Available patterns: smpte, tiles, plain.
Using the connector ID you found above (replace 46 with your actual connector ID):
# Example: connector 42 (HDMI-A-2), 1920x1080 at 60 Hz, SMPTE color bars
sudo modetest -M vc4 -s 42:1920x1080-60 -F smpte
You should see the classic SMPTE (Society of Motion Picture and Television Engineers) color bar test pattern on your display. Press Enter to exit.
Warning
If the desktop environment is running, it holds the DRM device and modetest will fail with "permission denied" or "resource busy." Stop the desktop first:
Try different patterns:
# Tile pattern — checkerboard with metadata text
sudo modetest -M vc4 -s 42:1920x1080-60 -F tiles
# Plain white — useful to check for dead pixels or backlight uniformity
sudo modetest -M vc4 -s 42:1920x1080-60 -F plain
Try a different resolution (if your display supports it):
Checkpoint
SMPTE color bars are visible on the display. modetest exits cleanly when you press Enter.
Stuck?
- "failed to set mode" — the resolution/refresh combination is not supported by your display. Check available modes with
modetest -M vc4 -c. - "permission denied" — use
sudo. If still failing, another process holds the DRM device (desktop environment, another modetest instance). - "cannot find driver vc4" — try without
-M:sudo modetest -cto list all available drivers and connectors. - Screen flickers then goes black — the mode was set but your monitor does not support it. Wait 10 seconds, modetest will timeout and restore the previous mode.
4. The DRM Display Pipeline
When you ran modetest -s 33:1920x1080-60 -F smpte, it did several things automatically behind the scenes. Understanding this pipeline is the key concept of DRM/KMS:
Connector 33 CRTC 57 Plane 46
(HDMI-A-1) ←── (timing engine) (pixel data)
│ │ │
│ "which port?" │ "what timing?" │ "which pixels?"
│ │ │
▼ ▼ ▼
HDMI signal 1920×1080 @ 60 Hz SMPTE test pattern
to monitor pixel clock, sync in a DRM framebuffer
Why three objects? Because in hardware, these are separate things:
- A Connector is a physical port — it exists whether or not anything is plugged in
-
A CRTC (CRT Controller — the name is historical, from CRT monitor days) is a timing generator inside the SoC. It does two things: (1) generates the pixel clock and sync signals (HSync, VSync) that tell the monitor when each line and frame starts, and (2) reads pixel data from planes and feeds it to the connector as a serial pixel stream. Each CRTC operates independently with its own timing — this is how the Pi can drive two HDMI monitors at different resolutions and refresh rates simultaneously. Without a free CRTC, a connector cannot display anything, even if it is physically connected.
-
A Plane holds the actual pixel data — a buffer in memory. Multiple planes can be stacked (overlays) and the CRTC composites them in hardware. For example, a video player can put the video stream on one plane and the subtitle text on an overlay plane — the CRTC blends them without any CPU work
modetest -s connects all three for you. A real DRM application (compositor, media player, kiosk UI) must set up this pipeline explicitly using ioctl() calls — that is the DRM/KMS API.
5. VSync Page Flipping
The main reason DRM/KMS replaces framebuffer is synchronized display updates. In the framebuffer tutorial, the bouncing square tore because you wrote pixels while the display controller was reading them. DRM solves this with page flipping — you prepare a complete frame in a back buffer, then ask the kernel to swap it at the next VBlank.
Test VSync'd page flipping:
The -v flag enables VSync'd page flipping — modetest allocates two buffers and flips between them synchronized to the display's vertical blanking interval. You will see the pattern appear without tearing. The terminal prints flip timing information.
Compare this to the framebuffer bouncing square — that had visible tearing because /dev/fb0 has no flip mechanism. DRM page flipping guarantees the display controller always reads a complete, consistent frame.
6. Programmatic Drawing: Framebuffer vs DRM
In the Framebuffer Basics tutorial, you already wrote pixels programmatically — fb_draw.py opens /dev/fb0, mmaps it, and writes pixels. That works on any Pi regardless of KMS configuration. Here is how the same task differs when done through DRM:
| Step | Framebuffer (fb_draw.py) |
DRM/KMS |
|---|---|---|
| Open device | open("/dev/fb0", "r+b") |
open("/dev/dri/card0") + ioctl to find connector |
| Get resolution | Read from sysfs (virtual_size) |
Query connector modes (EDID) |
| Get buffer | mmap the device file directly |
Create a "dumb buffer" via ioctl, then mmap it |
| Draw pixels | mm[offset] = packed_pixel |
Same — identical pixel math |
| Display it | Instant — writes go to active scanout | Call set_mode / page flip ioctl |
| VSync | Not available | Page flip waits for VBlank |
| Requires | /dev/fb0 (always present) |
/dev/dri/card0 (needs KMS enabled) |
The pixel packing and offset math (y * stride + x * pixel_bytes) is identical in both cases. The difference is plumbing — how you get the buffer and how you tell the hardware to display it.
What is a "Dumb Buffer"?
A dumb buffer is a plain block of GPU-accessible memory with no hardware acceleration — you write pixels to it with the CPU, just like a framebuffer. It is called "dumb" because the GPU does not render into it (as opposed to a "smart" buffer used by OpenGL/Vulkan). For embedded UIs without 3D graphics, dumb buffers are all you need.
Enabling DRM/KMS on the Raspberry Pi
If ls /dev/dri/ shows "No such file or directory", DRM/KMS is not enabled. The display is using the legacy firmware-driven framebuffer. To enable KMS:
# Check current config
grep dtoverlay /boot/firmware/config.txt
# Enable KMS (add or uncomment this line)
sudo nano /boot/firmware/config.txt
# dtoverlay=vc4-kms-v3d
sudo reboot
# After reboot, verify
ls /dev/dri/ # should show card0, card1, renderD128
For this course, KMS is not required — the framebuffer path works for all tutorials. KMS becomes important when you need VSync, multiple outputs, or hardware planes. The modetest tool can talk to the vc4 driver even without full KMS device nodes.
7. SPI and DSI Displays
So far, everything used the HDMI connector. But many embedded products use smaller displays connected via SPI or DSI instead of HDMI:
| HDMI | DSI (ribbon cable) | SPI (GPIO wires) | |
|---|---|---|---|
| Typical size | 7"–27" | 3.5"–10" | 1.3"–3.5" |
| Resolution | 1920×1080+ | 800×480 typical | 320×240 typical |
| Bandwidth | 5.9 Gbps (HDMI 1.4) | 1.5 Gbps (2 lanes) | 50 Mbps (SPI @ 50 MHz) |
| Rendering path | GPU scan-out (DRM/KMS) | GPU scan-out (DRM/KMS) | CPU pushes pixels over SPI |
| Max practical FPS | 60+ | 60 | 10–20 (bandwidth-limited) |
| DRM device | /dev/dri/card0 |
/dev/dri/card0 |
/dev/fb1 (legacy) or /dev/dri/card1 |
SPI displays are the interesting case for embedded: they are cheap (under $10), tiny, and need only a few wires — but the CPU must copy every pixel through the SPI bus. At 320×240×16 bpp = 153,600 bytes per frame, and SPI running at 50 MHz = 6.25 MB/s, the theoretical maximum is ~40 fps. In practice, overhead reduces this to 10–20 fps.
The kernel driver (fbtft or panel-mipi-dbi) handles the SPI transfer — you write to the framebuffer or DRM buffer, and the driver pushes changed pixels over SPI in the background. From your application's perspective, drawing to an SPI display is identical to drawing to HDMI — same mmap, same pixel math, different device path.
Hands-On
If you have a 3.5" SPI TFT HAT, try the SPI Display tutorial — it uses the same framebuffer techniques from the previous tutorial on a small SPI LCD, and shows the bandwidth limitations in practice.
8. Your Turn
Task A: Find All Modes
List every mode your display supports and answer:
- What is the highest resolution your display reports?
- What is the lowest resolution available?
- How many different refresh rates are listed for 1920x1080 (if any)?
Task B: Framebuffer vs DRM Side-by-Side
Run both approaches and compare:
- Draw something with
fb_draw.py(from the previous tutorial) - Then run
modetestwith SMPTE bars
Answer: What happens to your framebuffer drawing when modetest takes over? Why? (Hint: DRM/KMS takes exclusive control of the display pipeline — it does not share with /dev/fb0.)
Task C: Explore the Pipeline
Run the full dump and investigate your display hardware:
The output is hundreds of lines. Scroll through it (or pipe to less) and answer:
- How many CRTCs does your Pi have? (Look for the
CRTCs:section and count the rows with numeric IDs.) - How many Planes does it have? For each plane, what is its type — Primary, Overlay, or Cursor? (Search for
type:under each plane's properties.) - If a CRTC has 1 primary plane and 2 overlay planes, how many independent image layers can it composite in hardware?
Hint
The type property is buried in each plane's property list. Look for lines like:
value: 1 = Primary, value: 0 = Overlay, value: 2 = Cursor.
Solution Code
A helper script for Task C is available at src/embedded-linux/solutions/drm-kms-test/task_c_pipeline.sh — it parses modetest output and summarizes the pipeline. Try to explore the output yourself first.
What Just Happened?
You used the DRM/KMS API to directly control the display hardware — the same interface that modern compositors (Weston, KWin, Sway) use internally.
| Concept | What You Did |
|---|---|
| Connector | Found the physical HDMI port and its supported modes from EDID |
| Pipeline | Understood the Connector → CRTC → Plane chain that every DRM app must set up |
| Mode setting | Set resolution and refresh rate via -s |
| Test patterns | Used -F smpte/tiles/plain to generate test images |
| Page flipping | Used -v to see tear-free synchronized updates |
| FB vs DRM | Same pixel math — DRM adds mode discovery, VSync, and hardware planes |
| Display types | Compared HDMI, DSI, and SPI — same API, different bandwidth and rendering path |
| Approach | API | VSync | Mode Discovery | Status |
|---|---|---|---|---|
| Framebuffer | /dev/fb0 |
No | Manual (fbset/sysfs) | Deprecated |
| DRM/KMS | /dev/dri/card0 |
Yes (page flip) | Hardware-reported (EDID) | Current standard |
| Wayland | Compositor protocol | Yes (compositor) | Automatic | Full desktop |
Deliverable
- [ ]
modetest -M vc4 -coutput — identified your connector ID, status, and available modes - [ ] SMPTE test pattern displayed on screen via
modetest -s ... -F smpte - [ ] Tested at least two different resolutions or patterns
- [ ] Understand the framebuffer vs DRM drawing comparison (same pixel math, different plumbing)
- [ ] Task A — listed highest/lowest resolution and refresh rates
- [ ] Task C — counted planes and identified their types
Back to Course Overview | Previous: Framebuffer Basics | Next: SDL2 Display →