Grafik & Görüntü
TEKNİK REHBER GRAFİK & GÖRÜNTÜ DRM/KMS 2026

DRM/KMS —
kernel mode setting.

libdrm ile display pipeline'ı doğrudan yönet. CRTC, encoder, connector ve plane hiyerarşisini anla; atomic commit ile tear-free sayfa çevirimi yap; GEM ve DMA-BUF ile zero-copy grafik pipeline kur.

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)     │
  └─────────────────────────────────────────────┘
    
CRTC
Display controller — framebuffer'ı tarar ve piksel saatiyle senkronize sinyal üretir. Bir CRTC birden fazla plane'i birleştirebilir.
Encoder
CRTC çıkışını HDMI, MIPI DSI, LVDS gibi fiziksel sinyale dönüştürür. Tek CRTC'ye birden fazla encoder bağlanabilir (mirror/clone).
Connector
Fiziksel konnektör — HDMI-A-1, DP-1, DSI-1. EDID okur, desteklenen modları bildirir, hot-plug algılar.
Plane
Primary (zorunlu), Overlay (opsiyonel bindirme) ve Cursor (fare imleci) olmak üzere 3 türü vardır. Her plane bağımsız framebuffer ve pozisyon içerir.
Framebuffer
GEM handle'a bağlı piksel tamponu. Genişlik, yükseklik, piksel formatı (DRM_FORMAT_*) ve pitch (satır uzunluğu) bilgilerini taşır.

Kernel konfigürasyonu

Kconfig
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
UDL vs MODESET

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ı

drmOpen()
DRM cihazını açar. İlk parametre sürücü adı ("vc4", "imx-drm"), ikincisi NULL. Başarısızlıkta -1 döner.
drmModeGetResources()
CRTC, encoder ve connector ID listelerini içeren drmModeRes yapısını döner.
drmModeGetConnector()
Belirli bir connector'ın durumunu (bağlı/bağlı değil), desteklenen mod listesini ve EDID bilgisini döner.
drmModeGetCrtc()
Mevcut CRTC konfigürasyonunu döner: aktif mod, framebuffer ID ve x/y offset.
drmModeFreeResources()
Tüm Get* fonksiyonlarına karşılık gelen Free* fonksiyonları belleği serbest bırakır.

Ekranları listeleme — tam C örneği

drm_list.c
#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;
}
Derleme
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

clock
Piksel saati (kHz). 1920x1080@60Hz için ~148500 kHz.
hdisplay / vdisplay
Aktif piksel bölgesi. Gerçek çözünürlük burada.
hsync_start/end, htotal
Yatay senkronizasyon darbesinin başlangıç, bitiş ve toplam piksel sayısı.
vrefresh
Saniyedeki kare sayısı (Hz). Hesaplanmış değer: clock / (htotal * vtotal).

Framebuffer oluştur ve modu ayarla

C — mode set
#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ı

C — atomic commit
#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

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

C — PRIME export/import
/* 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_EXPBUFdrmPrimeFDToHandle()drmModeAddFB2().

DRM_FORMAT_MOD_*

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

DRM_PLANE_TYPE_PRIMARY
Ana içerik katmanı. Her CRTC için en az bir primary plane zorunludur.
DRM_PLANE_TYPE_OVERLAY
Bindirme katmanı. Video, grafik UI veya kamera önizlemesi için kullanılır. Sayısı donanıma göre değişir.
DRM_PLANE_TYPE_CURSOR
Fare imleci için optimize edilmiş, genellikle 64×64 piksel sınırlı özel katman.

Plane property'leri

bash — plane property listesi
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)

C — NV12 overlay
/* 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ı

DTS — MIPI DSI panel
&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

DTS — LVDS simple-panel
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>;
            };
        };
    };
};
drm-panel framework

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

bash
# 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

bash
# 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

bash
# 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

rpi4_drm.c (özet)
/* /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ı

bash — bring-up kontrol listesi
# 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ü

C — render döngüsü (sözde kod)
/* 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 */