프레임버퍼 서브시스템 (fbdev)

리눅스 커널 프레임버퍼(fbdev) 서브시스템을 심층 분석합니다. fb_info·fb_ops 핵심 구조체, fb_var_screeninfo/fb_fix_screeninfo 화면 정보, 픽셀 포맷과 색상 맵, mmap 메모리 매핑, cfb/sys 헬퍼를 통한 하드웨어 가속, 더블 버퍼링과 vsync, fbcon 콘솔 서브시스템, fb_deferred_io 지연 I/O, efifb/vesafb/simplefb 부트 타임 프레임버퍼, 사용자 공간 ioctl 인터페이스, DRM/KMS와의 비교, 최소 fbdev 드라이버 작성 가이드까지 /dev/fb0의 모든 것을 다룹니다.

전제 조건: 디바이스 드라이버캐릭터 디바이스 문서를 먼저 읽으세요. fbdev 드라이버는 플랫폼 디바이스로 등록되며, /dev/fb0는 캐릭터 디바이스(major 29)입니다. 메모리 매핑을 이해하려면 메모리 관리 기초도 함께 보시면 좋습니다.
일상 비유: 프레임버퍼는 공유 화이트보드와 비슷합니다. 누구나(사용자 공간 프로세스) 직접 마커로 그림을 그릴 수 있고, 하드웨어는 지속적으로 보드를 스캔하여 화면에 표시합니다. "페이지 뒤집기"는 더블 버퍼링이고, "보드 규격 변경"은 해상도·색상 변경입니다.

핵심 요약

  • fb_info — 프레임버퍼 디바이스 하나를 대표하는 최상위 구조체. 화면 정보·콜백·메모리 주소를 모두 담고 있습니다.
  • fb_ops — 드라이버가 구현하는 콜백 함수 집합. 그리기, 팬 디스플레이, 모드 설정 등 14개 함수 포인터입니다.
  • fb_var / fb_fix_screeninfo — 가변(해상도·색상)과 고정(메모리 주소·라인 길이) 화면 정보를 분리하여 관리합니다.
  • fbcon — 프레임버퍼 위에서 동작하는 커널 콘솔. VT(가상 터미널)의 텍스트를 픽셀로 렌더링합니다.
  • /dev/fb0 — 사용자 공간에서 프레임버퍼에 접근하는 캐릭터 디바이스. mmap으로 직접 픽셀을 조작할 수 있습니다.

단계별 이해

  1. 구조체 이해
    fb_info가 중심이고, 그 안에 fb_var_screeninfo(가변 정보), fb_fix_screeninfo(고정 정보), fb_ops(콜백)가 연결됩니다.
  2. 콜백 구현
    최소한 fb_check_var·fb_set_par·fb_setcolreg와 그리기 함수를 구현합니다.
  3. 메모리 설정
    비디오 메모리를 할당하고 screen_base/fix.smem_start/fix.smem_len을 설정합니다.
  4. 디바이스 등록
    register_framebuffer()로 등록하면 /dev/fbN이 생성되고, fbcon이 자동으로 연결됩니다.

프레임버퍼 개요

프레임버퍼(framebuffer)는 디스플레이 하드웨어에 표시될 픽셀 데이터를 저장하는 메모리 영역입니다. 리눅스 커널의 fbdev 서브시스템(drivers/video/fbdev/)은 이 메모리 영역에 대한 통일된 인터페이스를 제공하여, 다양한 디스플레이 하드웨어를 하나의 API로 접근할 수 있게 합니다.

역사와 배경

fbdev는 Linux 2.1.x(1997년)에 처음 도입되었습니다. 당시 X Window System 없이도 그래픽 출력이 필요한 임베디드 시스템과, 다양한 아키텍처(m68k, SPARC, PowerPC 등)의 디스플레이를 통일하려는 목적이었습니다. Martin Schaller가 최초 설계를 하고, Geert Uytterhoeven이 Amiga와 Atari 프레임버퍼를 구현하며 프레임워크를 확립했습니다.

디바이스 모델

fbdev는 캐릭터 디바이스(major 29)로 등록됩니다. 각 프레임버퍼 디바이스는 /dev/fb0, /dev/fb1, ... 으로 나타나며, 최대 FB_MAX(기본 32)개까지 지원합니다. 사용자 공간 프로그램은 open()/ioctl()/mmap()/close()로 프레임버퍼에 접근합니다.

User Space Kernel (VFS) fbdev Core Hardware Application fbset / cat /dev/fb0 DRM userspace (대안) /dev/fb0 (major 29) fb_info fb_ops fbcon fbmem.c (코어 디스패치) Display Controller Video RAM DRM/KMS (대안)

진화와 현재 상태

fbdev는 2000년대 중반까지 리눅스의 주요 그래픽 인터페이스였지만, DRM/KMS의 등장으로 "레거시" 인터페이스로 분류됩니다. 그러나 다음 영역에서는 여전히 활발히 사용됩니다:

레거시 경고: 새로운 디스플레이 드라이버를 작성할 때는 DRM/KMS(GPU 서브시스템)를 우선 고려하세요. fbdev는 하드웨어 가속, 다중 디스플레이, 원자적 모드 설정 등에서 구조적 한계가 있습니다. 단, SPI/I2C 소형 디스플레이나 부트 콘솔 등 단순한 용도에서는 fbdev가 여전히 적합할 수 있습니다.

소스 디렉토리 구조

경로설명
drivers/video/fbdev/core/fbdev 코어 (fbmem.c, fbcon.c, fb_defio.c 등)
drivers/video/fbdev/개별 fbdev 드라이버 (vfb.c, efifb.c, vesafb.c 등)
include/linux/fb.h커널 내부 fbdev 헤더 (fb_info, fb_ops 등)
include/uapi/linux/fb.h사용자 공간 API (ioctl 번호, 구조체)
Documentation/fb/공식 커널 문서 (framebuffer.rst, api.rst, fbcon.rst 등)
소스 규모: drivers/video/fbdev/ 디렉토리에는 약 200개 이상의 드라이버가 포함되어 있으며, 코어 프레임워크(core/)는 fbmem.c(~1,800줄), fbcon.c(~3,500줄), fb_defio.c(~300줄) 등으로 구성됩니다.

fbmem.c 내부 디스패치 흐름

fbmem.c는 fbdev 서브시스템의 핵심 파일로, /dev/fbN 캐릭터 디바이스의 file_operations를 구현합니다. 사용자 공간의 시스템 콜이 도착하면, 등록된 fb_info를 찾아 해당 fb_ops 콜백으로 디스패치합니다.

fbmem.c ioctl 디스패치 흐름 ioctl(fd, FBIOPUT_VSCREENINFO, &var) VFS → fb_ioctl() [fbmem.c] registered_fb[minor] → fb_info 검색 switch (cmd) — ioctl 명령 분기 FBIOPUT_VSCREENINFO → fb_set_var() FBIOPAN_DISPLAY → fb_pan_display() FBIOBLANK → fb_blank() 기타 (드라이버) → fb_ops->fb_ioctl() 1. fb_check_var() 2. fb_set_par() 3. fb_set_cmap() 4. fbcon 통보
/* fbmem.c — 핵심 file_operations 구조 (단순화) */
static const struct file_operations fb_fops = {
    .owner   = THIS_MODULE,
    .read    = fb_read,        /* fb_ops->fb_read 또는 기본 구현 */
    .write   = fb_write,       /* fb_ops->fb_write 또는 기본 구현 */
    .unlocked_ioctl = fb_ioctl,  /* ioctl 디스패치 */
    .mmap    = fb_mmap,        /* fb_ops->fb_mmap 또는 기본 구현 */
    .open    = fb_open,        /* fb_ops->fb_open */
    .release = fb_release,     /* fb_ops->fb_release */
};

/* 등록된 프레임버퍼 배열 — minor 번호로 인덱싱 */
struct fb_info *registered_fb[FB_MAX];   /* 최대 32개 */
int num_registered_fb;                    /* 현재 등록된 수 */
fb_set_var() 내부 순서: FBIOPUT_VSCREENINFO ioctl이 도착하면 fb_set_var()가 호출되며, 내부적으로 (1) fb_check_var()로 파라미터 검증 → (2) fb_set_par()로 하드웨어 설정 → (3) fb_set_cmap()으로 색상 맵 갱신 → (4) fbcon_event_notify()로 fbcon에 모드 변경 통보 순서로 진행됩니다. 어느 단계에서든 오류가 발생하면 이전 상태로 롤백합니다.

fb_notifier — 이벤트 통보 체인

fbdev는 fb_notifier_call_chain()을 통해 프레임버퍼 상태 변화를 커널의 다른 서브시스템에 알립니다. fbcon이 이 메커니즘을 사용하여 모드 변경, 블랭킹, 프레임버퍼 등록/해제 등을 감지합니다.

이벤트시점주요 수신자
FB_EVENT_MODE_CHANGE해상도/모드 변경 후fbcon (레이아웃 재계산)
FB_EVENT_BLANK블랭킹 상태 변경fbcon, 백라이트
FB_EVENT_FB_REGISTERED새 프레임버퍼 등록fbcon (바인딩)
FB_EVENT_FB_UNREGISTERED프레임버퍼 해제fbcon (분리)
FB_EVENT_SUSPEND시스템 절전 진입fbcon (출력 중단)
FB_EVENT_RESUME시스템 절전 복귀fbcon (출력 재개)

fb_info 핵심 구조체

struct fb_info는 fbdev 서브시스템의 중심 구조체입니다. 프레임버퍼 디바이스 하나당 하나의 fb_info 인스턴스가 존재하며, 화면 정보, 드라이버 콜백, 비디오 메모리 주소, 색상 맵, 디바이스 참조 등 모든 것을 담고 있습니다.

fb_info 구조체 정의

/* include/linux/fb.h */
struct fb_info {
    /* --- 기본 정보 --- */
    int node;                          /* /dev/fbN의 N (minor 번호) */
    int flags;                         /* FBINFO_* 플래그 */

    /* --- 화면 정보 --- */
    struct fb_var_screeninfo var;      /* 가변 화면 정보 (해상도, 색상, 타이밍) */
    struct fb_fix_screeninfo fix;      /* 고정 화면 정보 (메모리 주소, 라인 길이) */

    /* --- 색상 맵 --- */
    struct fb_cmap cmap;               /* 현재 색상 맵 (팔레트) */
    u32 *pseudo_palette;               /* 트루컬러용 의사 팔레트 (16 엔트리) */

    /* --- 드라이버 콜백 --- */
    const struct fb_ops *fbops;        /* 드라이버 오퍼레이션 함수 포인터 */

    /* --- 비디오 메모리 --- */
    char __iomem *screen_base;         /* 가상 주소: CPU가 접근하는 VRAM 시작 */
    unsigned long screen_size;         /* VRAM 크기 (0이면 fix.smem_len 사용) */

    /* --- 커널 내부 --- */
    struct device *device;             /* 부모 디바이스 (platform_device 등) */
    struct device *dev;                /* fb_info 자체의 device */

    /* --- Deferred I/O --- */
    struct fb_deferred_io *fbdefio;    /* Deferred I/O 설정 */

    /* --- 드라이버 개인 데이터 --- */
    void *par;                         /* 드라이버별 사적 데이터 */

    /* ... (일부 필드 생략) ... */
};
fb_info 프레임버퍼 디바이스 중심 구조체 fb_var_screeninfo 해상도, 색상, 타이밍 fb_fix_screeninfo 메모리 주소, 라인 길이 fb_ops 드라이버 콜백 (14개) screen_base VRAM 가상 주소 (iomem) pseudo_palette 트루컬러 16-엔트리 팔레트 fb_cmap 색상 맵 (팔레트 모드)

fb_var_screeninfo — 가변 화면 정보

"가변"이라는 이름처럼, 사용자 공간에서 FBIOPUT_VSCREENINFO ioctl로 런타임에 변경할 수 있는 정보입니다.

/* include/uapi/linux/fb.h */
struct fb_var_screeninfo {
    /* --- 가시 해상도 --- */
    __u32 xres;                /* 가시 영역 가로 픽셀 수 */
    __u32 yres;                /* 가시 영역 세로 픽셀 수 */

    /* --- 가상 해상도 (더블 버퍼링용) --- */
    __u32 xres_virtual;        /* 가상 가로 (>= xres) */
    __u32 yres_virtual;        /* 가상 세로 (>= yres, 2배면 더블 버퍼) */

    /* --- 오프셋 (팬 디스플레이) --- */
    __u32 xoffset;             /* 가시 영역의 가상 내 X 오프셋 */
    __u32 yoffset;             /* 가시 영역의 가상 내 Y 오프셋 */

    /* --- 색상 깊이 --- */
    __u32 bits_per_pixel;      /* 픽셀당 비트 수 (8, 16, 24, 32 등) */
    __u32 grayscale;           /* 0 = 컬러, 1 = 그레이스케일 */

    /* --- 색상 필드 (비트 레이아웃) --- */
    struct fb_bitfield red;    /* 빨강 채널: offset, length, msb_right */
    struct fb_bitfield green;  /* 초록 채널 */
    struct fb_bitfield blue;   /* 파랑 채널 */
    struct fb_bitfield transp; /* 알파/투명도 채널 */

    /* --- 하드웨어 가속 --- */
    __u32 nonstd;              /* 비표준 픽셀 포맷 */
    __u32 activate;            /* FB_ACTIVATE_* 플래그 */
    __u32 accel_flags;         /* 가속 플래그 (미사용) */

    /* --- 타이밍 (CRTC) --- */
    __u32 pixclock;            /* 픽셀 클럭 (ps 단위) */
    __u32 left_margin;         /* HBP (수평 뒷 포치) */
    __u32 right_margin;        /* HFP (수평 앞 포치) */
    __u32 upper_margin;        /* VBP (수직 뒷 포치) */
    __u32 lower_margin;        /* VFP (수직 앞 포치) */
    __u32 hsync_len;           /* 수평 동기 펄스 길이 */
    __u32 vsync_len;           /* 수직 동기 펄스 길이 */
    __u32 sync;                /* FB_SYNC_* 플래그 */
    __u32 vmode;               /* FB_VMODE_* (인터레이스, 더블스캔 등) */

    __u32 rotate;              /* 회전 각도 (0, 90, 180, 270) */
    __u32 colorspace;          /* 색상 공간 */
    __u32 reserved[4];         /* 예약 */
};

fb_fix_screeninfo — 고정 화면 정보

"고정"은 드라이버가 초기화 시 설정하고, 사용자 공간에서는 읽기만 가능하다는 의미입니다.

/* include/uapi/linux/fb.h */
struct fb_fix_screeninfo {
    char id[16];               /* 디바이스 식별 문자열 (예: "VESA VGA") */
    unsigned long smem_start;  /* 프레임버퍼 물리 시작 주소 */
    __u32 smem_len;            /* 프레임버퍼 메모리 크기 (바이트) */
    __u32 type;                /* FB_TYPE_* (PACKED_PIXELS, PLANES 등) */
    __u32 type_aux;            /* 인터리브 등 추가 타입 정보 */
    __u32 visual;              /* FB_VISUAL_* (TRUECOLOR, PSEUDOCOLOR 등) */
    __u16 xpanstep;            /* X 팬 단위 (0이면 팬 불가) */
    __u16 ypanstep;            /* Y 팬 단위 */
    __u16 ywrapstep;           /* Y 래핑 단위 */
    __u32 line_length;         /* 한 줄의 바이트 수 (stride) */
    unsigned long mmio_start;  /* MMIO 레지스터 물리 주소 */
    __u32 mmio_len;            /* MMIO 크기 */
    __u32 accel;               /* FB_ACCEL_* (가속 하드웨어 타입) */
    __u16 capabilities;        /* FB_CAP_* (fourcc 지원 등) */
    __u16 reserved[2];         /* 예약 */
};

fb_info 생명주기

fb_info는 다음 순서로 생성·등록·해제됩니다:

  1. framebuffer_alloc(size, dev)fb_info + par(사적 데이터) 할당
  2. 드라이버가 var, fix, fbops, screen_base 등을 설정
  3. register_framebuffer(info)/dev/fbN 생성, fbcon 연결
  4. (운영 중 — ioctl, mmap, fbcon 등이 fb_ops 호출)
  5. unregister_framebuffer(info) — 디바이스 제거
  6. framebuffer_release(info) — 메모리 해제
framebuffer_alloc() 사용 필수: fb_info를 직접 kzalloc()으로 할당하지 마세요. framebuffer_alloc()은 디바이스 모델 연결, 레퍼런스 카운팅, par 정렬 등을 올바르게 처리합니다. 첫 번째 인자 size는 드라이버 사적 데이터(info->par)의 크기입니다.
fb_info 생명주기 1. framebuffer_alloc() fb_info + par 메모리 할당 2. 드라이버 설정 var, fix, fbops, screen_base 3. register_framebuffer() /dev/fbN 생성, fbcon 연결 4. 운영 중 ioctl, mmap, fbcon 5. unregister_framebuffer() fbcon 분리, /dev/fbN 제거 6. framebuffer_release() fb_info 메모리 해제 register_framebuffer() 내부 단계 1. fb_check_foreignness() — 엔디안 검사 2. registered_fb[i] = info — 전역 배열 등록 3. device_create() — sysfs 디바이스 생성 4. fb_notifier → FB_EVENT_FB_REGISTERED

FBINFO_* 플래그

fb_info.flags 필드는 프레임버퍼의 능력과 상태를 나타내는 비트 플래그 조합입니다. 드라이버는 probe 시 적절한 플래그를 설정해야 합니다.

플래그의미
FBINFO_DEFAULT0기본값 (특별한 기능 없음)
FBINFO_HWACCEL_XPAN0x1000수평 팬 하드웨어 가속
FBINFO_HWACCEL_YPAN0x2000수직 팬 하드웨어 가속 (더블 버퍼링)
FBINFO_HWACCEL_YWRAP0x4000Y 방향 래핑 가속
FBINFO_HWACCEL_COPYAREA0x0100copyarea 하드웨어 가속
FBINFO_HWACCEL_FILLRECT0x0200fillrect 하드웨어 가속
FBINFO_HWACCEL_IMAGEBLIT0x0400imageblit 하드웨어 가속
FBINFO_READS_FAST0x0080VRAM 읽기가 빠름 (sys 헬퍼 불필요)
FBINFO_VIRTFB0x0004가상 프레임버퍼 (실제 HW 없음)
FBINFO_PARTIAL_PAN_OK0x0040부분 팬 허용 (xpanstep 단위)
FBINFO_MISC_TILEBLITTING0x20000타일 블리팅 지원 (텍스트 모드)
가속 플래그와 fbcon: FBINFO_HWACCEL_COPYAREA를 설정하면 fbcon이 스크롤 시 fb_copyarea()를 적극 활용합니다. 반대로 이 플래그가 없으면 fbcon은 소프트웨어 스크롤(SCROLL_REDRAW)을 선택할 수 있습니다. FBINFO_HWACCEL_YPAN은 fbcon에게 SCROLL_PAN 모드를 사용하도록 힌트를 줍니다.

fb_ops 콜백 함수

struct fb_ops는 fbdev 드라이버가 구현하는 함수 포인터 집합입니다. fbmem.c의 파일 오퍼레이션이 사용자 요청을 받으면, 해당 fb_ops 콜백을 호출하여 실제 하드웨어를 제어합니다.

fb_ops 구조체 정의

/* include/linux/fb.h */
struct fb_ops {
    struct module *owner;

    /* --- 디스플레이 제어 --- */
    int (*fb_open)(struct fb_info *info, int user);
    int (*fb_release)(struct fb_info *info, int user);
    int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
    int (*fb_set_par)(struct fb_info *info);
    int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
                        unsigned blue, unsigned transp, struct fb_info *info);
    int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
    int (*fb_blank)(int blank, struct fb_info *info);
    int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

    /* --- 그리기 (가속) --- */
    void (*fb_fillrect)(struct fb_info *info, const struct fb_fillrect *rect);
    void (*fb_copyarea)(struct fb_info *info, const struct fb_copyarea *area);
    void (*fb_imageblit)(struct fb_info *info, const struct fb_image *image);

    /* --- I/O --- */
    ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
                       size_t count, loff_t *ppos);
    ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
                        size_t count, loff_t *ppos);
    int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

    /* --- ioctl --- */
    int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, unsigned long arg);

    /* --- 커서 (선택) --- */
    int (*fb_cursor)(struct fb_info *info, struct fb_cursor *cursor);

    /* --- 동기화 --- */
    int (*fb_sync)(struct fb_info *info);

    /* --- 디버그 --- */
    void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
                        struct fb_var_screeninfo *var);
    void (*fb_destroy)(struct fb_info *info);
};

디스플레이 제어 콜백

콜백호출 시점역할필수
fb_check_varFBIOPUT_VSCREENINFO요청된 var 검증·조정 (하드웨어 변경 없음)권장
fb_set_parcheck_var 통과 후실제 하드웨어 모드 설정권장
fb_setcolreg색상 레지스터 설정팔레트/pseudo_palette 하나의 엔트리 설정권장
fb_setcmapFBIOPUTCMAP전체 색상 맵 일괄 설정선택
fb_blankFBIOBLANK화면 끄기/켜기 (DPMS)선택
fb_pan_displayFBIOPAN_DISPLAY가시 영역 이동 (더블 버퍼 플립)선택
fb_openopen()디바이스 열기 시 초기화선택
fb_releaseclose()디바이스 닫기 시 정리선택

그리기 콜백

fbcon과 사용자 공간의 그리기 요청을 처리합니다. 하드웨어 가속이 없으면 커널 헬퍼(cfb_* 또는 sys_*)를 사용합니다.

콜백역할cfb 헬퍼sys 헬퍼
fb_fillrect사각형 채우기cfb_fillrect()sys_fillrect()
fb_copyarea영역 복사 (스크롤)cfb_copyarea()sys_copyarea()
fb_imageblit이미지 전송 (글꼴 렌더링)cfb_imageblit()sys_imageblit()

I/O 콜백

/* 최소 fb_ops 구현 예제 */
static const struct fb_ops myfb_ops = {
    .owner          = THIS_MODULE,
    .fb_check_var   = myfb_check_var,
    .fb_set_par     = myfb_set_par,
    .fb_setcolreg   = myfb_setcolreg,
    .fb_blank       = myfb_blank,
    .fb_pan_display = myfb_pan_display,
    /* 그리기: cfb 헬퍼 (I/O 메모리 + 하드웨어 가속 없는 경우) */
    .fb_fillrect    = cfb_fillrect,
    .fb_copyarea    = cfb_copyarea,
    .fb_imageblit   = cfb_imageblit,
    /* mmap은 기본 fbmem.c 구현 사용 (커스텀 불필요 시 생략 가능) */
};
fb_check_var 주의: 이 콜백에서는 하드웨어 상태를 절대 변경하면 안 됩니다. 파라미터를 검증하고 필요하면 조정(예: 지원하지 않는 해상도를 가장 가까운 값으로)만 해야 합니다. 실제 하드웨어 변경은 fb_set_par에서 수행하세요.

fb_blank — DPMS 전원 레벨

fb_blank 콜백은 디스플레이 전원 관리(DPMS)를 제어합니다. 5단계 블랭킹 레벨이 정의되어 있으며, 에너지 절약 수준이 높아질수록 디스플레이 복원 시간이 길어집니다.

레벨상수H-SyncV-Sync화면복원 시간
화면 켜짐FB_BLANK_UNBLANK0ONONON즉시
일반 블랭크FB_BLANK_NORMAL1ONONOFF (검은색)즉시
HSYNC OFFFB_BLANK_HSYNC_SUSPEND2OFFONOFF짧음
VSYNC OFFFB_BLANK_VSYNC_SUSPEND3ONOFFOFF중간
완전 꺼짐FB_BLANK_POWERDOWN4OFFOFFOFF길음
DPMS 블랭킹 레벨 (절전 수준 증가 →) UNBLANK 화면 켜짐 NORMAL 검은색 화면 HSYNC_SUSP HSYNC OFF VSYNC_SUSP VSYNC OFF POWERDOWN 완전 꺼짐 전력 소모: 높음 전력 소모: 최소 절전 수준 증가 FB_BLANK_UNBLANK으로 복원

그리기 구조체 상세

fb_fillrect, fb_copyarea, fb_imageblit 콜백은 각각 전용 파라미터 구조체를 받습니다:

/* fb_fillrect — 사각형 채우기 파라미터 */
struct fb_fillrect {
    __u32 dx;      /* 대상 X 좌표 */
    __u32 dy;      /* 대상 Y 좌표 */
    __u32 width;   /* 채울 너비 */
    __u32 height;  /* 채울 높이 */
    __u32 color;   /* 채울 색상 (pseudo_palette 인덱스 또는 직접 값) */
    __u32 rop;     /* 래스터 연산: ROP_COPY(0), ROP_XOR(1) */
};

/* fb_copyarea — 영역 복사 파라미터 (스크롤 시 사용) */
struct fb_copyarea {
    __u32 dx;      /* 대상 X */
    __u32 dy;      /* 대상 Y */
    __u32 width;   /* 복사 너비 */
    __u32 height;  /* 복사 높이 */
    __u32 sx;      /* 소스 X */
    __u32 sy;      /* 소스 Y */
};

/* fb_image — 이미지 전송 파라미터 (글꼴 렌더링 시 사용) */
struct fb_image {
    __u32 dx;         /* 대상 X */
    __u32 dy;         /* 대상 Y */
    __u32 width;      /* 이미지 너비 */
    __u32 height;     /* 이미지 높이 */
    __u32 fg_color;   /* 전경색 (1bpp 모노크롬 시) */
    __u32 bg_color;   /* 배경색 */
    __u8 depth;       /* 이미지 깊이 (1=모노, 8=팔레트 등) */
    const char *data; /* 픽셀 데이터 포인터 */
    struct fb_cmap cmap;  /* 이미지 전용 팔레트 (선택) */
};
ROP 연산: fb_fillrect.ropROP_COPY이면 지정 색상으로 덮어쓰고, ROP_XOR이면 기존 픽셀과 XOR하여 커서 반전 효과를 만듭니다. fbcon은 커서 깜빡임에 ROP_XOR을 사용합니다.

픽셀 포맷과 색상 관리

프레임버퍼의 각 픽셀은 bits_per_pixel만큼의 비트로 구성됩니다. fb_var_screeninfored, green, blue, transp 필드가 각 채널의 비트 위치와 길이를 정의합니다.

픽셀 비트 레이아웃

픽셀 비트 레이아웃 비교 RGB565 (16bpp) R [15:11] 5bit G [10:5] 6bit B [4:0] 5bit RGB888 (24bpp) R [23:16] 8bit G [15:8] 8bit B [7:0] 8bit XRGB8888 (32bpp) X [31:24] 8bit R [23:16] 8bit G [15:8] 8bit B [7:0] 8bit ARGB8888 (32bpp) A [31:24] 8bit R [23:16] 8bit G [15:8] 8bit B [7:0] 8bit MSB LSB

색상 필드 설정

/* XRGB8888 색상 필드 설정 예제 */
static void myfb_set_xrgb8888(struct fb_var_screeninfo *var)
{
    var->bits_per_pixel = 32;
    var->red.offset     = 16;  var->red.length     = 8;  var->red.msb_right = 0;
    var->green.offset   = 8;   var->green.length   = 8;  var->green.msb_right = 0;
    var->blue.offset    = 0;   var->blue.length    = 8;  var->blue.msb_right = 0;
    var->transp.offset  = 0;   var->transp.length  = 0;  var->transp.msb_right = 0;

    /* RGB565 설정 예시:
     * var->bits_per_pixel = 16;
     * var->red    = (struct fb_bitfield){ .offset = 11, .length = 5 };
     * var->green  = (struct fb_bitfield){ .offset = 5,  .length = 6 };
     * var->blue   = (struct fb_bitfield){ .offset = 0,  .length = 5 };
     */
}

fb_cmap — 색상 맵

팔레트 모드(8bpp 이하)에서 색상 인덱스를 실제 RGB 값으로 변환하는 테이블입니다.

/* fb_setcolreg 구현 — 트루컬러 pseudo_palette 설정 */
static int myfb_setcolreg(unsigned regno, unsigned red, unsigned green,
                          unsigned blue, unsigned transp,
                          struct fb_info *info)
{
    u32 *pal = info->pseudo_palette;

    if (regno >= 16)
        return -EINVAL;

    /* 16비트로 정규화된 값을 실제 bpp에 맞게 변환 */
    red   >>= (16 - info->var.red.length);
    green >>= (16 - info->var.green.length);
    blue  >>= (16 - info->var.blue.length);

    pal[regno] = (red   << info->var.red.offset)   |
                 (green << info->var.green.offset) |
                 (blue  << info->var.blue.offset);

    if (info->var.transp.length > 0) {
        transp >>= (16 - info->var.transp.length);
        pal[regno] |= (transp << info->var.transp.offset);
    }

    return 0;
}
/* fb_cmap 구조체 */
struct fb_cmap {
    __u32 start;    /* 시작 인덱스 */
    __u32 len;      /* 엔트리 수 */
    __u16 *red;     /* 빨강 배열 */
    __u16 *green;   /* 초록 배열 */
    __u16 *blue;    /* 파랑 배열 */
    __u16 *transp;  /* 알파 배열 (NULL 가능) */
};

pseudo_palette

트루컬러 모드(16/24/32bpp)에서 fbcon이 사용하는 16개 엔트리의 "가짜 팔레트"입니다. fbcon은 콘솔 색상(0~15)을 이 배열에서 찾아 실제 픽셀 값으로 변환합니다. 드라이버는 probeu32 pseudo_palette[16] 배열을 할당하고 info->pseudo_palette에 연결해야 합니다.

포맷bppVisualpseudo_palette 필요setcolreg 역할
RGB56516TRUECOLOR예 (16개)pseudo_palette 채움
RGB88824TRUECOLOR예 (16개)pseudo_palette 채움
XRGB888832TRUECOLOR예 (16개)pseudo_palette 채움
8bpp indexed8PSEUDOCOLOR아니오하드웨어 팔레트 설정
FB_TYPE_*설명
FB_TYPE_PACKED_PIXELS0일반 packed 픽셀 (가장 흔함)
FB_TYPE_PLANES1비트플레인 (Amiga 등 레거시)
FB_TYPE_INTERLEAVED_PLANES2인터리브 비트플레인
FB_TYPE_TEXT3텍스트 모드
FB_TYPE_VGA_PLANES4VGA 비트플레인
FB_TYPE_FOURCC5FourCC 기반 포맷 (DRM 호환)
FB_VISUAL_*설명
FB_VISUAL_MONO010흑백 (0=흰색, 1=검은색)
FB_VISUAL_MONO101흑백 (0=검은색, 1=흰색)
FB_VISUAL_TRUECOLOR2트루컬러 (pseudo_palette 사용)
FB_VISUAL_PSEUDOCOLOR3인덱스 색상 (하드웨어 팔레트)
FB_VISUAL_DIRECTCOLOR4다이렉트컬러 (R/G/B 별도 팔레트)
FB_VISUAL_STATIC_PSEUDOCOLOR5정적 인덱스 색상 (변경 불가)
엔디안 주의: fb_bitfield.msb_right가 0이면 리틀 엔디안 비트 순서(기본), 1이면 빅 엔디안 비트 순서입니다. 대부분의 현대 하드웨어는 msb_right = 0을 사용하지만, 일부 임베디드 디스플레이 컨트롤러는 다를 수 있으니 데이터시트를 반드시 확인하세요.

Stride (라인 바이트 길이)와 정렬

fix.line_length(stride)는 프레임버퍼 메모리에서 한 행(row)이 차지하는 바이트 수입니다. stride ≥ xres × (bpp/8)이며, 하드웨어에 따라 2의 거듭제곱 정렬, 캐시 라인 정렬(64바이트), 또는 특정 배수(예: 128바이트) 정렬이 필요할 수 있습니다. stride가 잘못되면 화면이 비틀어져 보이는 "대각선 깨짐" 현상이 발생합니다.

Stride (line_length)와 패딩 바이트 Row 0 유효 픽셀 데이터 (xres × bpp/8) 패딩 Row 1 유효 픽셀 데이터 패딩 Row 2 유효 픽셀 데이터 패딩 xres × (bpp / 8) 바이트 fix.line_length (stride) — 정렬 포함 Stride 계산 공식 기본: stride = xres × (bpp / 8) 정렬: stride = ALIGN(xres × bpp/8, align) 예: 1366 × 4 = 5464 → ALIGN(5464, 64) = 5504 → 패딩 = 5504 - 5464 = 40 바이트/행 잘못된 stride 시 증상 • 화면이 비틀어져 대각선으로 표시 • 행 끝에 쓰레기 픽셀 출현 • 행이 하나씩 밀려 보이는 "shearing" → fix.line_length를 HW에 맞게 수정
/* stride 정렬 계산 예제 */
static void myfb_calc_stride(struct fb_info *info)
{
    unsigned int min_stride;
    unsigned int align = 64;  /* 64바이트 정렬 (예시) */

    min_stride = info->var.xres * (info->var.bits_per_pixel / 8);

    /* ALIGN 매크로: (value + align - 1) & ~(align - 1) */
    info->fix.line_length = ALIGN(min_stride, align);

    /* 1366×768 32bpp 예시:
     * min_stride = 1366 × 4 = 5464
     * ALIGN(5464, 64) = 5504
     * → 행당 40바이트 패딩 */
}

/* 픽셀 좌표 → 메모리 오프셋 변환 */
static inline unsigned long pixel_offset(struct fb_info *info,
                                          unsigned int x, unsigned int y)
{
    /* stride 기반 계산 (xres 대신 line_length 사용!) */
    return y * info->fix.line_length +
           x * (info->var.bits_per_pixel / 8);
}

픽셀 포맷 변환 유틸리티

서로 다른 픽셀 포맷 간 변환이 필요한 경우, 비트 시프트와 마스킹으로 수행합니다:

/* 픽셀 포맷 변환 유틸리티 */

/* RGB888 → RGB565 변환 (24bpp → 16bpp) */
static inline u16 rgb888_to_rgb565(u32 rgb888)
{
    u8 r = (rgb888 >> 16) & 0xFF;
    u8 g = (rgb888 >> 8)  & 0xFF;
    u8 b =  rgb888        & 0xFF;

    return ((r >> 3) << 11) |  /* R: 8bit → 5bit */
           ((g >> 2) << 5)  |  /* G: 8bit → 6bit */
           ((b >> 3));         /* B: 8bit → 5bit */
}

/* RGB565 → XRGB8888 변환 (16bpp → 32bpp) */
static inline u32 rgb565_to_xrgb8888(u16 rgb565)
{
    u8 r = (rgb565 >> 11) & 0x1F;
    u8 g = (rgb565 >> 5)  & 0x3F;
    u8 b =  rgb565        & 0x1F;

    /* 상위 비트로 확장하여 밝기 보존 */
    return ((r << 3 | r >> 2) << 16) |
           ((g << 2 | g >> 4) << 8)  |
           ((b << 3 | b >> 2));
}

/* fb_var_screeninfo에서 픽셀 값 조합 */
static inline u32 fb_pack_pixel(struct fb_var_screeninfo *var,
                                 u8 r, u8 g, u8 b, u8 a)
{
    return ((r >> (8 - var->red.length))   << var->red.offset)   |
           ((g >> (8 - var->green.length)) << var->green.offset) |
           ((b >> (8 - var->blue.length))  << var->blue.offset)  |
           (var->transp.length ?
            ((a >> (8 - var->transp.length)) << var->transp.offset) : 0);
}
바이트 순서와 CPU 아키텍처: FB_TYPE_PACKED_PIXELS에서 픽셀 데이터는 네이티브 바이트 순서로 저장됩니다. 리틀 엔디안(x86, ARM LE)에서 XRGB8888 픽셀 0x00FF0000(빨강)은 메모리에 00 00 FF 00 순서로 저장됩니다. 빅 엔디안 시스템에서는 반대이므로, 유저스페이스에서 바이트 단위로 접근할 때는 htole32() 등의 바이트 순서 변환이 필요합니다. 커널 드라이버에서는 일반적으로 CPU 네이티브 순서를 사용하므로 걱정할 필요가 없습니다.

메모리 매핑과 DMA

사용자 공간에서 프레임버퍼 픽셀을 직접 조작하려면 mmap() 시스템 콜로 비디오 메모리를 프로세스 주소 공간에 매핑합니다. fbmem.c의 기본 fb_mmap 구현은 fix.smem_start부터 fix.smem_len만큼의 물리 메모리를 매핑합니다.

메모리 유형

유형주소특징예시
MMIO VRAMioremap()PCI BAR 등 하드웨어 VRAM데스크탑 GPU fbdev
DMA coherentdma_alloc_coherent()CPU-디바이스 일관성 보장임베디드 LCD 컨트롤러
시스템 메모리vmalloc()/kmalloc()소프트웨어 프레임버퍼vfb, USB 디스플레이
CMAdma_alloc_from_contiguous()연속 물리 메모리ARM SoC 디스플레이
가상 주소 공간 User: mmap(fd, ...) VFS → fb_mmap() remap_pfn_range() / vm_iomap_memory() User VA → 물리 매핑 완료 물리 메모리 Video RAM fix.smem_start (물리 주소) Front Buffer (xres × yres × bpp/8) Back Buffer (선택) (더블 버퍼링 시) fix.smem_len (전체 크기) PFN 매핑 screen_base (커널 가상 주소)

fb_mmap 구현

/* 커스텀 fb_mmap 구현 (I/O 메모리) */
static int myfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
    unsigned long start = info->fix.smem_start;
    unsigned long len   = info->fix.smem_len;
    unsigned long off   = vma->vm_pgoff << PAGE_SHIFT;

    if (off + (vma->vm_end - vma->vm_start) > len)
        return -EINVAL;

    /* Write-Combining 캐시 속성 (VRAM에 적합) */
    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

    /* 물리 주소를 사용자 가상 주소에 매핑 */
    return vm_iomap_memory(vma, start, len);
}

캐시 일관성

메모리 유형에 따라 적절한 캐시 속성을 선택해야 합니다:

DMA coherent 버퍼 할당

/* DMA coherent 프레임버퍼 할당 (임베디드 LCD 컨트롤러) */
static int myfb_alloc_vram(struct myfb_priv *priv)
{
    struct device *dev = priv->dev;
    size_t size = priv->xres * priv->yres * (priv->bpp / 8);

    /* DMA coherent 메모리 할당 → CPU/디바이스 모두 접근 가능 */
    priv->vram_virt = dma_alloc_coherent(dev, size,
                                         &priv->vram_phys, GFP_KERNEL);
    if (!priv->vram_virt)
        return -ENOMEM;

    /* fb_info에 연결 */
    priv->info->screen_base      = priv->vram_virt;
    priv->info->fix.smem_start   = priv->vram_phys;
    priv->info->fix.smem_len     = size;

    return 0;
}
mmap 보안: fb_mmap 구현 시 사용자가 요청한 매핑 범위가 fix.smem_len을 초과하지 않는지 반드시 검증하세요. 검증 없이 remap_pfn_range()를 호출하면 VRAM 영역 밖의 물리 메모리가 노출되어 보안 취약점이 됩니다.

VRAM 크기 계산

프레임버퍼에 필요한 VRAM 크기는 해상도, 색상 깊이, 버퍼 수로 결정됩니다:

해상도bpp싱글 버퍼더블 버퍼트리플 버퍼
640×48016600 KB1.2 MB1.8 MB
1280×720 (720p)323.5 MB7.0 MB10.5 MB
1920×1080 (1080p)327.9 MB15.8 MB23.7 MB
3840×2160 (4K)3231.6 MB63.3 MB94.9 MB
/* VRAM 크기 계산 */
static size_t calc_vram_size(unsigned int xres, unsigned int yres,
                             unsigned int bpp, unsigned int num_buffers,
                             unsigned int stride_align)
{
    size_t stride = ALIGN(xres * (bpp / 8), stride_align);
    return stride * yres * num_buffers;
}

/* 예: 1920×1080, 32bpp, 더블 버퍼, 64바이트 정렬
 * stride = ALIGN(1920 × 4, 64) = ALIGN(7680, 64) = 7680 (이미 정렬됨)
 * VRAM  = 7680 × 1080 × 2 = 16,588,800 bytes ≈ 15.8 MB */

CMA (Contiguous Memory Allocator)

ARM/ARM64 SoC의 디스플레이 컨트롤러는 대부분 물리적으로 연속된 메모리를 요구합니다. CMA는 부팅 시 메모리 영역을 예약하고, 디바이스가 필요할 때 연속 물리 메모리를 할당합니다.

/* CMA를 통한 프레임버퍼 할당 */
static int myfb_alloc_cma(struct myfb_priv *priv)
{
    struct device *dev = priv->dev;

    /* dma_alloc_coherent()는 CMA가 활성화되면 자동으로 CMA 풀에서 할당 */
    priv->vram_virt = dma_alloc_coherent(dev, priv->vram_size,
                                         &priv->vram_phys, GFP_KERNEL);
    if (!priv->vram_virt) {
        dev_err(dev, "CMA 할당 실패 (%zu bytes)\n", priv->vram_size);
        return -ENOMEM;
    }

    /* vram을 0으로 초기화 (검은 화면) */
    memset(priv->vram_virt, 0, priv->vram_size);

    dev_info(dev, "VRAM: %zu bytes @ phys 0x%pad\n",
             priv->vram_size, &priv->vram_phys);
    return 0;
}

/* Device Tree CMA 예약 예시:
 * reserved-memory {
 *     #address-cells = <2>;
 *     #size-cells = <2>;
 *     ranges;
 *
 *     fb_reserved: framebuffer@80000000 {
 *         compatible = "shared-dma-pool";
 *         reg = <0x0 0x80000000 0x0 0x2000000>;  // 32 MB
 *         reusable;
 *     };
 * };
 *
 * lcd-controller {
 *     memory-region = <&fb_reserved>;
 * };
 */
VRAM 메모리 영역 상세 구조 물리 메모리 (fix.smem_start ~ fix.smem_start + fix.smem_len) Front Buffer stride × yres bytes Back Buffer stride × yres bytes 여유 smem_start + stride × yres + smem_len 커널 가상 주소 (screen_base) screen_base = ioremap(fix.smem_start, fix.smem_len) 또는 dma_alloc_coherent() 반환값 유저 가상 주소 (mmap 반환값) user_va = mmap(NULL, fix.smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0) ioremap / dma remap_pfn_range 디스플레이 컨트롤러 DMA → 스캔아웃 HW DMA
CMA 크기 조절: 부트 파라미터 cma=64M으로 CMA 풀 크기를 설정할 수 있습니다. 4K 해상도 더블 버퍼링에는 최소 64MB가 필요합니다. /proc/meminfoCmaTotal/CmaFree로 CMA 사용 현황을 확인하세요. dmesg | grep -i cma로 부팅 시 CMA 예약 영역 정보를 확인할 수 있습니다.

하드웨어 가속

fbdev에서 "하드웨어 가속"은 fb_fillrect, fb_copyarea, fb_imageblit 세 가지 그리기 콜백을 하드웨어 블리터(2D 엔진)로 구현하는 것을 의미합니다. 가속이 없는 경우 커널이 제공하는 소프트웨어 헬퍼를 사용합니다.

그리기 경로 비교 cfb_* (I/O 메모리) writel() / memcpy_toio() I/O Memory (VRAM) PCI BAR, ioremap() HW 매핑 VRAM 직접 접근 sys_* (시스템 메모리) memcpy() / memset() System Memory (RAM) vmalloc(), DMA coherent USB 디스플레이, SPI LCD custom (HW blitter) HW 레지스터 명령 전송 2D Engine / DMA Blitter SoC 2D 가속기 DMA 기반 고속 전송 속도: 보통 복잡도: 낮음 속도: 느림 (CPU 의존) 복잡도: 낮음 속도: 빠름 (HW 가속) 복잡도: 높음

cfb 헬퍼 (I/O 메모리용)

cfb_fillrect(), cfb_copyarea(), cfb_imageblit()은 I/O 메모리에 매핑된 VRAM에 직접 접근합니다. writel()/memcpy_toio() 등 I/O 접근 함수를 사용하므로, ioremap()으로 매핑된 VRAM에 적합합니다.

sys 헬퍼 (시스템 메모리용)

sys_fillrect(), sys_copyarea(), sys_imageblit()은 일반 시스템 메모리(vmalloc/kmalloc)에 접근합니다. memcpy()/memset()을 사용하므로, USB 디스플레이나 SPI LCD 등 시스템 메모리에 섀도 버퍼를 두는 드라이버에 적합합니다.

가속 방식 선택 가이드

드라이버에서 그리기 헬퍼를 선택할 때의 판단 기준입니다:

  1. VRAM이 PCI BAR / ioremap으로 매핑됐다면cfb_* 사용
  2. 시스템 메모리(vmalloc/kmalloc/DMA coherent)라면sys_* 사용
  3. SoC에 2D 블리터/DMA 엔진이 있다면 → 커스텀 구현 (가장 빠름)
  4. SPI/I2C/USB 디스플레이라면sys_* + Deferred I/O
cfb vs sys 혼용 금지: cfb_fillrectsys_copyarea를 섞어 사용하면 안 됩니다. cfb_*는 I/O 접근자(fb_readl()/fb_writel())를 사용하고, sys_*는 일반 메모리 접근(memcpy)을 사용합니다. 메모리 유형에 맞는 한 세트를 일관되게 사용해야 합니다.

커스텀 가속

/* cfb/sys 헬퍼 사용 vs 커스텀 가속 */
/* 방법 1: cfb 헬퍼 (가속 없음, I/O 메모리) */
static const struct fb_ops simple_cfb_ops = {
    .owner       = THIS_MODULE,
    .fb_fillrect  = cfb_fillrect,
    .fb_copyarea  = cfb_copyarea,
    .fb_imageblit = cfb_imageblit,
};

/* 방법 2: sys 헬퍼 (가속 없음, 시스템 메모리) */
static const struct fb_ops simple_sys_ops = {
    .owner       = THIS_MODULE,
    .fb_fillrect  = sys_fillrect,
    .fb_copyarea  = sys_copyarea,
    .fb_imageblit = sys_imageblit,
};

/* 방법 3: 커스텀 HW 가속 (2D blitter) */
static void myfb_fillrect(struct fb_info *info,
                           const struct fb_fillrect *rect)
{
    struct myfb_priv *priv = info->par;

    /* 하드웨어 블리터에 fill 명령 전송 */
    writel(rect->dx, priv->mmio + BLITTER_DST_X);
    writel(rect->dy, priv->mmio + BLITTER_DST_Y);
    writel(rect->width, priv->mmio + BLITTER_WIDTH);
    writel(rect->height, priv->mmio + BLITTER_HEIGHT);
    writel(rect->color, priv->mmio + BLITTER_COLOR);
    writel(BLIT_CMD_FILL, priv->mmio + BLITTER_CMD);

    /* 완료 대기 */
    myfb_wait_idle(priv);
}
비교 항목cfb_* 헬퍼sys_* 헬퍼커스텀 가속
메모리 유형I/O 메모리 (ioremap)시스템 메모리 (vmalloc)하드웨어 레지스터
접근 방식writel/memcpy_toiomemcpy/memsetHW 블리터 명령
CPU 부하높음높음낮음 (HW 오프로드)
구현 난이도매우 쉬움매우 쉬움하드웨어 의존
용도PCI VGA, ioremap VRAMUSB/SPI 디스플레이SoC 2D 엔진

더블 버퍼링과 Pan/Scroll

더블 버퍼링은 화면 깜빡임(tearing)을 방지하는 기법입니다. 가상 해상도(yres_virtual)를 가시 해상도(yres)의 2배로 설정하고, fb_pan_display로 가시 영역의 시작 오프셋을 전환(flip)합니다.

더블 버퍼링과 FBIOPAN_DISPLAY Video RAM (yres_virtual = 2 × yres) Front Buffer yoffset = 0 (현재 표시 중) Back Buffer yoffset = yres (다음 프레임 그리기 중) 디스플레이 Front Buffer 표시 중 스캔아웃 FBIOPAN_DISPLAY ioctl var.yoffset = yres → fb_pan_display() → Front/Back 교체 (vsync 대기) flip

fb_pan_display 구현

/* fb_pan_display — 가시 영역 시작 오프셋 변경 */
static int myfb_pan_display(struct fb_var_screeninfo *var,
                            struct fb_info *info)
{
    struct myfb_priv *priv = info->par;
    unsigned long offset;

    /* 범위 검증 */
    if (var->yoffset + info->var.yres > info->var.yres_virtual)
        return -EINVAL;

    /* 새 시작 주소 계산 */
    offset = var->yoffset * info->fix.line_length +
             var->xoffset * (info->var.bits_per_pixel / 8);

    /* 하드웨어에 새 시작 주소 설정 */
    writel(info->fix.smem_start + offset,
           priv->mmio + LCD_BASE_ADDR_REG);

    return 0;
}

가상 해상도 설정

더블 버퍼링을 위해 fb_check_var에서 가상 해상도를 허용합니다:

FBIO_WAITFORVSYNC

/* FBIO_WAITFORVSYNC 구현 — vsync 대기 */
static int myfb_ioctl(struct fb_info *info, unsigned int cmd,
                      unsigned long arg)
{
    struct myfb_priv *priv = info->par;

    switch (cmd) {
    case FBIO_WAITFORVSYNC: {
        u32 crt = 0;
        if (copy_from_user(&crt, (void __user *)arg, sizeof(crt)))
            return -EFAULT;
        /* 다음 vsync 인터럽트 대기 */
        return myfb_wait_for_vsync(priv, crt);
    }
    default:
        return -ENOTTY;
    }
}
Tearing 주의: fb_pan_display를 vsync 없이 호출하면 스캔아웃 도중 버퍼가 전환되어 화면이 찢어지는 tearing 현상이 발생합니다. FBIO_WAITFORVSYNC로 vsync를 대기한 후 pan을 수행하거나, fb_pan_display 구현 내에서 vsync 대기 후 레지스터를 변경하는 것이 좋습니다.
Tearing: Vsync 미사용 vs Vsync 사용 Vsync 없이 Flip 프레임 N (이전) ↑ 이미 스캔된 영역 ← Tear Line 프레임 N+1 (새) ↓ 스캔 중 버퍼 전환됨 스캔라인 ↓ Vsync 대기 후 Flip 프레임 N+1 (완전) VBlank 기간에 전환 VBlank 기간 — 여기서 Flip!

트리플 버퍼링

더블 버퍼링에서 vsync를 기다리는 동안 CPU/GPU가 유휴 상태가 되어 프레임 레이트가 떨어질 수 있습니다. 트리플 버퍼링은 세 번째 버퍼를 추가하여 이 문제를 해결합니다:

/* 트리플 버퍼링 설정 */
/* fb_check_var에서 yres_virtual을 3배 허용 */
static int myfb_check_var(struct fb_var_screeninfo *var,
                          struct fb_info *info)
{
    /* 최대 트리플 버퍼까지 허용 */
    if (var->yres_virtual > var->yres * 3)
        var->yres_virtual = var->yres * 3;

    /* VRAM 범위 확인 */
    if (var->yres_virtual * var->xres_virtual *
        (var->bits_per_pixel / 8) > info->fix.smem_len) {
        var->yres_virtual = info->fix.smem_len /
            (var->xres_virtual * (var->bits_per_pixel / 8));
    }
    return 0;
}

/* 유저스페이스 트리플 버퍼 사용 패턴 */
/* buffer_idx 순환: 0 → 1 → 2 → 0 → ... */
/* var.yoffset = buffer_idx * var.yres;
 * ioctl(fd, FBIO_WAITFORVSYNC, &crt);
 * ioctl(fd, FBIOPAN_DISPLAY, &var);
 * → 렌더링은 (buffer_idx + 1) % 3 에서 계속 */
더블 vs 트리플 버퍼링: 더블 버퍼링은 메모리 사용이 적지만 vsync 대기 시 프레임 드롭이 발생할 수 있습니다. 트리플 버퍼링은 메모리를 50% 더 사용하지만, 렌더링 파이프라인이 vsync에 묶이지 않아 더 부드러운 애니메이션을 제공합니다. 임베디드 시스템에서 메모리가 제한적이라면 더블 버퍼링이 일반적이고, 고성능 시스템에서는 트리플 버퍼링을 권장합니다.

fbcon — 프레임버퍼 콘솔

fbcon은 프레임버퍼 위에서 동작하는 커널 콘솔 드라이버입니다. VT(Virtual Terminal) 서브시스템이 출력하는 텍스트를 fb_ops의 그리기 함수를 통해 픽셀로 렌더링합니다. 프레임버퍼가 등록되면 fbcon이 자동으로 해당 프레임버퍼에 바인딩됩니다.

VT 서브시스템 (printk, getty) fbcon 문자 → 픽셀 변환 폰트 데이터 (font_8x16 등) fb_fillrect() 배경 채우기 fb_copyarea() 스크롤 복사 fb_imageblit() 글꼴 렌더링 VRAM (screen_base) 디스플레이 출력

fbcon 동작 원리

  1. VT가 문자를 출력하면 fbconcon_putc()/con_putcs()가 호출됩니다.
  2. fbcon이 폰트 비트맵에서 해당 문자의 글리프를 찾습니다.
  3. fb_imageblit()으로 글리프를 VRAM에 전송합니다.
  4. 스크롤 시 fb_copyarea()로 VRAM 내용을 이동합니다.
  5. 배경은 fb_fillrect()로 채웁니다.

폰트 렌더링

커널에는 여러 빌트인 폰트가 포함되어 있습니다(lib/fonts/): font_8x8, font_8x16(기본), font_10x18, font_sun12x22 등. CONFIG_FONT_* 옵션으로 컴파일에 포함할 폰트를 선택합니다.

스크롤 모드

모드동작장점단점
SCROLL_MOVEfb_copyarea()로 VRAM 내 데이터 이동일반적, HW 가속 가능느린 메모리에서 비효율
SCROLL_PANfb_pan_display()로 오프셋 변경VRAM 복사 없음, 매우 빠름큰 가상 해상도 필요
SCROLL_WRAPPAN + 링 버퍼 방식 래핑메모리 효율적래핑 경계 처리
SCROLL_REDRAW전체 화면 재그리기단순, 항상 동작가장 느림

fbcon 커널 파라미터

# fbcon 커널 부트 파라미터 예시

# 특정 VT를 특정 프레임버퍼에 매핑
fbcon=map:0123       # VT0→fb0, VT1→fb1, VT2→fb2, VT3→fb3

# fbcon 폰트 설정
fbcon=font:VGA8x16   # 기본 폰트 지정

# fbcon 스크롤 모드
fbcon=scrollback:0   # 스크롤백 비활성화

# fbcon 완전 비활성화
fbcon=off            # fbcon 사용 안 함 (DRM 콘솔만 사용 시)
fbcon=map: 활용: 다중 프레임버퍼가 있을 때 fbcon=map:으로 VT와 프레임버퍼의 매핑을 제어할 수 있습니다. 예를 들어 fbcon=map:10은 VT0→fb1, VT1→fb0으로 매핑합니다. DRM 사용 시 fbcon=off로 fbcon을 비활성화하고 DRM 콘솔(drm_fbdev_generic_setup())만 사용하는 것이 일반적입니다.

커널 부팅 시 좌측 상단에 표시되는 리눅스 Tux 로고는 fbcon이 렌더링합니다. CONFIG_LOGO 옵션으로 활성화되며, CPU 코어 수만큼 로고가 나란히 표시됩니다.

/* 로고 관련 구조체 (include/linux/linux_logo.h) */
struct linux_logo {
    int type;           /* LINUX_LOGO_MONO, _VGA16, _CLUT224 */
    unsigned int width;
    unsigned int height;
    unsigned int clutsize;   /* CLUT224: 224개 색상 팔레트 */
    const unsigned char *clut;
    const unsigned char *data;
};

/* fbcon이 로고를 렌더링하는 흐름:
 * fbcon_init() → fb_prepare_logo() → fb_show_logo()
 *   → fb_show_logo_line()  — 로고 이미지를 fb_image로 변환
 *     → fb_imageblit()     — VRAM에 전송
 *
 * 각 CPU당 로고 한 개씩 수평으로 배치
 * 로고 영역 아래부터 콘솔 텍스트 시작 */
로고 커스터마이즈: drivers/video/logo/에 PPM 형식의 로고 이미지를 추가하고 Kconfig에 등록하면 커스텀 부팅 로고를 사용할 수 있습니다. CONFIG_LOGO_LINUX_CLUT224는 기본 224색 Tux 로고이며, logo_linux_clut224.ppm을 교체하여 변경합니다. fbcon=logo-pos:center 부트 파라미터로 로고 위치를 중앙에 배치할 수도 있습니다.

커서 처리

fbcon은 텍스트 커서를 두 가지 방식으로 렌더링합니다:

방식구현장점단점
소프트웨어 커서 fb_fillrect(ROP_XOR)으로 커서 위치 반전 모든 fbdev에서 동작 깜빡임 시 CPU 부하, tearing 가능
하드웨어 커서 fb_cursor() 콜백으로 HW 커서 제어 CPU 부하 없음, 부드러운 깜빡임 드라이버 구현 필요, 크기 제한
/* fb_cursor 콜백 (하드웨어 커서 지원 시) */
static int myfb_cursor(struct fb_info *info,
                        struct fb_cursor *cursor)
{
    struct myfb_priv *priv = info->par;

    if (cursor->set & FB_CUR_SETPOS) {
        /* 커서 위치 설정 */
        writel(cursor->image.dx, priv->mmio + HW_CURSOR_X);
        writel(cursor->image.dy, priv->mmio + HW_CURSOR_Y);
    }
    if (cursor->set & FB_CUR_SETIMAGE) {
        /* 커서 이미지 설정 (최대 64×64 등 HW 제한) */
        memcpy_toio(priv->mmio + HW_CURSOR_DATA,
                    cursor->image.data,
                    cursor->image.width * cursor->image.height / 8);
    }
    if (cursor->enable)
        writel(CURSOR_EN, priv->mmio + HW_CURSOR_CTRL);
    else
        writel(0, priv->mmio + HW_CURSOR_CTRL);

    return 0;
}

/* 하드웨어 커서를 지원하지 않으면 fb_cursor를 NULL로 두면
 * fbcon이 자동으로 소프트웨어 커서(ROP_XOR fillrect)를 사용합니다 */
fbcon 텍스트 렌더링 파이프라인 입력 문자 'A' (0x41) 폰트 테이블 font_8x16[0x41] 글리프 ..##.... .#..#... .####... .#..#... fb_imageblit() VRAM에 글리프 전송 색상 매핑: VT 콘솔 색상 (0~15) pseudo_palette[color_idx] 실제 픽셀 값 (XRGB8888 등) 예: 흰색(7) → pseudo_palette[7] → 0x00FFFFFF 스크롤 시 동작 SCROLL_MOVE fb_copyarea() VRAM 내 블록 이동 + fb_fillrect(새 행) SCROLL_PAN fb_pan_display() yoffset 변경만 VRAM 복사 불필요 SCROLL_REDRAW 전체 화면 재그리기 fb_imageblit() × N 가장 느리지만 확실 SCROLL_WRAP PAN + 링 버퍼 yoffset 순환 사용 YWRAP 플래그 필요

VT 전환과 fbcon

사용자가 Ctrl+Alt+F1~F6으로 VT를 전환하면 fbcon이 해당 VT의 화면 내용을 복원합니다:

  1. fbcon_switch()가 호출되어 새 VT에 대한 fb_info를 선택합니다.
  2. fb_set_var()로 해당 VT의 해상도/모드를 복원합니다.
  3. fbcon_redraw()가 전체 화면을 다시 그립니다.
  4. X11/Wayland가 활성 VT를 소유하면 KD_GRAPHICS 모드가 설정되어 fbcon 출력이 비활성화됩니다.
KD_GRAPHICS와 VT 전환: X 서버나 Wayland 컴포지터가 활성 상태이면 해당 VT는 KD_GRAPHICS 모드이며, fbcon은 그 VT에 텍스트를 그리지 않습니다. Ctrl+Alt+F2 등으로 텍스트 콘솔 VT로 전환하면 KD_TEXT 모드의 VT에서 fbcon이 다시 활성화됩니다. chvt 2 명령으로도 VT를 전환할 수 있습니다.

Deferred I/O

fb_deferred_io는 시스템 메모리에 할당된 프레임버퍼의 변경 사항을 추적하여, 일정 시간 후 하드웨어에 일괄 전송하는 메커니즘입니다. SPI/I2C 연결 소형 디스플레이나 E-Ink 패널처럼 즉시 업데이트가 불가능하거나 비효율적인 디바이스에 특히 유용합니다.

User write (mmap된 버퍼) page fault 추적 mkwrite → dirty 표시 dirty list 추가 변경된 페이지 기록 delayed_work delay (ms) 후 워크큐 실행 deferred_io_work → driver callback dirty 영역만 HW에 전송 SPI / I2C 전송 핵심 아이디어 1. mmap 쓰기 → page fault로 변경 감지 2. dirty 페이지 모아서 일괄 전송 3. delay로 연속 쓰기 합침 (배칭)

fb_deferred_io 구조체

fb_deferred_io는 지연 시간과 콜백을 정의합니다:

변경 추적 원리

  1. 드라이버가 vmalloc()으로 시스템 메모리 프레임버퍼를 할당합니다.
  2. fb_deferred_io_init() 호출 시, mmap의 page_mkwrite 핸들러가 설정됩니다.
  3. 사용자가 mmap된 메모리에 쓰면 page fault가 발생하고, 해당 페이지가 dirty list에 추가됩니다.
  4. 설정된 delay 시간 후 delayed_work가 실행되어 드라이버의 deferred_io 콜백을 호출합니다.
  5. 콜백에서 dirty 페이지 목록을 받아 실제 하드웨어에 전송합니다.

Deferred I/O 드라이버 예제

/* SPI 디스플레이 Deferred I/O 예제 */
static void myfb_deferred_io(struct fb_info *info,
                             struct list_head *pagereflist)
{
    struct myfb_priv *priv = info->par;
    struct fb_deferred_io_pageref *pageref;
    unsigned long min_off = ULONG_MAX, max_off = 0;

    /* dirty 영역의 최소/최대 오프셋 계산 */
    list_for_each_entry(pageref, pagereflist, list) {
        unsigned long off = pageref->offset;
        if (off < min_off) min_off = off;
        if (off + PAGE_SIZE > max_off) max_off = off + PAGE_SIZE;
    }

    /* dirty 영역만 SPI로 전송 */
    myfb_spi_transfer(priv,
                      info->screen_buffer + min_off,
                      min_off, max_off - min_off);
}

static struct fb_deferred_io myfb_defio = {
    .delay   = HZ / 30,              /* 약 33ms (30fps) */
    .sort_pagereflist = true,         /* 오프셋 순 정렬 */
    .deferred_io     = myfb_deferred_io,
};

/* probe에서 설정 */
info->fbdefio = &myfb_defio;
fb_deferred_io_init(info);
E-Ink에 적합: E-Ink 디스플레이는 전체 화면 갱신에 수백 ms가 걸립니다. Deferred I/O의 delay를 길게 설정(예: HZ = 1초)하면 여러 쓰기를 모아 한 번에 갱신하여 효율적입니다. drivers/video/fbdev/broadsheet.cmetronomefb.c가 E-Ink Deferred I/O의 실제 사례입니다.

Deferred I/O 내부 동작

fb_deferred_io 프레임워크의 핵심 구조체와 초기화/정리 순서를 살펴봅니다:

/* fb_deferred_io 구조체 (include/linux/fb.h) */
struct fb_deferred_io {
    unsigned long delay;           /* 워크큐 지연 시간 (jiffies) */
    bool sort_pagereflist;         /* dirty 리스트 오프셋 순 정렬 여부 */
    int open_count;                /* 열린 fd 수 (내부 관리) */
    struct mutex lock;             /* dirty 리스트 보호 뮤텍스 */
    struct list_head pagereflist;  /* dirty 페이지 리스트 */
    struct delayed_work deferred_work;  /* 워크큐 아이템 */

    /* 드라이버 콜백 — dirty 페이지를 HW에 전송 */
    void (*deferred_io)(struct fb_info *info,
                        struct list_head *pagereflist);
};

/* fb_deferred_io_pageref — dirty 페이지 정보 */
struct fb_deferred_io_pageref {
    struct page *page;             /* 변경된 페이지 */
    unsigned long offset;          /* 프레임버퍼 내 오프셋 */
    struct list_head list;         /* 연결 리스트 */
};
Deferred I/O: 페이지 추적 메커니즘 상세 1. mmap 설정 모든 페이지를 읽기 전용으로 설정 2. 쓰기 시도 page fault 발생! (read-only → write) 3. page_mkwrite 쓰기 허용 + dirty 등록 + delayed_work 스케줄 시스템 메모리 프레임버퍼 PAGE 0 PAGE 1 dirty! PAGE 2 PAGE 3 dirty! PAGE 4 PAGE 5 ... dirty pagereflist: [PAGE 1, offset=4096] → [PAGE 3, offset=12288] 4. delayed_work 실행 (delay ms 후) 5. driver->deferred_io() → SPI/I2C/USB 전송 워크큐 실행 후 모든 dirty 페이지를 다시 읽기 전용으로 설정 → 다음 쓰기 시 다시 감지 페이지 보호 복원
/* Deferred I/O 초기화 / 정리 순서 */

/* probe() 또는 fb_open() 시 */
static int myfb_init_defio(struct fb_info *info)
{
    /* 1. 시스템 메모리로 프레임버퍼 할당 (vmalloc 필수!) */
    info->screen_buffer = vzalloc(info->fix.smem_len);
    if (!info->screen_buffer)
        return -ENOMEM;

    /* 2. fb_deferred_io 구조체 설정 */
    info->fbdefio = &myfb_defio;  /* .delay, .deferred_io 설정 */

    /* 3. Deferred I/O 초기화 — vm_ops 교체, 워크큐 설정 */
    fb_deferred_io_init(info);

    return 0;
}

/* remove() 또는 fb_release() 시 */
static void myfb_cleanup_defio(struct fb_info *info)
{
    /* 반드시 unregister_framebuffer 전에 호출! */
    fb_deferred_io_cleanup(info);

    vfree(info->screen_buffer);
    info->screen_buffer = NULL;
}

/* 중요: Deferred I/O는 vmalloc() 메모리에서만 동작합니다.
 * ioremap()이나 dma_alloc_coherent() 메모리에서는 사용할 수 없습니다.
 * page fault 기반 추적이 vmalloc 페이지에만 적용되기 때문입니다. */
vmalloc 필수: fb_deferred_iovmalloc()으로 할당된 메모리에서만 동작합니다. ioremap()이나 dma_alloc_coherent() 메모리의 페이지는 VM 서브시스템의 page fault 추적 대상이 아니므로, Deferred I/O의 page_mkwrite 핸들러가 호출되지 않습니다. SPI/I2C/USB 디스플레이 드라이버에서는 반드시 vmalloc()으로 섀도 버퍼를 할당하고, Deferred I/O 콜백에서 이 버퍼의 변경분을 실제 하드웨어로 전송해야 합니다.

부트 타임 프레임버퍼

부트 타임 프레임버퍼는 실제 GPU/디스플레이 드라이버가 로드되기 전에 화면 출력을 제공하는 간단한 드라이버들입니다. 펌웨어(BIOS/EFI)가 설정한 프레임버퍼를 그대로 사용하며, 모드 변경이나 하드웨어 가속 없이 픽셀만 출력합니다.

부트 → 실제 드라이버 전환 타임라인 time Firmware EFI GOP / VESA VBE 모드 설정 efifb / vesafb FW 설정 그대로 사용 부트 메시지 표시 simplefb (선택) DT 기반 일반 FB 임베디드 부트로더 실제 드라이버 DRM/KMS or fbdev HW 가속, 모드 설정 커널 초기화 시 자동 등록 부트 FB를 교체(takeover) 실제 드라이버 로드 시 부트 FB는 자동 제거됩니다. remove_conflicting_framebuffers() 호출

efifb — EFI 프레임버퍼

efifb는 EFI(UEFI) 펌웨어의 GOP(Graphics Output Protocol)가 설정한 프레임버퍼를 사용합니다. 커널 부트 시 EFI 부트 서비스에서 전달받은 프레임버퍼 주소와 해상도 정보를 그대로 사용하며, 모드 변경 기능은 없습니다. CONFIG_FB_EFI로 활성화합니다.

vesafb — VESA 프레임버퍼

vesafb는 BIOS의 VESA VBE(VESA BIOS Extensions) 인터페이스로 설정된 프레임버퍼를 사용합니다. 부트로더(GRUB 등)에서 vga= 파라미터로 모드를 선택하고, 커널은 그 모드를 유지합니다. 레거시 BIOS 부팅 시 사용되며, UEFI 시스템에서는 efifb를 사용합니다.

simplefb — 범용 심플 프레임버퍼

/* simplefb Device Tree 바인딩 예제 */
/* drivers/video/fbdev/simplefb.c 는 DT/플랫폼 디바이스로 등록 */

/* Device Tree 노드:
 * framebuffer {
 *     compatible = "simple-framebuffer";
 *     reg = <0x80000000 (1920*1080*4)>;
 *     width = <1920>;
 *     height = <1080>;
 *     stride = <(1920*4)>;
 *     format = "a8r8g8b8";
 * };
 */

/* simplefb는 부트로더가 사전 설정한 프레임버퍼를 커널에서 사용.
 * 모드 변경 불가 — 부트로더가 설정한 해상도/포맷 그대로 유지.
 * ARM/ARM64 임베디드에서 DRM 드라이버 로드 전 콘솔 출력에 활용. */

부트 프레임버퍼 핸드오프

실제 GPU 드라이버(DRM 또는 네이티브 fbdev)가 로드되면, 부트 프레임버퍼를 제거해야 합니다:

simplefb 해상도 고정: simplefb는 부트로더가 설정한 해상도를 변경할 수 없습니다. fb_check_var에서 다른 해상도 요청을 거부합니다. 해상도를 변경하려면 실제 드라이버가 필요합니다. simpledrm(drivers/gpu/drm/tiny/simpledrm.c)은 simplefb의 DRM 대응 버전으로, DRM 프레임워크에서 같은 역할을 합니다.

부트 프레임버퍼 커널 파라미터

부트 프레임버퍼의 동작을 제어하는 주요 커널 명령줄 파라미터들입니다:

파라미터적용 대상설명
video=efifbefifbefifb 활성화 (보통 자동 감지)
video=efifb:offefifbefifb 비활성화
video=vesafb:mtrr:3vesafbMTRR 설정 (0=없음, 3=write-combining)
video=vesafb:ywrapvesafbY-wrap 스크롤 활성화
vga=0x317vesafbVESA 모드 번호 (1024×768 16bpp)
vga=askvesafb부팅 시 모드 선택 메뉴
video=simplefbsimplefbsimplefb 활성화
video=simpledrmsimpledrmsimplefb 대신 simpledrm 사용
nomodeset전체KMS 모드 설정 비활성화 (부트 FB 유지)
# GRUB에서 부트 프레임버퍼 설정 예시

# /etc/default/grub
# VESA 모드 지정 (레거시 BIOS)
GRUB_CMDLINE_LINUX="vga=0x31B"  # 1280×1024 24bpp

# EFI 프레임버퍼 + fbcon 활성화
GRUB_CMDLINE_LINUX="fbcon=font:VGA8x16"

# GPU 드라이버 문제 시 부트 FB로 폴백
GRUB_CMDLINE_LINUX="nomodeset"  # KMS 비활성화 → efifb/vesafb 유지

# 해상도 목록 확인 (VESA 모드)
# vga=ask로 부팅하면 사용 가능한 모드 번호가 표시됨

efifb 내부 동작

efifb가 커널에서 초기화되는 과정을 단계별로 살펴봅니다:

  1. 부트로더(GRUB EFI)가 EFI GOP(Graphics Output Protocol)를 호출하여 프레임버퍼를 설정합니다.
  2. 커널 EFI 스텁(drivers/firmware/efi/)이 screen_info 구조체에 프레임버퍼 정보를 저장합니다.
  3. efifb_probe()screen_info에서 주소, 해상도, 색상 깊이를 읽습니다.
  4. ioremap_wc()로 EFI가 설정한 프레임버퍼 물리 주소를 Write-Combining으로 매핑합니다.
  5. register_framebuffer()로 등록하면 fbcon이 즉시 콘솔을 표시합니다.
/* efifb 핵심 코드 (drivers/video/fbdev/efifb.c, 간략화) */
static int efifb_probe(struct platform_device *dev)
{
    /* screen_info는 부팅 시 EFI 스텁이 채운 전역 구조체 */
    info->var.xres = screen_info.lfb_width;
    info->var.yres = screen_info.lfb_height;
    info->var.bits_per_pixel = screen_info.lfb_depth;

    info->fix.smem_start = screen_info.lfb_base;
    info->fix.smem_len = screen_info.lfb_size;
    info->fix.line_length = screen_info.lfb_linelength;

    /* WC(Write-Combining)로 VRAM 매핑 */
    info->screen_base = ioremap_wc(info->fix.smem_start,
                                   info->fix.smem_len);

    /* 색상 필드는 screen_info에서 직접 가져옴 */
    info->var.red.offset = screen_info.red_pos;
    info->var.red.length = screen_info.red_size;
    /* green, blue도 동일 ... */

    /* fb_ops는 최소한의 것만: cfb_fillrect, cfb_copyarea, cfb_imageblit
     * fb_check_var, fb_set_par는 해상도 변경을 거부 */

    return register_framebuffer(info);
}
efifb vs simpledrm: 최근 커널(5.14+)에서는 efifb 대신 simpledrm을 기본으로 사용하는 추세입니다. simpledrm은 DRM 프레임워크를 사용하므로 Wayland 등 현대 디스플레이 서버와의 호환성이 더 좋습니다. CONFIG_DRM_SIMPLEDRM=y가 설정되면 efifb 대신 simpledrm이 부트 디스플레이를 담당합니다.

사용자 공간 인터페이스

사용자 공간에서 프레임버퍼에 접근하는 주요 방법은 /dev/fbN 디바이스 파일을 통한 ioctl()mmap()입니다.

주요 ioctl 명령

ioctl방향설명
FBIOGET_VSCREENINFOGET가변 화면 정보 읽기 (해상도, 색상)
FBIOPUT_VSCREENINFOSET가변 화면 정보 설정 (모드 변경)
FBIOGET_FSCREENINFOGET고정 화면 정보 읽기 (메모리 주소, 크기)
FBIOPAN_DISPLAYSET가시 영역 이동 (더블 버퍼 플립)
FBIO_WAITFORVSYNC수직 동기 대기
FBIOGETCMAPGET색상 맵 읽기
FBIOPUTCMAPSET색상 맵 설정
FBIOBLANKSET화면 끄기/켜기 (DPMS)
FBIO_CURSORSET하드웨어 커서 설정

sysfs 속성

/sys/class/graphics/fb0/에서 다양한 속성을 확인할 수 있습니다:

사용자 공간 도구

# fbset — 프레임버퍼 설정 도구
fbset -fb /dev/fb0 -i            # 현재 설정 정보 출력
fbset -fb /dev/fb0 -g 1920 1080 1920 2160 32   # 해상도/깊이 변경
fbset -fb /dev/fb0 -depth 16     # 색상 깊이 변경
fbset -fb /dev/fb0 -accel true   # 가속 활성화

# 기타 도구
cat /proc/fb                     # 등록된 프레임버퍼 목록
cat /dev/fb0 > screenshot.raw    # 화면 캡처 (raw)
fbi -d /dev/fb0 image.png        # 이미지 표시 (fbi 도구)
dd if=/dev/zero of=/dev/fb0      # 화면 지우기 (검은색)

mmap으로 직접 그리기

/* 사용자 공간에서 프레임버퍼에 직접 그리기 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>

int main(void)
{
    int fd = open("/dev/fb0", O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    /* 화면 정보 읽기 */
    struct fb_var_screeninfo var;
    struct fb_fix_screeninfo fix;
    ioctl(fd, FBIOGET_VSCREENINFO, &var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fix);

    printf("해상도: %ux%u, %ubpp, stride=%u\n",
           var.xres, var.yres, var.bits_per_pixel, fix.line_length);

    /* 프레임버퍼 mmap */
    size_t screensize = fix.smem_len;
    char *fbp = mmap(NULL, screensize, PROT_READ | PROT_WRITE,
                     MAP_SHARED, fd, 0);
    if (fbp == MAP_FAILED) { perror("mmap"); close(fd); return 1; }

    /* 화면을 빨간색으로 채우기 (XRGB8888 가정) */
    if (var.bits_per_pixel == 32) {
        for (unsigned y = 0; y < var.yres; y++) {
            unsigned *line = (unsigned *)(fbp + y * fix.line_length);
            for (unsigned x = 0; x < var.xres; x++) {
                /* XRGB8888: 0x00RRGGBB */
                line[x] = 0x00FF0000;  /* 빨강 */
            }
        }
    }

    /* 중앙에 100x100 파란 사각형 그리기 */
    if (var.bits_per_pixel == 32) {
        unsigned cx = var.xres / 2 - 50;
        unsigned cy = var.yres / 2 - 50;
        for (unsigned y = cy; y < cy + 100 && y < var.yres; y++) {
            unsigned *line = (unsigned *)(fbp + y * fix.line_length);
            for (unsigned x = cx; x < cx + 100 && x < var.xres; x++) {
                line[x] = 0x000000FF;  /* 파랑 */
            }
        }
    }

    munmap(fbp, screensize);
    close(fd);
    return 0;
}

화면 캡처와 복원

/* 프레임버퍼 스크린샷 캡처 유틸리티 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

/* PPM(P6) 형식으로 스크린샷 저장 */
static int fb_screenshot_ppm(const char *fbdev, const char *output)
{
    int fd = open(fbdev, O_RDONLY);
    struct fb_var_screeninfo var;
    struct fb_fix_screeninfo fix;
    FILE *fp;
    unsigned int x, y;

    ioctl(fd, FBIOGET_VSCREENINFO, &var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fix);

    unsigned char *fb = mmap(NULL, fix.smem_len,
                             PROT_READ, MAP_SHARED, fd, 0);

    fp = fopen(output, "wb");
    fprintf(fp, "P6\n%u %u\n255\n", var.xres, var.yres);

    for (y = 0; y < var.yres; y++) {
        for (x = 0; x < var.xres; x++) {
            unsigned long off = y * fix.line_length +
                                x * (var.bits_per_pixel / 8);

            if (var.bits_per_pixel == 32) {
                /* XRGB8888 → RGB */
                unsigned int pixel = *(unsigned int *)(fb + off);
                unsigned char rgb[3] = {
                    (pixel >> var.red.offset)   & 0xFF,
                    (pixel >> var.green.offset) & 0xFF,
                    (pixel >> var.blue.offset)  & 0xFF
                };
                fwrite(rgb, 1, 3, fp);
            }
        }
    }

    fclose(fp);
    munmap(fb, fix.smem_len);
    close(fd);
    return 0;
}
fbdev ioctl 호출 흐름 유저 공간 ioctl(fd, cmd, arg) mmap(fd, ...) read/write(fd, ...) fbmem.c fb_ioctl() fb_mmap() fb_read()/fb_write() cmd 디스패치 GET_VSCREENINFO PUT_VSCREENINFO PAN_DISPLAY WAITFORVSYNC BLANK 드라이버 ioctl 드라이버 fb_ops → 하드웨어 제어
ioctl 에러 코드: fbdev ioctl이 지원되지 않는 명령을 받으면 -ENOTTY를 반환합니다. FBIOPUT_VSCREENINFO-EINVAL을 반환하면 요청한 해상도/포맷이 드라이버의 fb_check_var에서 거부된 것입니다. -ENOSYS는 해당 기능이 구현되지 않았음을 의미합니다.

fbdev vs DRM/KMS 비교

fbdev와 DRM/KMS는 모두 디스플레이 하드웨어에 접근하는 커널 서브시스템이지만, 설계 철학과 기능 범위가 크게 다릅니다.

fbdev vs DRM/KMS 아키텍처 비교 fbdev (레거시) App / fbset / fbi /dev/fb0 (ioctl + mmap) fbmem.c → fb_ops fbcon fb_defio Display HW (단일 출력) DRM/KMS (현대) Mesa / Wayland / Xorg /dev/dri/card0 (DRM ioctl) DRM Core → drm_driver CRTC Plane Encoder Connector Display HW (다중 출력) GEM/ TTM vs 단순, 단일 버퍼 레거시, 임베디드 적합 복잡, 다중 버퍼/출력 현대 데스크탑 표준

아키텍처 차이

기능 비교

기능fbdevDRM/KMS
모드 설정단일 ioctl (FBIOPUT_VSCREENINFO)Atomic modesetting (원자적)
다중 디스플레이제한적 (fb0, fb1 독립)완전 지원 (CRTC 연결)
하드웨어 가속2D blitter만 (fillrect 등)3D, 비디오 디코딩, 컴퓨트
오버레이 플레인미지원완전 지원 (primary, overlay, cursor)
VsyncFBIO_WAITFORVSYNC (제한적)page flip + vblank 이벤트
메모리 관리단순 mmap (드라이버 직접)GEM/TTM (참조 카운팅, 공유)
버퍼 공유미지원DMA-BUF (GPU↔카메라↔디스플레이)
전원 관리FBIOBLANK만DPMS + 런타임 PM
사용자 공간fbset, fbiMesa, Wayland, libdrm
커널 콘솔fbcon (네이티브)drm_fbdev_generic (에뮬레이션)

fbdev → DRM 마이그레이션

기존 fbdev 드라이버를 DRM으로 전환하는 일반적인 경로:

  1. drm_simple_display_pipe — 가장 간단한 DRM 드라이버 형태 (단일 CRTC, 단일 Plane, 단일 Connector)
  2. drm_gem_dma_helper — DMA coherent 메모리 기반 GEM 객체 관리
  3. drm_fbdev_generic_setup() — DRM 드라이버에서 fbdev 호환 계층 제공
  4. drivers/gpu/drm/tiny/ — SPI/I2C 소형 디스플레이 DRM 드라이버 참고 (ili9341, st7789, ssd130x 등)

DRM fbdev 호환 계층

DRM 드라이버가 drm_fbdev_generic_setup()을 호출하면, DRM 위에 fbdev 에뮬레이션 계층이 생성되어 /dev/fb0를 통한 레거시 접근이 가능합니다. fbcon도 이 계층을 통해 동작합니다. 자세한 내용은 GPU 서브시스템 — fbdev 에뮬레이션을 참고하세요.

fbdev → DRM 코드 비교

같은 기능(프레임버퍼 등록)을 fbdev와 DRM에서 각각 어떻게 구현하는지 비교합니다:

/* ============ fbdev 방식 ============ */
/* 단순, 직관적, 제한적 */

static int fbdev_init(struct platform_device *pdev)
{
    struct fb_info *info;

    info = framebuffer_alloc(sizeof(*priv), &pdev->dev);
    /* fb_fix, fb_var, fb_ops 직접 설정 ... */
    info->fbops = &myfb_ops;
    info->screen_base = vram;

    return register_framebuffer(info);
    /* → /dev/fb0 생성, fbcon 자동 연결 */
}

/* ============ DRM 방식 ============ */
/* 구조화, 확장 가능, 복잡 */

static int drm_init(struct platform_device *pdev)
{
    struct drm_device *drm;

    drm = drm_dev_alloc(&my_driver, &pdev->dev);

    /* DRM 오브젝트 파이프라인 구성 */
    drm_simple_display_pipe_init(drm, &pipe,
        &pipe_funcs,       /* CRTC/Plane 콜백 */
        formats, n_formats, /* 지원 포맷 목록 */
        NULL,               /* 모디파이어 */
        &connector);       /* 커넥터 */

    drm_mode_config_reset(drm);
    drm_dev_register(drm, 0);

    /* fbdev 에뮬레이션 (레거시 호환) */
    drm_fbdev_generic_setup(drm, 32);
    /* → /dev/dri/card0 + /dev/fb0 생성 */
    return 0;
}
DRM tiny 드라이버 참고: drivers/gpu/drm/tiny/ 디렉토리의 드라이버들은 fbdev에서 DRM으로 전환하는 최소 예제입니다. ili9341.c(SPI LCD), ssd130x.c(OLED), st7789.c(TFT LCD) 등이 있으며, drm_simple_display_pipe을 사용하여 fbdev와 비슷한 수준의 단순함으로 DRM 드라이버를 구현합니다. 새 디스플레이 드라이버를 작성한다면 fbdev 대신 DRM tiny 스타일을 권장합니다.

드라이버 작성 가이드

최소한의 fbdev 플랫폼 드라이버를 처음부터 작성하는 방법을 단계별로 안내합니다. 이 드라이버는 DMA coherent 메모리를 사용하는 임베디드 LCD 컨트롤러를 가정합니다.

최소 드라이버 골격

/* 최소 fbdev 플랫폼 드라이버 — myfb.c */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/of.h>

struct myfb_priv {
    void __iomem *mmio;            /* LCD 컨트롤러 레지스터 */
    void *vram_virt;               /* DMA 가상 주소 */
    dma_addr_t vram_phys;          /* DMA 물리 주소 */
    size_t vram_size;              /* VRAM 크기 */
    struct fb_info *info;
    u32 pseudo_palette[16];        /* fbcon용 팔레트 */
};

/* --- fb_check_var: 파라미터 검증 (HW 변경 금지) --- */
static int myfb_check_var(struct fb_var_screeninfo *var,
                          struct fb_info *info)
{
    /* 32bpp XRGB8888만 지원 */
    if (var->bits_per_pixel != 32)
        var->bits_per_pixel = 32;

    var->red.offset     = 16;  var->red.length     = 8;
    var->green.offset   = 8;   var->green.length   = 8;
    var->blue.offset    = 0;   var->blue.length    = 8;
    var->transp.offset  = 0;   var->transp.length  = 0;

    /* 가상 해상도: 더블 버퍼 허용 */
    if (var->yres_virtual < var->yres)
        var->yres_virtual = var->yres;
    if (var->yres_virtual > var->yres * 2)
        var->yres_virtual = var->yres * 2;

    return 0;
}

/* --- fb_set_par: 실제 하드웨어 모드 설정 --- */
static int myfb_set_par(struct fb_info *info)
{
    struct myfb_priv *priv = info->par;

    /* LCD 컨트롤러에 해상도/타이밍 설정 */
    writel(info->var.xres, priv->mmio + LCD_HRES);
    writel(info->var.yres, priv->mmio + LCD_VRES);
    writel(info->var.bits_per_pixel, priv->mmio + LCD_BPP);

    /* line_length 재계산 */
    info->fix.line_length = info->var.xres *
                            (info->var.bits_per_pixel / 8);

    /* LCD 활성화 */
    writel(LCD_EN | LCD_TFT, priv->mmio + LCD_CTRL);

    return 0;
}

/* --- fb_setcolreg: pseudo_palette 설정 --- */
static int myfb_setcolreg(unsigned regno, unsigned red, unsigned green,
                          unsigned blue, unsigned transp,
                          struct fb_info *info)
{
    u32 *pal = info->pseudo_palette;

    if (regno >= 16)
        return -EINVAL;

    red   >>= (16 - info->var.red.length);
    green >>= (16 - info->var.green.length);
    blue  >>= (16 - info->var.blue.length);

    pal[regno] = (red   << info->var.red.offset) |
                 (green << info->var.green.offset) |
                 (blue  << info->var.blue.offset);
    return 0;
}

/* --- fb_blank: DPMS 제어 --- */
static int myfb_blank(int blank, struct fb_info *info)
{
    struct myfb_priv *priv = info->par;

    switch (blank) {
    case FB_BLANK_UNBLANK:
        writel(readl(priv->mmio + LCD_CTRL) | LCD_EN,
               priv->mmio + LCD_CTRL);
        break;
    case FB_BLANK_POWERDOWN:
        writel(readl(priv->mmio + LCD_CTRL) & ~LCD_EN,
               priv->mmio + LCD_CTRL);
        break;
    default:
        break;
    }
    return 0;
}

/* --- fb_pan_display: 더블 버퍼 플립 --- */
static int myfb_pan_display(struct fb_var_screeninfo *var,
                            struct fb_info *info)
{
    struct myfb_priv *priv = info->par;
    unsigned long offset;

    if (var->yoffset + info->var.yres > info->var.yres_virtual)
        return -EINVAL;

    offset = var->yoffset * info->fix.line_length;
    writel(priv->vram_phys + offset, priv->mmio + LCD_BASE_ADDR);

    return 0;
}

/* --- fb_ops 정의 --- */
static const struct fb_ops myfb_ops = {
    .owner          = THIS_MODULE,
    .fb_check_var   = myfb_check_var,
    .fb_set_par     = myfb_set_par,
    .fb_setcolreg   = myfb_setcolreg,
    .fb_blank       = myfb_blank,
    .fb_pan_display = myfb_pan_display,
    .fb_fillrect    = cfb_fillrect,
    .fb_copyarea    = cfb_copyarea,
    .fb_imageblit   = cfb_imageblit,
};

/* --- probe: 디바이스 초기화 + 등록 --- */
static int myfb_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct myfb_priv *priv;
    struct fb_info *info;
    struct resource *res;
    int ret;

    /* fb_info + priv 할당 */
    info = framebuffer_alloc(sizeof(*priv), dev);
    if (!info)
        return -ENOMEM;

    priv = info->par;
    priv->info = info;

    /* MMIO 레지스터 매핑 */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    priv->mmio = devm_ioremap_resource(dev, res);
    if (IS_ERR(priv->mmio)) {
        ret = PTR_ERR(priv->mmio);
        goto err_release;
    }

    /* DMA 프레임버퍼 할당 (1920×1080×4 × 2 = 더블 버퍼) */
    priv->vram_size = 1920 * 1080 * 4 * 2;
    priv->vram_virt = dma_alloc_coherent(dev, priv->vram_size,
                                         &priv->vram_phys, GFP_KERNEL);
    if (!priv->vram_virt) {
        ret = -ENOMEM;
        goto err_release;
    }

    /* fb_fix_screeninfo 설정 */
    strscpy(info->fix.id, "myfb", sizeof(info->fix.id));
    info->fix.type        = FB_TYPE_PACKED_PIXELS;
    info->fix.visual      = FB_VISUAL_TRUECOLOR;
    info->fix.accel       = FB_ACCEL_NONE;
    info->fix.smem_start  = priv->vram_phys;
    info->fix.smem_len    = priv->vram_size;
    info->fix.line_length = 1920 * 4;
    info->fix.ypanstep    = 1;  /* 팬 디스플레이 허용 */

    /* fb_var_screeninfo 설정 */
    info->var.xres           = 1920;
    info->var.yres           = 1080;
    info->var.xres_virtual   = 1920;
    info->var.yres_virtual   = 2160;  /* 더블 버퍼 */
    info->var.bits_per_pixel = 32;
    info->var.red.offset     = 16; info->var.red.length = 8;
    info->var.green.offset   = 8;  info->var.green.length = 8;
    info->var.blue.offset    = 0;  info->var.blue.length = 8;
    info->var.activate       = FB_ACTIVATE_NOW;

    /* 연결 */
    info->fbops          = &myfb_ops;
    info->screen_base    = priv->vram_virt;
    info->screen_size    = priv->vram_size;
    info->pseudo_palette = priv->pseudo_palette;
    info->flags          = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN;

    /* 등록 → /dev/fbN 생성, fbcon 자동 연결 */
    ret = register_framebuffer(info);
    if (ret)
        goto err_dma;

    platform_set_drvdata(pdev, priv);
    dev_info(dev, "myfb: %ux%u %ubpp @ 0x%llx\n",
             info->var.xres, info->var.yres,
             info->var.bits_per_pixel,
             (unsigned long long)priv->vram_phys);
    return 0;

err_dma:
    dma_free_coherent(dev, priv->vram_size,
                      priv->vram_virt, priv->vram_phys);
err_release:
    framebuffer_release(info);
    return ret;
}

/* --- remove: 정리 --- */
static void myfb_remove(struct platform_device *pdev)
{
    struct myfb_priv *priv = platform_get_drvdata(pdev);
    struct device *dev = &pdev->dev;

    unregister_framebuffer(priv->info);
    dma_free_coherent(dev, priv->vram_size,
                      priv->vram_virt, priv->vram_phys);
    framebuffer_release(priv->info);
}

static const struct of_device_id myfb_of_match[] = {
    { .compatible = "vendor,my-lcd-controller" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myfb_of_match);

static struct platform_driver myfb_driver = {
    .probe  = myfb_probe,
    .remove = myfb_remove,
    .driver = {
        .name           = "myfb",
        .of_match_table = myfb_of_match,
    },
};
module_platform_driver(myfb_driver);

MODULE_DESCRIPTION("Minimal fbdev platform driver example");
MODULE_LICENSE("GPL");

플랫폼 통합

실제 하드웨어에서는 다음 리소스를 플랫폼 디바이스 또는 Device Tree에서 가져옵니다:

Device Tree 바인딩

/* Device Tree 바인딩 예제 */
lcd-controller@40020000 {
    compatible = "vendor,my-lcd-controller";
    reg = <0x40020000 0x1000>;       /* MMIO 레지스터 */
    interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&rcc LCD_CLK>;
    clock-names = "pixel";
    resets = <&rcc LCD_RST>;

    /* 디스플레이 타이밍 */
    display-timings {
        native-mode = <&timing0>;
        timing0: timing0 {
            clock-frequency = <148500000>;
            hactive = <1920>;
            vactive = <1080>;
            hfront-porch = <88>;
            hback-porch = <148>;
            hsync-len = <44>;
            vfront-porch = <4>;
            vback-porch = <36>;
            vsync-len = <5>;
        };
    };
};

등록 API 요약

자원 해제 필수: remove 함수에서 반드시 unregister_framebuffer() → DMA 해제 → framebuffer_release() 순서를 지키세요. 순서가 잘못되면 use-after-free가 발생할 수 있습니다. devm_* 계열 함수를 적극 활용하면 해제 순서 문제를 줄일 수 있습니다.

전원 관리 (Suspend/Resume)

fbdev 드라이버에서 시스템 절전(suspend/resume)을 지원하려면 플랫폼 드라이버의 PM 콜백을 구현합니다:

/* fbdev 드라이버의 suspend/resume 구현 */
static int myfb_suspend(struct device *dev)
{
    struct myfb_priv *priv = dev_get_drvdata(dev);
    struct fb_info *info = priv->info;

    /* 1. fbcon에게 콘솔 출력 중단 알림 */
    console_lock();
    fb_set_suspend(info, 1);  /* fbcon 일시정지 */
    console_unlock();

    /* 2. LCD 컨트롤러 비활성화 */
    writel(0, priv->mmio + LCD_CTRL);

    /* 3. 클럭 비활성화 */
    clk_disable_unprepare(priv->pixel_clk);

    return 0;
}

static int myfb_resume(struct device *dev)
{
    struct myfb_priv *priv = dev_get_drvdata(dev);
    struct fb_info *info = priv->info;

    /* 1. 클럭 재활성화 */
    clk_prepare_enable(priv->pixel_clk);

    /* 2. LCD 컨트롤러 재초기화 */
    myfb_set_par(info);  /* 해상도/타이밍 재설정 */

    /* 3. fbcon 재개 */
    console_lock();
    fb_set_suspend(info, 0);  /* fbcon 재개 → 화면 복원 */
    console_unlock();

    return 0;
}

static DEFINE_SIMPLE_DEV_PM_OPS(myfb_pm_ops,
                                 myfb_suspend, myfb_resume);

static struct platform_driver myfb_driver = {
    .driver = {
        .name = "myfb",
        .pm   = pm_sleep_ptr(&myfb_pm_ops),
    },
};
fb_set_suspend(): 이 함수는 fbcon에게 프레임버퍼가 일시정지/재개됨을 알립니다. fb_set_suspend(info, 1) 호출 시 fbcon은 해당 프레임버퍼에 대한 모든 출력을 중단합니다. Resume 시 fb_set_suspend(info, 0)을 호출하면 fbcon이 화면을 다시 그리며, FB_EVENT_RESUME 알림도 발생합니다. 반드시 console_lock()/console_unlock() 사이에서 호출해야 합니다.

일반적인 에러 처리 패턴

fbdev 드라이버에서 흔히 사용되는 에러 처리 패턴들입니다:

/* 패턴 1: goto 체인 에러 처리 (전통적) */
static int myfb_probe(struct platform_device *pdev)
{
    struct fb_info *info;
    int ret;

    info = framebuffer_alloc(sizeof(*priv), &pdev->dev);
    if (!info)
        return -ENOMEM;

    priv->clk = devm_clk_get(&pdev->dev, "pixel");
    if (IS_ERR(priv->clk)) {
        ret = PTR_ERR(priv->clk);
        goto err_release_fb;
    }

    ret = clk_prepare_enable(priv->clk);
    if (ret)
        goto err_release_fb;

    /* DMA 할당 ... */
    if (!priv->vram_virt) {
        ret = -ENOMEM;
        goto err_clk;
    }

    ret = register_framebuffer(info);
    if (ret)
        goto err_dma;

    return 0;  /* 성공 */

err_dma:
    dma_free_coherent(&pdev->dev, ...);
err_clk:
    clk_disable_unprepare(priv->clk);
err_release_fb:
    framebuffer_release(info);
    return ret;
}

/* 패턴 2: devm_* 활용 (권장 — 자동 해제) */
static int myfb_probe_devm(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct fb_info *info;

    info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);

    /* devm_clk_get: 디바이스 제거 시 자동 put */
    priv->clk = devm_clk_get_enabled(dev, "pixel");
    if (IS_ERR(priv->clk))
        return PTR_ERR(priv->clk);

    /* devm_ioremap_resource: 자동 unmap */
    priv->mmio = devm_ioremap_resource(dev, res);
    if (IS_ERR(priv->mmio))
        return PTR_ERR(priv->mmio);

    /* 주의: framebuffer_alloc/register_framebuffer는
     * devm 버전이 없으므로 수동 해제 필요 */
    return register_framebuffer(info);
}

Virtual Framebuffer (vfb)

vfb(Virtual Framebuffer)는 실제 디스플레이 하드웨어 없이 동작하는 소프트웨어 프레임버퍼입니다. 시스템 메모리에 가상 화면 버퍼를 할당하며, fbdev API 테스트, 헤드리스 서버의 가상 디스플레이, 프레임버퍼 프로그래밍 학습 등에 활용됩니다. 소스는 drivers/video/fbdev/vfb.c에 있습니다.

vfb 모듈 설정

CONFIG_FB_VIRTUAL=m으로 커널 설정 후 모듈로 빌드합니다.

vfb 사용법

# vfb 모듈 로드 (1024×768, 32bpp 기본)
sudo modprobe vfb vfb_enable=1

# 등록 확인
cat /proc/fb
# 0 Virtual FB    ← vfb가 fb0으로 등록됨

# fbset으로 확인
fbset -fb /dev/fb0 -i

# 가상 프레임버퍼에 그리기 테스트
dd if=/dev/urandom of=/dev/fb0 bs=1024 count=$((768*4))

# 모듈 제거
sudo modprobe -r vfb

vfb 내부 구현

vfb는 fbdev 학습에 최적의 참고 코드입니다. ~250줄의 매우 간결한 구현이며, 핵심 동작은 다음과 같습니다:

/* vfb 핵심 동작 (drivers/video/fbdev/vfb.c, 간략화) */

/* 1. vmalloc()으로 가상 VRAM 할당 */
static void *videomemory;
static u_long videomemorysize = 1024 * 768 * 4;  /* 기본 크기 */

/* 2. 최소한의 fb_ops (sys_* 헬퍼 사용) */
static const struct fb_ops vfb_ops = {
    .owner       = THIS_MODULE,
    .fb_read     = fb_sys_read,
    .fb_write    = fb_sys_write,
    .fb_check_var = vfb_check_var,
    .fb_set_par  = vfb_set_par,
    .fb_setcolreg = vfb_setcolreg,
    .fb_pan_display = vfb_pan_display,
    .fb_fillrect  = sys_fillrect,   /* 시스템 메모리용 */
    .fb_copyarea  = sys_copyarea,
    .fb_imageblit = sys_imageblit,
    .fb_mmap     = vfb_mmap,
};

/* 3. vfb_mmap — vmalloc 메모리를 유저에게 페이지 단위 매핑 */
static int vfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
    return remap_vmalloc_range(vma, videomemory, vma->vm_pgoff);
    /* remap_pfn_range가 아닌 remap_vmalloc_range 사용!
     * vmalloc 메모리는 물리적으로 비연속이므로 전용 함수 필요 */
}

/* vfb는 Deferred I/O 없이 sys_* 헬퍼만 사용하므로,
 * mmap 쓰기가 즉시 VRAM(vmalloc 버퍼)에 반영됩니다.
 * 실제 디스플레이가 없으므로 변경 사항은 메모리에만 존재합니다. */
vfb로 fbdev 테스트: 실제 하드웨어 없이 fbdev 프로그래밍을 연습하려면 vfb를 사용하세요. QEMU에서도 modprobe vfb로 가상 프레임버퍼를 만들고, /dev/fb0에 mmap으로 그리기 테스트를 할 수 있습니다. cat /dev/fb0 > screenshot.raw로 가상 화면을 덤프한 후 호스트에서 raw 이미지 뷰어로 확인할 수 있습니다. (ffplay -f rawvideo -pixel_format bgra -video_size 1024x768 screenshot.raw)

디버깅과 도구

fbdev 드라이버 개발 시 유용한 디버깅 기법과 도구를 소개합니다.

/proc/fb

/proc/fb는 현재 등록된 모든 프레임버퍼의 인덱스와 이름을 보여줍니다:

$ cat /proc/fb
0 EFI VGA
1 i915drmfb

ftrace / bpftrace

# fbdev 관련 ftrace 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/fb/enable
cat /sys/kernel/debug/tracing/trace_pipe

# fb_ops 콜백 추적 (kprobe)
echo 'p:fb_open fb_open' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/fb_open/enable

# bpftrace로 fb_mmap 호출 추적
bpftrace -e 'kprobe:fb_mmap { printf("fb_mmap pid=%d\n", pid); }'

# ftrace로 fb_ioctl 추적 (어떤 ioctl이 호출되는지)
echo 'p:fb_ioctl fb_ioctl cmd=%si' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/fb_ioctl/enable
cat /sys/kernel/debug/tracing/trace_pipe

일반적인 문제와 해결

/proc/fb 활용: cat /proc/fb로 현재 어떤 프레임버퍼 드라이버가 등록되어 있는지 빠르게 확인하세요. 부트 프레임버퍼(efifb/vesafb)가 실제 드라이버로 올바르게 교체되었는지 확인하는 데 유용합니다.

debugfs 인터페이스

일부 fbdev 드라이버는 debugfs에 추가 정보를 제공합니다:

# debugfs 마운트 확인
mount | grep debugfs
# debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec)

# fbdev 관련 debugfs (드라이버에 따라 다름)
ls /sys/kernel/debug/dri/0/   # DRM fbdev 에뮬레이션 시

# sysfs를 통한 상세 정보 확인
cat /sys/class/graphics/fb0/name          # 드라이버 이름
cat /sys/class/graphics/fb0/stride        # stride (line_length)
cat /sys/class/graphics/fb0/bits_per_pixel
cat /sys/class/graphics/fb0/virtual_size  # xres_virtual,yres_virtual
cat /sys/class/graphics/fb0/pan           # 현재 pan 오프셋

# 프레임버퍼 상태 종합 확인 스크립트
for attr in name stride bits_per_pixel virtual_size pan blank; do
    echo -n "$attr: "
    cat /sys/class/graphics/fb0/$attr 2>/dev/null || echo "N/A"
done

성능 측정

fbdev 드라이버의 그리기 성능을 측정하는 방법:

/* 커널 내 성능 측정 (드라이버 개발 시) */
#include <linux/ktime.h>

static void myfb_benchmark_fillrect(struct fb_info *info)
{
    struct fb_fillrect rect = {
        .dx = 0, .dy = 0,
        .width = info->var.xres,
        .height = info->var.yres,
        .color = 0, .rop = ROP_COPY,
    };
    ktime_t start, end;
    int i;

    start = ktime_get();
    for (i = 0; i < 100; i++)
        info->fbops->fb_fillrect(info, &rect);
    end = ktime_get();

    pr_info("fillrect %ux%u × 100: %lld us\n",
            rect.width, rect.height,
            ktime_to_us(ktime_sub(end, start)));
}
# 유저스페이스에서 간단한 mmap 쓰기 벤치마크
time dd if=/dev/zero of=/dev/fb0 bs=1M count=8
# → 쓰기 대역폭 측정 (MB/s)

# fbtest 도구 (fbdev 벤치마크)
# https://github.com/nicupavel/fbtest
fbtest --device /dev/fb0 --test fill
fbtest --device /dev/fb0 --test scroll

커널 로그 활용

fbdev 관련 커널 로그 메시지를 필터링하여 문제를 진단합니다:

# fbdev 관련 dmesg 필터
dmesg | grep -iE 'fb[0-9]|framebuffer|fbcon|fbmem|efifb|vesafb|simplefb'

# 주요 로그 메시지 해석:
# "fb0: EFI VGA frame buffer device"
#   → efifb가 fb0으로 등록됨
#
# "fbcon: Deferring console take-over"
#   → fbcon이 콘솔 인수를 나중으로 연기
#
# "fb0: switching to i915drmfb from EFI VGA"
#   → DRM 드라이버(i915)가 efifb를 교체
#
# "Console: switching to colour frame buffer device 240x67"
#   → fbcon이 텍스트 모드에서 프레임버퍼 콘솔로 전환
#     (240×67은 8×16 폰트 기준 문자 수)

# 동적 디버그 활성화 (더 자세한 로그)
echo 'module fb_sys_fops +p' > /sys/kernel/debug/dynamic_debug/control
echo 'file drivers/video/fbdev/core/fbmem.c +p' > /sys/kernel/debug/dynamic_debug/control

커널 설정 (Kconfig)

옵션설명권장
CONFIG_FB프레임버퍼 서브시스템 활성화Y (fbdev 사용 시)
CONFIG_FRAMEBUFFER_CONSOLEfbcon (프레임버퍼 콘솔)Y (콘솔 필요 시)
CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY주 디스플레이 자동 감지Y
CONFIG_FB_EFIEFI 프레임버퍼 (efifb)Y (UEFI 시스템)
CONFIG_FB_VESAVESA 프레임버퍼 (vesafb)Y (BIOS 시스템)
CONFIG_FB_SIMPLESimple 프레임버퍼 (simplefb)Y (임베디드)
CONFIG_FB_VIRTUALVirtual 프레임버퍼 (vfb)M (테스트용)
CONFIG_FB_CFB_FILLRECTcfb_fillrect 헬퍼자동 선택
CONFIG_FB_CFB_COPYAREAcfb_copyarea 헬퍼자동 선택
CONFIG_FB_CFB_IMAGEBLITcfb_imageblit 헬퍼자동 선택
CONFIG_FB_SYS_FILLRECTsys_fillrect 헬퍼자동 선택
CONFIG_FB_SYS_COPYAREAsys_copyarea 헬퍼자동 선택
CONFIG_FB_SYS_IMAGEBLITsys_imageblit 헬퍼자동 선택
CONFIG_FB_DEFERRED_IODeferred I/O 지원자동 선택
CONFIG_FONT_8x88×8 콘솔 폰트선택
CONFIG_FONT_8x168×16 콘솔 폰트 (기본)Y
CONFIG_LOGO부팅 시 리눅스 로고 표시선택

용도별 설정 시나리오

시나리오필수 옵션권장 옵션비활성화
임베디드 LCD
(SoC 디스플레이)
CONFIG_FB=y
CONFIG_FB_CFB_*
SoC 드라이버
FRAMEBUFFER_CONSOLE
FB_DEFERRED_IO
LOGO
FB_EFI, FB_VESA
SPI/I2C 소형 디스플레이
(IoT, E-Ink)
CONFIG_FB=y
CONFIG_FB_SYS_*
FB_DEFERRED_IO
FRAMEBUFFER_CONSOLE
FONT_8x8 (작은 화면)
FB_CFB_*, FB_EFI
데스크탑 (DRM 기반)
(GPU 드라이버 사용)
CONFIG_DRM=y
GPU 드라이버
DRM_FBDEV_EMULATION
FB_EFI (부팅용)
대부분의 FB_*
(DRM이 대체)
헤드리스 서버
(원격 관리)
CONFIG_FB=m
FB_VIRTUAL
FB_EFI (BMC 콘솔) FRAMEBUFFER_CONSOLE
LOGO
fbdev 드라이버 개발
(테스트/학습)
CONFIG_FB=y
FB_VIRTUAL=m
FB_CFB_*
FB_SYS_*
FRAMEBUFFER_CONSOLE
FB_DEFERRED_IO
DYNAMIC_DEBUG
# 임베디드 LCD 최소 설정 예시 (.config 발췌)
CONFIG_FB=y
CONFIG_FB_CFB_FILLRECT=y
CONFIG_FB_CFB_COPYAREA=y
CONFIG_FB_CFB_IMAGEBLIT=y
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_FONT_8x16=y
CONFIG_LOGO=y
# CONFIG_FB_EFI is not set      ← 임베디드에서 불필요
# CONFIG_FB_VESA is not set     ← BIOS 없음
CONFIG_FB_SIMPLE=y              ← 부트로더 콘솔 표시용

# SPI 디스플레이 추가 설정
CONFIG_FB_SYS_FILLRECT=y
CONFIG_FB_SYS_COPYAREA=y
CONFIG_FB_SYS_IMAGEBLIT=y
CONFIG_FB_SYS_FOPS=y
CONFIG_FB_DEFERRED_IO=y

참고자료

커널 공식 문서

커널 소스 경로

학습 순서 권장: 디바이스 드라이버캐릭터 디바이스프레임버퍼 (현재 페이지)GPU 서브시스템 (DRM/KMS) 순서로 학습하면 디스플레이 서브시스템의 발전 과정을 이해하기 좋습니다.

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.