00 fbdev nedir
Linux framebuffer (fbdev), kullanıcı alanının doğrudan ekran belleğine yazmasına olanak tanıyan eski bir kernel sürücüsüdür. GPU hızlandırması olmadan, CPU piksel verisini doğrudan gönderir.
Tarihsel bağlam
/dev/fb0 karakter cihazı üzerinden erişilir.DRM_FBDEV_EMULATION ile geriye uyumluluk sağlar.Hangi durumlarda fbdev?
Basit, statik display (logo, status bar)?
├─ Evet → fbdev yeterli
└─ Hayır
↓
GPU hızlandırma gerekli?
├─ Evet → DRM/KMS + libdrm veya Qt/LVGL
└─ Hayır → fbdev veya simpledrm
Kernel konfigürasyonu
CONFIG_FB=y
CONFIG_FB_VESA=y # x86 VESA framebuffer
CONFIG_FB_EFI=y # UEFI GOP framebuffer
CONFIG_FB_BCM2708=y # Raspberry Pi legacy fbdev
CONFIG_FRAMEBUFFER_CONSOLE=y # fbcon: kernel konsolu
CONFIG_LOGO=y # boot logo (penguen)
01 ioctl API — fb_var_screeninfo, fb_fix_screeninfo
/dev/fb0 açıldıktan sonra ioctl çağrıları ile ekranın özelliklerini sorgulayabilir ve bazılarını değiştirebilirsin.
fb_var_screeninfo — değiştirilebilir özellikler
xres_virtual/yres_virtual double buffering için sanal çözünürlük.fb_fix_screeninfo — sabit özellikler
xres * bytes_per_pixel'e eşit değildir; hizalama padding olabilir.ioctl örneği
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
int main(void)
{
int fd = open("/dev/fb0", O_RDWR);
if (fd < 0) { perror("/dev/fb0"); return 1; }
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
printf("Çözünürlük : %dx%d\n",
vinfo.xres, vinfo.yres);
printf("Bit/piksel : %d bpp\n",
vinfo.bits_per_pixel);
printf("Satır uzunluğu: %d byte\n",
finfo.line_length);
printf("FB bellek : %d KB\n",
finfo.smem_len / 1024);
printf("Kırmızı : ofset=%d, uzunluk=%d\n",
vinfo.red.offset, vinfo.red.length);
printf("Yeşil : ofset=%d, uzunluk=%d\n",
vinfo.green.offset, vinfo.green.length);
printf("Mavi : ofset=%d, uzunluk=%d\n",
vinfo.blue.offset, vinfo.blue.length);
close(fd);
return 0;
}
02 mmap ile piksel yazma
Framebuffer'a en verimli erişim mmap() ile sağlanır. Kernel belleğini kullanıcı alanına eşler; memcpy gibi standart bellek işlemleri ile piksel yazılır.
Piksel adresi hesabı
offset = y * line_length + x * bytes_per_pixel
/* bytes_per_pixel = bits_per_pixel / 8 */
/* line_length ≥ xres * bytes_per_pixel (padding olabilir!) */
RGB565 piksel yazma — tam örnek
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <stdint.h>
/* RGB888 → RGB565 dönüşümü */
static inline uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8) << 8) |
((g & 0xFC) << 3) |
((b & 0xF8) >> 3);
}
int main(void)
{
int fd = open("/dev/fb0", O_RDWR);
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
int width = vinfo.xres;
int height = vinfo.yres;
int bpp = vinfo.bits_per_pixel / 8; /* byte/piksel */
long fb_size = finfo.line_length * height;
uint8_t *fb = mmap(NULL, fb_size,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
/* Gradyan çiz */
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint16_t color = rgb565(
x * 255 / width, /* kırmızı: soldan sağa */
y * 255 / height, /* yeşil: yukarıdan aşağı */
128 /* mavi: sabit */
);
/* piksel adresine yaz */
long offset = y * finfo.line_length + x * bpp;
*(uint16_t *)(fb + offset) = color;
}
}
sleep(3);
munmap(fb, fb_size);
close(fd);
return 0;
}
ARGB8888 — 32-bit piksel
/* 32-bit piksel: A=31:24, R=23:16, G=15:8, B=7:0 */
uint32_t *pixels32 = (uint32_t *)fb;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
/* line_length/4 kullan (uint32 adreslemesi) */
pixels32[y * (finfo.line_length / 4) + x] =
0xFF000000 | /* A = opak */
((x * 255 / width) << 16) | /* R */
((y * 255 / height) << 8) | /* G */
0x80; /* B */
}
}
03 Double buffering — FBIOPAN_DISPLAY, vsync
Tek buffer'a yazıp göstermek tearing'e yol açar. Double buffering ile bir buffer görüntülenirken diğerine yazılır; VBlank anında tamponlar değiştirilir.
Sanal çözünürlük ile double buffer
#include <linux/fb.h>
struct fb_var_screeninfo vinfo;
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);
/* Dikey sanal çözünürlüğü 2x yap */
vinfo.yres_virtual = vinfo.yres * 2;
ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vinfo);
long fb_size = finfo.line_length * vinfo.yres_virtual;
uint8_t *fb = mmap(NULL, fb_size,
PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
/* Buffer 0: fb[0 ... line_length*yres-1] */
/* Buffer 1: fb[line_length*yres ... fb_size-1] */
int current_buf = 0;
while (rendering) {
int next_buf = 1 - current_buf;
uint8_t *draw_fb = fb + next_buf * finfo.line_length * vinfo.yres;
/* next_buf'a çiz */
render_frame(draw_fb);
/* VBlank'i bekle ve tamponları değiştir */
vinfo.yoffset = next_buf * vinfo.yres;
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo);
/* Opsiyonel: FBIO_WAITFORVSYNC ile VBlank senkronizasyonu */
int zero = 0;
ioctl(fb_fd, FBIO_WAITFORVSYNC, &zero);
current_buf = next_buf;
}
FBIO_WAITFORVSYNC ioctl'i tüm sürücülerde desteklenmeyebilir. Raspberry Pi'nin legacy fbdev sürücüsünde çalışır. Desteklenmiyorsa EINVAL veya ENOIOCTLCMD döner. Modern sistemlerde DRM/KMS atomic commit ile page flip eventi tercih edilir.
04 fbset — timing değiştirme, custom modeline
fbset aracı, framebuffer sürücüsünün timing parametrelerini kullanıcı alanından değiştirmek için kullanılır.
fbset kullanımı
# Mevcut mod bilgisi
fbset -i
# Belirli bir çözünürlük ayarla
fbset -xres 1280 -yres 720 -depth 32
# Refresh rate değiştir
fbset -a 1024x768-75 # /etc/fb.modes'dan
# Özel modeline
fbset \
-xres 800 -yres 480 \
-vxres 800 -vyres 960 \
-depth 16 \
-left 40 -right 40 -upper 29 -lower 13 \
-hslen 48 -vslen 3 \
-pixclock 33333 \
-sync 0 -vmode 0
# Desteklenen tüm modları listele
cat /etc/fb.modes
/etc/fb.modes — mod veritabanı
mode "800x480-60"
geometry 800 480 800 960 16
timings 33333 40 40 29 13 48 3
rgba 5/11,6/5,5/0,0/0
endmode
mode "1024x600-60"
geometry 1024 600 1024 1200 16
timings 15385 140 160 20 12 20 3
rgba 5/11,6/5,5/0,0/0
endmode
Timings değerindeki ilk sayı piksel saati periyodu (pikosaniye). Hesap: pixclock = 1e12 / (Hz * htotal * vtotal). 800x480@60Hz için htotal≈888, vtotal≈525: pixclock = 1e12 / (60 * 888 * 525) ≈ 35818.
05 Splash screen — Plymouth, psplash, BMP decode
Boot splash screen, kullanıcıya sistem hazırlanırken görsel geri bildirim sağlar. Systemd öncesinde fbdev üzerinden gösterilir.
Plymouth — systemd entegreli splash
# Yocto'da plymouth
IMAGE_INSTALL:append = " plymouth plymouth-script"
# Tema seçimi
plymouth-set-default-theme -R bgrt # ya da "spinner", "solar"
# Özel tema
mkdir -p /usr/share/plymouth/themes/my-brand/
cat > /usr/share/plymouth/themes/my-brand/my-brand.plymouth << 'EOF'
[Plymouth Theme]
Name=My Brand
Description=Custom splash
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/my-brand
ScriptFile=/usr/share/plymouth/themes/my-brand/my-brand.script
EOF
psplash — Yocto için minimal splash
# local.conf
IMAGE_INSTALL:append = " psplash"
# Özel logo: 40x40 piksel PNG, RGBA
# psplash-poky-img.h formatına dönüştür
gdk-pixbuf-csource --name=psplash_poky_img \
my_logo.png > psplash-poky-img.h
# psplash kernel cmdline parametresi
# APPEND += " psplash=true"
# İlerleme çubuğu rengi (yüzde 0-100)
psplash-write "PROGRESS 50"
psplash-write "MSG Sistem başlatılıyor..."
C ile BMP decode → framebuffer
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#pragma pack(1)
typedef struct {
uint16_t type; /* "BM" = 0x4D42 */
uint32_t size;
uint16_t reserved1, reserved2;
uint32_t offset; /* piksel verisi başlangıcı */
uint32_t hdr_size;
int32_t width, height;
uint16_t planes, bpp; /* 24 veya 32 */
uint32_t compression;
uint32_t img_size;
int32_t xppm, yppm;
uint32_t clr_used, clr_important;
} BmpHeader;
#pragma pack()
void bmp_to_fb(const char *bmp_path, uint8_t *fb,
int fb_width, int line_length, int fb_bpp)
{
FILE *f = fopen(bmp_path, "rb");
BmpHeader hdr;
fread(&hdr, sizeof(hdr), 1, f);
/* BMP'de satırlar alt-üst sırada, hizalama 4-byte */
int bmp_bpp = hdr.bpp / 8;
int row_size = ((hdr.width * bmp_bpp + 3) / 4) * 4;
uint8_t *row = malloc(row_size);
for (int y = 0; y < hdr.height; y++) {
/* BMP: en alt satır önce */
int fb_y = hdr.height - 1 - y;
fseek(f, hdr.offset + y * row_size, SEEK_SET);
fread(row, 1, row_size, f);
for (int x = 0; x < hdr.width && x < fb_width; x++) {
/* BMP: BGR sırası */
uint8_t b = row[x * bmp_bpp + 0];
uint8_t g = row[x * bmp_bpp + 1];
uint8_t r = row[x * bmp_bpp + 2];
if (fb_bpp == 2) {
/* RGB565 */
uint16_t px = ((r & 0xF8) << 8) |
((g & 0xFC) << 3) |
((b) >> 3);
*(uint16_t *)(fb + fb_y * line_length + x * 2) = px;
} else {
/* ARGB8888 */
uint32_t px = 0xFF000000 | (r << 16) | (g << 8) | b;
*(uint32_t *)(fb + fb_y * line_length + x * 4) = px;
}
}
}
free(row);
fclose(f);
}
06 Python ile framebuffer — Pillow, numpy, video playback
Python'dan framebuffer erişimi hızlı prototipleme için kullanışlıdır. Pillow ile resim yükle, numpy ile piksel manipülasyonu yap, mmap ile direkt yaz.
Temel Python framebuffer yazımı
import mmap
import struct
import fcntl
import ctypes
# fb_var_screeninfo ioctl yapısı (kısaltılmış)
FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
with open('/dev/fb0', 'r+b') as fb:
# Ekran bilgisini al (ilk 8 uint32: xres, yres, xvres, yvres,
# xoffset, yoffset, bpp, grayscale)
vinfo_buf = bytearray(160)
fcntl.ioctl(fb, FBIOGET_VSCREENINFO, vinfo_buf)
xres, yres, _, _, _, _, bpp = struct.unpack_from('7I', vinfo_buf)
finfo_buf = bytearray(68)
fcntl.ioctl(fb, FBIOGET_FSCREENINFO, finfo_buf)
line_length = struct.unpack_from('I', finfo_buf, 16)[0]
fb_size = line_length * yres
fb_mem = mmap.mmap(fb.fileno(), fb_size,
mmap.MAP_SHARED,
mmap.PROT_READ | mmap.PROT_WRITE)
# Kırmızı ekran (RGB565: R=0b11111, G=0, B=0 → 0xF800)
fb_mem.seek(0)
fb_mem.write(b'\x00\xF8' * (fb_size // 2))
Pillow + numpy ile resim gösterme
from PIL import Image
import numpy as np
import mmap, fcntl, struct
def show_image(img_path, fb_path='/dev/fb0'):
with open(fb_path, 'r+b') as fb:
# Ekran bilgisi (xres, yres, bpp al)
vinfo = bytearray(160)
fcntl.ioctl(fb, 0x4600, vinfo)
xres, yres, _, _, _, _, bpp = struct.unpack_from('7I', vinfo)
finfo = bytearray(68)
fcntl.ioctl(fb, 0x4602, finfo)
line_length = struct.unpack_from('I', finfo, 16)[0]
img = Image.open(img_path).convert('RGB')
img = img.resize((xres, yres), Image.LANCZOS)
arr = np.array(img, dtype=np.uint8)
if bpp == 16:
# RGB888 → RGB565
r = (arr[:, :, 0] >> 3).astype(np.uint16)
g = (arr[:, :, 1] >> 2).astype(np.uint16)
b = (arr[:, :, 2] >> 3).astype(np.uint16)
pixels = ((r << 11) | (g << 5) | b).astype(np.uint16)
else:
# ARGB8888
a = np.full((yres, xres), 255, dtype=np.uint8)
pixels = np.stack([arr[:,:,2], arr[:,:,1],
arr[:,:,0], a], axis=2)
fb_mem = mmap.mmap(fb.fileno(), line_length * yres)
fb_mem.seek(0)
fb_mem.write(pixels.tobytes()[:line_length * yres])
show_image('/tmp/splash.png')
Video playback (frame by frame)
import cv2
import numpy as np
import mmap, fcntl, struct, time
def fb_video(video_path, fb_path='/dev/fb0'):
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS) or 25
with open(fb_path, 'r+b') as fb:
vinfo = bytearray(160)
fcntl.ioctl(fb, 0x4600, vinfo)
xres, yres, _, _, _, _, bpp = struct.unpack_from('7I', vinfo)
finfo = bytearray(68)
fcntl.ioctl(fb, 0x4602, finfo)
line_length = struct.unpack_from('I', finfo, 16)[0]
fb_mem = mmap.mmap(fb.fileno(), line_length * yres)
frame_time = 1.0 / fps
while cap.isOpened():
t0 = time.monotonic()
ret, frame = cap.read()
if not ret:
cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # döngü
continue
frame = cv2.resize(frame, (xres, yres))
# BGR → RGB565
r = (frame[:,:,2] >> 3).astype(np.uint16)
g = (frame[:,:,1] >> 2).astype(np.uint16)
b = (frame[:,:,0] >> 3).astype(np.uint16)
px = ((r << 11) | (g << 5) | b).astype(np.uint16)
fb_mem.seek(0)
fb_mem.write(px.tobytes()[:line_length * yres])
# FPS sınırla
elapsed = time.monotonic() - t0
if elapsed < frame_time:
time.sleep(frame_time - elapsed)
cap.release()
07 fbdev → DRM migration
Kernel 5.14+ ile birçok eski fbdev sürücüsünün yerini simpledrm aldı. DRM_FBDEV_EMULATION ise DRM sürücüsü üzerinden /dev/fb0 arayüzü sunar.
simpledrm
CONFIG_DRM_SIMPLEDRM=y
# EFI framebuffer, VESA, platform framebuffer gibi
# basit donanımlar için DRM sürücüsü
# Eski vesafb, efifb'nin yerine geçer
DRM_FBDEV_EMULATION — geriye uyumluluk
CONFIG_DRM_FBDEV_EMULATION=y
# DRM sürücüsü /dev/fb0 arayüzü sağlar
# fbdev uygulamaları kod değişikliği olmadan çalışmaya devam eder
# Kontrol: hangi driver /dev/fb0'ı sunuyor?
cat /sys/class/graphics/fb0/device/uevent
# DRIVER=simpledrm veya DRIVER=vc4 gibi
# DRM cihazı
ls /dev/dri/
# card0 renderD128
# fbdev emulation debug
dmesg | grep "fbdev"
Migration kontrol listesi
FBIOGET_VSCREENINFO, FBIOPAN_DISPLAY emulasyon katmanında desteklenir.CONFIG_FRAMEBUFFER_CONSOLE ile DRM üzerinden çalışmaya devam eder.08 Pratik
Üç gerçek senaryo: Raspberry Pi /dev/fb0 ile pixel art çizimi, C + BMP parser ile boot splash screen ve framebuffer console font değiştirme.
Raspberry Pi — /dev/fb0 ile pixel art
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <stdint.h>
/* 8x8 pixel art sprite (RGB565) */
static const uint16_t smile[8][8] = {
{0,0,0xF800,0xF800,0xF800,0xF800,0,0},
{0,0xF800,0,0,0,0,0xF800,0},
{0xF800,0,0x001F,0,0,0x001F,0,0xF800},
{0xF800,0,0,0,0,0,0,0xF800},
{0xF800,0,0x001F,0,0,0x001F,0,0xF800},
{0xF800,0,0,0x07E0,0x07E0,0,0,0xF800},
{0,0xF800,0,0,0,0,0xF800,0},
{0,0,0xF800,0xF800,0xF800,0xF800,0,0},
};
void draw_sprite(uint8_t *fb, int line_length,
int sx, int sy, int scale)
{
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
uint16_t color = smile[y][x];
/* Ölçeklenmiş piksel bloğu çiz */
for (int dy = 0; dy < scale; dy++) {
for (int dx = 0; dx < scale; dx++) {
long off = (sy + y*scale + dy) * line_length
+ (sx + x*scale + dx) * 2;
*(uint16_t *)(fb + off) = color;
}
}
}
}
}
Boot splash screen — C + BMP parser
# /etc/systemd/system/splash.service
[Unit]
Description=Boot Splash Screen
DefaultDependencies=no
After=sysinit.target
Before=basic.target
[Service]
Type=oneshot
ExecStart=/usr/bin/fb-splash /usr/share/splash.bmp
RemainAfterExit=yes
[Install]
WantedBy=basic.target
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Kullanım: fb-splash <dosya.bmp>\n");
return 1;
}
int fd = open("/dev/fb0", O_RDWR);
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
long fb_size = finfo.line_length * vinfo.yres;
uint8_t *fb = mmap(NULL, fb_size,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
int bpp = vinfo.bits_per_pixel / 8;
bmp_to_fb(argv[1], fb, vinfo.xres,
finfo.line_length, bpp);
munmap(fb, fb_size);
close(fd);
return 0;
}
Framebuffer console font değiştirme
# Mevcut font listesi
ls /usr/share/consolefonts/*.psf*
# Font değiştir (setfont)
setfont /usr/share/consolefonts/Lat2-Terminus16.psf
# Büyük font (HiDPI ekran)
setfont /usr/share/consolefonts/Uni3-Terminus32x16.psf
# Kernel parametresi ile kalıcı (cmdline)
# fbcon=font:VGA8x16
# Yüksek çözünürlüklü ekranda okunabilir font
# Grub GRUB_CMDLINE_LINUX içine ekle:
# "fbcon=font:TER16x32"
# Ekran döndürme (90 derece)
echo 1 > /sys/class/graphics/fbcon/rotate_all
# 0=normal, 1=CCW90, 2=180, 3=CW90