2. Framebuffer基础回顾:FB设备驱动模型、核心数据结构与关键操作函数
在深入DRM之前,必须牢固掌握Framebuffer(FB)设备驱动模型。FB是Linux内核中最早用于显示输出的统一抽象层,它将显示缓冲区抽象为一个可直接读写的内存区域,用户空间通过/dev/fbX设备节点操作显示。本节从驱动模型、核心数据结构struct fb_info以及关键操作函数三个维度进行系统回顾。
2.1 FB设备驱动模型架构
FB驱动模型采用“平台设备驱动”或“PCI设备驱动”的注册方式,核心是struct fb_info实例的分配、初始化与注册。整体架构分为三层:
- 硬件层:包含LCD控制器、DMA引擎、显存(VRAM)等物理资源。
- 驱动层:实现
struct fb_ops中的回调函数,管理显存映射、寄存器配置、电源控制。 - 核心层:内核
fbcon(帧缓冲控制台)和fbmem(帧缓冲内存管理)提供统一的文件操作接口。
关键认知:FB模型将显示设备简化为“一块内存+一组控制寄存器”,所有上层图形栈(X11、Wayland、Qt)最终通过
mmap映射显存进行像素操作。
2.2 核心数据结构:struct fb_info
struct fb_info是FB驱动的“心脏”,每个显示设备对应一个实例。该结构体定义在linux/fb.h中,包含以下关键字段:
| 字段 | 类型 | 作用描述 |
|---|---|---|
fbops |
struct fb_ops * |
指向操作函数表,驱动必须实现 |
fb_var_screeninfo |
struct fb_var_screeninfo |
可变参数:分辨率、位深、时序、虚拟分辨率 |
fb_fix_screeninfo |
struct fb_fix_screeninfo |
固定参数:显存物理地址、长度、行字节数 |
screen_base |
void __iomem * |
显存虚拟地址基址,用于CPU直接访问 |
screen_size |
unsigned long |
显存总大小(字节) |
device |
struct device * |
关联的Linux设备模型节点 |
par |
void * |
驱动私有数据指针,存放硬件寄存器基址等 |
pseudo_palette |
u32 * |
伪调色板,用于8bpp索引色模式 |
移植经验:在嵌入式平台移植时,
screen_base通常通过ioremap或dma_alloc_coherent获得。par字段是驱动私有数据的“万能口袋”,建议将寄存器基址、时钟结构体、GPIO描述符等全部放入自定义结构体,通过par指针访问。
2.3 关键操作函数:struct fb_ops
struct fb_ops是驱动与内核核心层的“契约”,驱动必须实现其中部分回调。以下是移植中最常涉及的函数:
| 函数原型 | 触发时机 | 典型实现内容 |
|---|---|---|
fb_open / fb_release |
应用层open/close /dev/fbX | 电源管理计数、时钟使能/禁能 |
fb_setcolreg |
设置调色板寄存器(伪彩色模式) | 写入硬件CLUT(颜色查找表) |
fb_blank |
控制台切换或电源管理事件 | 开关LCD背光、进入/退出睡眠模式 |
fb_check_var |
应用层通过FBIOPUT_VSCREENINFO设置模式前 | 验证分辨率、时序、位深是否在硬件能力内,修正非法参数 |
fb_set_par |
参数验证通过后,实际配置硬件 | 设置LCD控制器寄存器:HBP、HFP、VBP、VFP、像素时钟分频 |
fb_pan_display |
应用层请求虚拟分辨率平移 | 更新显存起始偏移地址寄存器,实现无撕裂滚动 |
fb_mmap |
应用层mmap映射显存 | 将物理显存映射到用户空间虚拟地址 |
fb_ioctl |
自定义ioctl命令 | 扩展硬件特定功能(如背光亮度、旋转角度) |
常见移植陷阱:
fb_check_var必须严格校验时序参数,否则可能导致硬件锁死或花屏。fb_set_par中配置像素时钟时,注意PLL分频系数计算,避免超出LCD面板规格。fb_mmap必须使用pgprot_noncached或pgprot_writecombine设置显存缓存属性,否则CPU写入无法被DMA及时看到。
2.4 驱动注册与注销流程
一个典型的FB驱动加载流程如下:
- 分配fb_info:调用
framebuffer_alloc()分配struct fb_info实例。 - 填充固定参数:设置
fb_fix_screeninfo中的smem_start(物理地址)、smem_len(显存大小)、line_length(行字节数)。 - 填充可变参数:设置
fb_var_screeninfo的默认分辨率、位深、时序。 - 注册fb_ops:将实现的回调函数指针赋值给
fb_info->fbops。 - 注册设备:调用
register_framebuffer()将设备注册到内核。 - 注销:模块卸载时调用
unregister_framebuffer()和framebuffer_release()。
2.5 用户空间操作示例
以下代码展示应用层如何通过FB接口获取显示参数并直接绘制像素:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/fb.h>
int main() {
int fbfd = open("/dev/fb0", O_RDWR);
if (fbfd < 0) { perror("open"); return -1; }
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
// 获取可变参数(分辨率、位深)
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
// 获取固定参数(显存地址、行字节数)
ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
printf("分辨率: %dx%d, 位深: %d\n",
vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
long screensize = finfo.line_length * vinfo.yres;
char *fbp = mmap(NULL, screensize, PROT_READ | PROT_WRITE,
MAP_SHARED, fbfd, 0);
if (fbp == MAP_FAILED) { perror("mmap"); close(fbfd); return -1; }
// 将屏幕中间区域填充为红色 (RGB565: 0xF800)
int x = vinfo.xres / 2, y = vinfo.yres / 2;
long location = (x + vinfo.xres * y) * (vinfo.bits_per_pixel / 8);
*((unsigned short *)(fbp + location)) = 0xF800;
munmap(fbp, screensize);
close(fbfd);
return 0;
}
移植要点:上述代码在DRM框架下不再适用。DRM要求通过
drmModeSetCrtc、drmModeAddFB等API操作,且显存映射通过drmPrimeHandleToFD或mmap on drm fd完成。理解FB的ioctl模型有助于对比理解DRM的drmModeObject体系。
2.6 FB与DRM核心差异速览
| 对比维度 | Framebuffer (FB) | Direct Rendering Manager (DRM) |
|---|---|---|
| 设备节点 | /dev/fbX |
/dev/dri/cardX |
| 核心数据结构 | struct fb_info |
struct drm_device + struct drm_crtc + struct drm_plane |
| 显存管理 | 单一线性显存,无GEM/DMA-BUF | GEM对象 + DMA-BUF导出/导入 |
| 多图层支持 | 不支持硬件图层叠加 | 支持多个plane叠加合成 |
| 垂直同步 | 无原生VSync控制 | 通过page_flip事件精确控制 |
| GPU加速 | 无直接支持 | 原生集成GPU命令提交 |
| 现代显示特性 | 不支持HDMI/DP音频、HDR、自适应同步 | 完整支持 |