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 中的函数指针回调到具体驱动。
Linux FrameBuffer 驱动分层架构 用户空间 (User Space) 应用程序 / GUI 库 (Qt, DirectFB, Xorg) 系统调用接口 (open/read/write/mmap/ioctl) 核心层:fbmem.c 设备管理 | 文件操作 | 事件通知 | registered_fb[] 数组 抽象层:fb_info 结构体 fb_ops 回调 | var/fix 屏幕参数 | 伪调色板 硬件驱动层 (具体芯片驱动) 初始化 | 寄存器配置 | DMA 传输 | 中断处理 硬件 (LCD 控制器 / 显存)

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 切换显示起始偏移,即可实现无撕裂的页面翻转。
fb_info 结构体核心成员关系 struct fb_info fb_ops *fbops fb_var_screeninfo var fb_fix_screeninfo fix screen_base / screen_size pseudo_palette void *par (私有数据) 驱动核心:所有硬件操作通过 fb_ops 回调 屏幕参数通过 var/fix 暴露给用户空间

2.3 注册与注销接口

驱动通过 register_framebuffer 和 unregister_framebuffer 两个核心接口完成设备生命周期管理。这两个函数定义在 fbmem.c 中,是驱动与核心层交互的唯一入口。

2.3.1 注册流程

  1. 分配 fb_info:使用 framebuffer_alloc 分配并初始化 fb_info 结构体,同时分配私有数据 par 空间。
  2. 填充参数:设置 var(分辨率、时序)、fix(内存地址、行宽)、fbops(操作函数表)。
  3. 硬件初始化:配置 LCD 控制器时钟、GPIO 复用、DMA 通道等。
  4. 申请显存:通过 dma_alloc_coherent 或 ioremap 分配/映射显存,赋值给 screen_base 和 fix.smem_start。
  5. 调用 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 注销流程

  1. 调用 unregister_framebuffer(info),核心层移除设备节点并通知用户空间。
  2. 释放 DMA 显存:dma_free_coherent。
  3. 释放 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 后 无 无设备节点
FrameBuffer 注册/注销状态机 未注册 运行中 已注销 register_framebuffer unregister_framebuffer 注册/注销内部操作 注册:framebuffer_alloc → 填充 fb_info → register_framebuffer → 创建设备节点 注销:unregister_framebuffer → 移除设备节点 → dma_free_coherent → framebuffer_release 注意:注册顺序不可颠倒,注销时必须先释放 DMA 再释放 fb_info

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 传输的前提。