00 DRM/KMS mimarisi
DRM (Direct Rendering Manager), Linux'ta GPU ve display donanımını yöneten kernel alt sistemidir. KMS (Kernel Mode Setting) ise display modunu (çözünürlük, piksel saati) kullanıcı alanından değil kernel içinden ayarlayan katmandır.
Nesneler ve hiyerarşi
GPU/Display Engine
┌─────────────────────────────────────────────┐
│ Framebuffer (FB) ← GEM buffer │
│ ↓ │
│ Plane (Primary / Overlay / Cursor) │
│ ↓ │
│ CRTC (display controller, scan-out) │
│ ↓ │
│ Encoder (HDMI TX / MIPI DSI / LVDS) │
│ ↓ │
│ Connector (HDMI-A-1 / DSI-1 / LVDS-1) │
└─────────────────────────────────────────────┘
Kernel konfigürasyonu
CONFIG_DRM=y
CONFIG_DRM_KMS_HELPER=y
CONFIG_DRM_GEM_DMA_HELPER=y # DMA koherent buffer
CONFIG_DRM_PANEL=y # panel framework
CONFIG_DRM_MIPI_DSI=y # MIPI DSI host
CONFIG_DRM_VC4=y # Raspberry Pi 4 GPU
CONFIG_DRM_IMX=y # i.MX8 display engine
Eski UMS (User Mode Setting) artık kullanılmıyor. Modern DRM sürücülerinin tamamı KMS'i destekler. drm_info ile hangi sürücünün MODESET özelliğine sahip olduğunu doğrulayabilirsin.
01 libdrm API — kaynakları keşfet
libdrm, /dev/dri/card0 cihaz dosyası üzerinden DRM ioctl'lerini saran kullanıcı alanı kütüphanesidir. Bağlantı, kaynakları listeleme ve mod sorgulama için temel fonksiyonları sağlar.
Temel API fonksiyonları
drmModeRes yapısını döner.Ekranları listeleme — tam C örneği
#include <stdio.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
int main(void)
{
int fd = drmOpen("vc4", NULL); /* ya da open("/dev/dri/card0", O_RDWR) */
if (fd < 0) { perror("drmOpen"); return 1; }
drmModeRes *res = drmModeGetResources(fd);
printf("CRTC sayısı : %d\n", res->count_crtcs);
printf("Encoder sayısı: %d\n", res->count_encoders);
printf("Connector say.: %d\n", res->count_connectors);
for (int i = 0; i < res->count_connectors; i++) {
drmModeConnector *conn =
drmModeGetConnector(fd, res->connectors[i]);
const char *state = (conn->connection == DRM_MODE_CONNECTED)
? "BAĞLI" : "bağlı değil";
printf(" Connector %d: %s (%d mod)\n",
conn->connector_id, state, conn->count_modes);
for (int m = 0; m < conn->count_modes; m++) {
drmModeModeInfo *mode = &conn->modes[m];
printf(" [%d] %dx%d @ %d Hz\n",
m, mode->hdisplay, mode->vdisplay,
mode->vrefresh);
}
drmModeFreeConnector(conn);
}
drmModeFreeResources(res);
drmClose(fd);
return 0;
}
gcc -o drm_list drm_list.c $(pkg-config --cflags --libs libdrm)
02 Mode setting — drmModeSetCrtc ve modeline
Ekrana içerik göstermek için framebuffer oluştur, CRTC'yi o framebuffer'a ve seçilen moda bağla.
Modeline anatomisi
Framebuffer oluştur ve modu ayarla
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm/drm_fourcc.h>
#include <sys/mman.h>
/* 1. dumb buffer oluştur */
struct drm_mode_create_dumb creq = {
.width = mode->hdisplay,
.height = mode->vdisplay,
.bpp = 32,
};
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
/* creq.handle = GEM handle, creq.pitch = satır uzunluğu */
/* 2. framebuffer kaydı */
uint32_t fb_id;
drmModeAddFB(fd,
mode->hdisplay, mode->vdisplay,
24, /* depth */
32, /* bpp */
creq.pitch,
creq.handle,
&fb_id);
/* 3. buffer'ı mmap et — piksel yaz */
struct drm_mode_map_dumb mreq = { .handle = creq.handle };
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
uint32_t *pixels = mmap(NULL, creq.size,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset);
/* kırmızı ekran */
for (uint64_t i = 0; i < creq.size / 4; i++)
pixels[i] = 0x00FF0000;
/* 4. CRTC'yi ayarla */
drmModeSetCrtc(fd,
crtc_id,
fb_id,
0, 0, /* x, y offset */
&connector_id, 1, /* connector listesi */
mode); /* drmModeModeInfo* */
Double buffering
İki framebuffer oluştur (fb_id[0] ve fb_id[1]). Birini gösterirken diğerine yaz. drmModeSetCrtc() veya sayfa çevirimi ile aktif tamponu değiştir.
03 Atomic commit — non-blocking sayfa çevirimi
Eski API tek nesneyi değiştirirken, atomic commit tüm display pipeline değişikliklerini tek atomik işlemde uygular. Bu hem tutarlılığı garanti eder hem de non-blocking modda VBlank senkronizasyonu sağlar.
Atomic API adımları
#include <xf86drmMode.h>
/* 1. atomic capability aç */
drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
/* 2. property ID'lerini al (önceden bir kere yap) */
/* drmModeObjectGetProperties() ile CRTC/plane property'lerini sorgula */
/* 3. atomic request oluştur */
drmModeAtomicReqPtr req = drmModeAtomicAlloc();
/* 4. property'leri ekle: nesne tipi | ID | property ID | değer */
drmModeAtomicAddProperty(req,
plane_id,
prop_fb_id, /* "FB_ID" property */
new_fb_id);
drmModeAtomicAddProperty(req,
plane_id,
prop_crtc_id, /* "CRTC_ID" property */
crtc_id);
drmModeAtomicAddProperty(req, plane_id, prop_src_x, 0);
drmModeAtomicAddProperty(req, plane_id, prop_src_y, 0);
drmModeAtomicAddProperty(req, plane_id, prop_src_w, width << 16);
drmModeAtomicAddProperty(req, plane_id, prop_src_h, height << 16);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_x, 0);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_y, 0);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_w, width);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_h, height);
/* 5. commit — non-blocking, page flip event iste */
drmModeAtomicCommit(fd, req,
DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT,
user_data);
drmModeAtomicFree(req);
/* 6. VBlank event bekle */
drmEventContext evctx = {
.version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = on_page_flip,
};
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
select(fd + 1, &fds, NULL, NULL, NULL);
drmHandleEvent(fd, &evctx);
DRM_MODE_ATOMIC_TEST_ONLY bayrağı ile commit'i gerçekten uygulamadan donanımın konfigürasyonu destekleyip desteklemediğini test edebilirsin. Overlay plane formatını önceden doğrulamak için kullanışlıdır.
04 GEM buffer yönetimi — mmap, PRIME ve DMA-BUF
GEM (Graphics Execution Manager), GPU belleğini yöneten kernel nesnesidir. Her GEM nesnesinin bir handle'ı vardır; bu handle fd'ye dönüştürülerek süreçler arası paylaşılabilir (PRIME).
GEM handle ömrü
CREATE_DUMB ioctl → GEM handle (uint32_t)
↓
MAP_DUMB ioctl → mmap offset → mmap() → CPU erişimi
↓
drmModeAddFB() → FB ID (display engine okur)
↓
drmPrimeHandleToFD()→ DMA-BUF fd (başka sürücüye/sürece ver)
PRIME — sürücüler arası sıfır kopya
/* GPU sürücüsünden DMA-BUF fd al */
int dmabuf_fd;
drmPrimeHandleToFD(gpu_fd,
gem_handle,
DRM_CLOEXEC | DRM_RDWR,
&dmabuf_fd);
/* Display sürücüsüne aynı buffer'ı import et */
uint32_t import_handle;
drmPrimeFDToHandle(disp_fd, dmabuf_fd, &import_handle);
/* Artık import_handle üzerinden FB oluşturulabilir */
drmModeAddFB2(disp_fd, width, height,
DRM_FORMAT_ARGB8888,
handles, pitches, offsets,
&fb_id, 0);
dma_buf_export — zero-copy pipeline
GPU render eder → DMA-BUF fd → display engine okur, tek kopya olmadan. V4L2 kamera çıkışını da aynı yolla display'e besleyebilirsin: VIDIOC_EXPBUF → drmPrimeFDToHandle() → drmModeAddFB2().
Modern donanımlar tiled veya compressed format kullanır. drmModeAddFB2WithModifiers() ile DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED gibi modifier'ları belirtebilirsin.
05 KMS plane ve overlay — z-order, blend, scaler
Overlay plane'ler video oynatma, OSD ve cursor için ayrı framebuffer katmanları sunar. Donanım compositor gibi çalışır; CPU karıştırması gerekmez.
Plane türleri
Plane property'leri
modetest -p # tüm plane property'lerini listele
# Tipik property'ler:
# FB_ID — bağlı framebuffer
# CRTC_ID — bağlı CRTC
# SRC_X/Y/W/H — kaynak dikdörtgen (16.16 sabit noktalı)
# CRTC_X/Y/W/H— hedef dikdörtgen (piksel)
# zpos — z sırası (küçük = altta)
# alpha — 0..65535 genel saydamlık
# pixel blend mode — None / Pre-multiplied / Coverage
# rotation — 0 / 90 / 180 / 270 / reflect-x / reflect-y
# COLOR_ENCODING— YUV BT.601 / BT.709 / BT.2020
# COLOR_RANGE — limited / full
YUV overlay örneği (video playback)
/* NV12 (YUV 4:2:0) framebuffer oluştur */
uint32_t handles[4] = { y_handle, uv_handle, 0, 0 };
uint32_t pitches[4] = { width, width, 0, 0 };
uint32_t offsets[4] = { 0, 0, 0, 0 };
drmModeAddFB2(fd, width, height,
DRM_FORMAT_NV12,
handles, pitches, offsets,
&fb_id, 0);
/* overlay plane'e ata — donanım renk dönüşümü yapar */
drmModeAtomicAddProperty(req, overlay_plane_id,
prop_fb_id, fb_id);
drmModeAtomicAddProperty(req, overlay_plane_id,
prop_color_encoding, DRM_COLOR_YCBCR_BT709);
drmModeAtomicAddProperty(req, overlay_plane_id,
prop_color_range, DRM_COLOR_YCBCR_FULL_RANGE);
06 Device tree ve driver — MIPI DSI, LVDS, HDMI
Display alt sistemi, device tree'deki panel ve encoder tanımlamalarına dayanır. Yanlış zamanlama parametresi ya da eksik binding, sürücünün probe etmemesine yol açar.
MIPI DSI panel tanımı
&mipi_dsi {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
panel@0 {
compatible = "jadard,jd9365da"; /* panel sürücüsü */
reg = <0>;
reset-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
backlight = <&backlight>;
dsi-lanes = <4>;
display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <65000000>; /* 65 MHz piksel saati */
hactive = <1024>;
vactive = <600>;
hfront-porch = <160>;
hback-porch = <140>;
hsync-len = <20>;
vfront-porch = <12>;
vback-porch = <20>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <1>;
};
};
};
};
simple-panel ve LVDS
panel: panel {
compatible = "panel-lvds";
width-mm = <203>;
height-mm = <133>;
data-mapping = "jeida-24"; /* veya "vesa-24" */
panel-timing {
clock-frequency = <65000000>;
hactive = <1024>; vactive = <600>;
hfront-porch = <40>; hback-porch = <220>;
vfront-porch = <5>; vback-porch = <20>;
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
panel_in: endpoint {
remote-endpoint = <&lvds_out>;
};
};
};
};
Kernel 5.10+, panel sürücülerini drm_panel_funcs arayüzü üzerinden standartlaştırır. prepare(), enable(), disable(), unprepare() sırası backlight ve reset sinyali sırasını belirler.
07 Debug — modetest, kmscube, drm_info
Display sorunlarını izole etmek için katmanlı debug araçları kullanılır: önce donanım tespiti, sonra basit test, sonra OpenGL ES test.
modetest
# libdrm-tests paketinden gelir
modetest -M vc4 # vc4 modülünü kullan
modetest -p # tüm plane property'leri
modetest -c # connector ve mod listesi
# Test deseni göster: 1920x1080 HDMI-A-1 üzerinde
modetest -M vc4 -s 31:1920x1080 -P 26:1920x1080@XR24
# Belirli bir connector ID ile test
modetest -s <connector_id>:<mode_string>
kmscube — OpenGL ES dönen küp
# GPU + DRM + EGL yığınını bir arada test eder
kmscube -D /dev/dri/card0
# Belirli CRTC ve connector ile
kmscube -D /dev/dri/card0 -M vc4
# NV12 video modunda (video decoder testi)
kmscube -D /dev/dri/card0 -V /dev/video0
drm_info ve debugfs
# JSON formatında tüm DRM nesnelerini döker
drm_info
# debugfs — mevcut CRTC durumu
cat /sys/kernel/debug/dri/0/state
# GPU sürücüsüne özel debugfs (vc4)
ls /sys/kernel/debug/dri/0/
# bo_stats crtc0 gem_names name state vc4_hdmi ...
# framebuffer listesi
cat /sys/kernel/debug/dri/0/framebuffer
# EDID dump
cat /sys/kernel/debug/dri/0/HDMI-A-1/edid_override
08 Pratik
Üç gerçek dünya senaryosu: Raspberry Pi 4 HDMI libdrm örneği, i.MX8 MIPI DSI panel bring-up ve triple buffering ile tear-free render.
Raspberry Pi 4 — libdrm dumb buffer ile gradyan
/* /dev/dri/card0 = vc4 sürücüsü */
int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
drmSetMaster(fd); /* gerekli: modeset izni */
drmModeRes *res = drmModeGetResources(fd);
/* ilk bağlı connector'ı bul */
for (int i = 0; i < res->count_connectors; i++) {
drmModeConnector *c = drmModeGetConnector(fd, res->connectors[i]);
if (c->connection == DRM_MODE_CONNECTED && c->count_modes > 0) {
connector = c;
break;
}
}
/* preferred modu seç (ilk mod genellikle en yüksek çözünürlük) */
drmModeModeInfo mode = connector->modes[0];
/* dumb buffer + FB + mmap + renk gradyanı ... */
for (int y = 0; y < mode.vdisplay; y++)
for (int x = 0; x < mode.hdisplay; x++)
pixels[y * pitch/4 + x] =
((y * 255 / mode.vdisplay) << 16) | /* R */
((x * 255 / mode.hdisplay) << 8); /* G */
drmModeSetCrtc(fd, crtc_id, fb_id, 0, 0,
&connector->connector_id, 1, &mode);
sleep(5);
drmModeSetCrtc(fd, crtc_id, 0, 0, 0, NULL, 0, NULL); /* temizle */
i.MX8 MIPI DSI panel bring-up adımları
# 1. Sürücünün probe ettiğini doğrula
dmesg | grep -E "imx-drm|mipi-dsi|panel"
# 2. Connector durumu
drm_info | grep -A5 connector
# 3. Panel güç sırasını izle (GPIO reset)
cat /sys/kernel/debug/gpio
# 4. MIPI DSI register dump (sürücüye özgü)
cat /sys/kernel/debug/dri/1/imx-drm/dsi0
# 5. Basit test deseni
modetest -M imx-drm -s <conn_id>:1024x600
Triple buffering ile tear-free render döngüsü
/* 3 framebuffer: fb[0], fb[1], fb[2] */
int display_idx = 0; /* şu an ekranda */
int render_idx = 1; /* şu an render edilen */
int queued_idx = 2; /* queued (bir sonraki flip) */
while (running) {
/* render_idx'e çiz (CPU/GPU) */
render_frame(fb[render_idx]);
/* queued_idx'i atomik flip için gönder */
atomic_flip(fb[render_idx]);
/* page flip eventi bekle */
wait_vblank_event(); /* drmHandleEvent() */
/* indeksleri döndür */
int tmp = display_idx;
display_idx = queued_idx;
queued_idx = render_idx;
render_idx = tmp;
}
/* Sonuç: display ve render hiçbir zaman aynı buffer'ı paylaşmaz */