2. Linux FrameBuffer 驱动框架:fbmem.c 核心、fb_info 结构体、注册与注销接口
Linux FrameBuffer 驱动框架位于 drivers/video/fbdev/ 目录下,以 fbmem.c 为枢纽,向上对接用户空间的 /dev/fb* 设备节点,向下通过 fb_info 结构体与具体硬件驱动交互。理解这一分层架构是编写和调优 FrameBuffer 驱动的基础。
2.1 框架分层与 fbmem.c 核心职责
整个 FrameBuffer 子系统采用经典的分层设计,fbmem.c 作为核心层,承担以下关键职责:
- 设备管理:注册主设备号
29,动态创建/dev/fb0~/dev/fb31字符设备。 - 文件操作接口:实现
open、release、read、write、mmap、ioctl等标准文件操作,将用户态请求转发给底层驱动。 - 事件通知:通过
fb_notifier_call_chain向用户空间发送FB_EVENT_FB_REGISTERED、FB_EVENT_BLANK等事件。 - 资源协调:管理全局
registered_fb[]数组,维护所有已注册的 FrameBuffer 设备实例。
关键点:
fbmem.c 本身不操作硬件寄存器,它只提供通用框架;所有硬件相关操作都通过 fb_info 中的函数指针回调到具体驱动。
2.2 fb_info 结构体深度解析
fb_info 是 FrameBuffer 驱动中最重要的数据结构,定义在 include/linux/fb.h 中。它聚合了驱动所需的所有信息,包括屏幕参数、操作函数指针、状态标志和私有数据。
| 成员 | 类型 | 说明 |
|---|---|---|
| fbops | struct fb_ops * |
硬件操作函数表,包含 fb_open、fb_release、fb_setcolreg、fb_blank、fb_pan_display 等回调 |
| var | struct fb_var_screeninfo |
可变屏幕参数:分辨率、位深、像素时钟、行/帧同步时序、虚拟分辨率(用于双缓冲) |
| fix | struct fb_fix_screeninfo |
固定屏幕参数:物理地址、内存长度、行宽(bytes_per_line)、内存类型、加速能力标志 |
| screen_base | char __iomem * |
显存在内核空间的虚拟地址,驱动通过此指针直接读写显存 |
| screen_size | unsigned long |
显存总大小(字节),通常为 fix.smem_len |
| pseudo_palette | void * |
伪调色板数组,用于 8/16 位色深下的颜色查找表加速 |
| fbcon_ops | struct fbcon_ops * |
控制台操作接口,用于内核终端在 FrameBuffer 上的显示 |
| device | struct device * |
指向平台设备或 PCI 设备的指针,用于电源管理和 sysfs 接口 |
| par | void * |
私有数据指针,驱动可在此存放自定义结构体(寄存器基址、时钟配置、DMA 描述符等) |
| state | int |
当前状态:FBINFO_STATE_RUNNING / FBINFO_STATE_SUSPENDED |
调优提示:
fb_var_screeninfo 中的 xres_virtual 和 yres_virtual 是实现双缓冲的关键——将虚拟分辨率设为实际分辨率的 2 倍(垂直或水平),通过 fb_pan_display 切换显示起始偏移,即可实现无撕裂的页面翻转。
2.3 注册与注销接口
驱动通过 register_framebuffer 和 unregister_framebuffer 两个核心接口完成设备生命周期管理。这两个函数定义在 fbmem.c 中,是驱动与核心层交互的唯一入口。
2.3.1 注册流程
- 分配 fb_info:使用
framebuffer_alloc分配并初始化fb_info结构体,同时分配私有数据par空间。 - 填充参数:设置
var(分辨率、时序)、fix(内存地址、行宽)、fbops(操作函数表)。 - 硬件初始化:配置 LCD 控制器时钟、GPIO 复用、DMA 通道等。
- 申请显存:通过
dma_alloc_coherent或ioremap分配/映射显存,赋值给screen_base和fix.smem_start。 - 调用 register_framebuffer:将
fb_info注册到核心层,核心层将其加入registered_fb[]数组,并创建/dev/fbX设备节点。
典型注册代码片段:
static int myfb_probe(struct platform_device *pdev)
{
struct fb_info *info;
struct myfb_par *par;
int ret;
// 1. 分配 fb_info 及私有数据
info = framebuffer_alloc(sizeof(struct myfb_par), &pdev->dev);
if (!info)
return -ENOMEM;
par = info->par;
// 2. 填充 fb_ops
info->fbops = &myfb_ops;
// 3. 设置屏幕参数
info->var = myfb_var_default;
info->fix = myfb_fix_default;
// 4. 申请 DMA 显存
info->screen_base = dma_alloc_coherent(info->device,
info->fix.smem_len,
(dma_addr_t *)&info->fix.smem_start,
GFP_KERNEL);
if (!info->screen_base) {
framebuffer_release(info);
return -ENOMEM;
}
info->screen_size = info->fix.smem_len;
// 5. 注册到核心层
ret = register_framebuffer(info);
if (ret) {
dma_free_coherent(info->device, info->fix.smem_len,
info->screen_base, info->fix.smem_start);
framebuffer_release(info);
return ret;
}
platform_set_drvdata(pdev, info);
dev_info(&pdev->dev, "FrameBuffer registered\n");
return 0;
}
2.3.2 注销流程
- 调用
unregister_framebuffer(info),核心层移除设备节点并通知用户空间。 - 释放 DMA 显存:
dma_free_coherent。 - 释放
fb_info:framebuffer_release(info)。
注意事项:注销时必须确保没有进程正在使用
/dev/fbX,否则可能导致内核崩溃。建议在驱动卸载前先检查引用计数,或使用 try_module_get/module_put 机制保护。
2.4 注册与注销状态机
从驱动加载到卸载,FrameBuffer 设备经历以下状态转换:
| 阶段 | 操作 | 核心层行为 | 用户空间可见性 |
|---|---|---|---|
| 未注册 | 驱动 probe 前 | 无 | 无设备节点 |
| 注册中 | register_framebuffer |
分配次设备号,创建 /dev/fbX,发送 FB_EVENT_FB_REGISTERED |
设备节点出现,可 open |
| 运行中 | 正常使用 | 处理文件操作,维护 registered_fb[] |
可读写、mmap、ioctl |
| 注销中 | unregister_framebuffer |
移除设备节点,发送 FB_EVENT_FB_UNREGISTERED |
设备节点消失,后续 open 失败 |
| 已注销 | 驱动 remove 后 | 无 | 无设备节点 |
2.5 关键数据结构对比:fb_var_screeninfo vs fb_fix_screeninfo
这两个结构体共同描述了一个 FrameBuffer 设备的完整显示能力,理解它们的区别对驱动开发至关重要。
| 对比维度 | fb_var_screeninfo(可变参数) | fb_fix_screeninfo(固定参数) |
|---|---|---|
| 修改时机 | 运行时可通过 FBIOPUT_VSCREENINFO 修改 |
驱动初始化时确定,运行时只读 |
| 典型成员 | xres, yres, bits_per_pixel, pixclock, left_margin, right_margin, upper_margin, lower_margin, hsync_len, vsync_len, xres_virtual, yres_virtual |
smem_start, smem_len, line_length, type, visual, accel |
| 用户空间访问 | FBIOPUT_VSCREENINFO / FBIOGET_VSCREENINFO |
FBIOGET_FSCREENINFO(只读) |
| 双缓冲关联 | xres_virtual > xres 或 yres_virtual > yres 时启用虚拟分辨率 |
line_length 反映实际行宽,smem_len 需满足虚拟分辨率总大小 |
| 驱动关注点 | 时序参数必须与 LCD 控制器寄存器严格对应 | 物理地址必须连续,line_length 需对齐到 32 字节 |
双缓冲配置示例:对于 1024×768@32bpp 的屏幕,若设置
xres_virtual = 1024、yres_virtual = 1536(2 倍垂直分辨率),则 smem_len 至少需要 1024 × 1536 × 4 = 6 MB。通过 fb_pan_display 设置 yoffset = 0 或 yoffset = 768 即可切换前后缓冲。
2.6 本章小结
- fbmem.c 是 FrameBuffer 子系统的核心调度层,负责设备节点管理和文件操作分发。
- fb_info 是驱动与核心层之间的契约结构体,包含操作函数表、屏幕参数和私有数据。
- register_framebuffer 和 unregister_framebuffer 是驱动注册/注销的标准接口,必须与
framebuffer_alloc/framebuffer_release配对使用。 - 理解
fb_var_screeninfo与fb_fix_screeninfo的差异,是正确实现双缓冲和 DMA 传输的前提。