2. fbdev 框架详解:核心数据结构、操作接口与生命周期

fbdev(Framebuffer Device)是 Linux 内核中历史最悠久的显示子系统框架,其核心设计围绕 fb_info 结构体、fb_ops 回调函数以及标准的字符设备注册/注销流程展开。理解这三部分,是掌握 fbdev 工作原理以及后续迁移到 DRM KMS 的基础。

2.1 fb_info 结构体:帧缓冲设备的核心描述符

fb_info 是 fbdev 框架中最重要的数据结构,每个帧缓冲设备在内存中对应一个 fb_info 实例。它包含了设备的所有硬件信息、状态、操作函数指针以及内存映射信息。

关键认知: fb_info 既是设备描述符,也是驱动与用户空间交互的桥梁。用户空间通过 mmap 映射 fb_info 中的 screen_base 指向的显存,直接进行像素读写。

2.1.1 核心字段解析

字段名 类型 描述
fbops struct fb_ops * 指向设备操作函数集的指针,驱动必须实现
screen_base char __iomem * 显存在内核空间的虚拟基地址,用户空间 mmap 的目标
screen_size unsigned long 显存总大小(字节),决定了可支持的显示分辨率
var struct fb_var_screeninfo 可变屏幕信息:分辨率、位深、像素时钟、时序参数等
fix struct fb_fix_screeninfo 固定屏幕信息:显存物理地址、行长度、MMIO 地址等
device struct device * 指向 Linux 设备模型中的 device 结构,用于电源管理和 sysfs
par void * 驱动私有数据指针,用于存放硬件寄存器基址、时钟配置等
state int 设备状态标志,如 FBINFO_STATE_RUNNING
node int 设备编号,对应 /dev/fb<N> 中的 N
开发提示: var 和 fix 是用户空间通过 ioctl(FBIOGET_VSCREENINFO / FBIOGET_FSCREENINFO)直接读取的对象。驱动在 fb_set_par 回调中根据 var 的配置更新硬件,并同步修改 fix 中的行长度等参数。

2.1.2 fb_info 结构体关系图

fb_info 核心结构体关系图 struct fb_info fbops (struct fb_ops *) screen_base / screen_size var / fix fb_open / fb_release fb_read / fb_write fb_setcolreg / fb_blank 显存虚拟地址映射 用户空间 mmap 目标 可变参数 (分辨率/位深) 固定参数 (物理地址/行长) 说明:par 指针指向驱动私有数据,device 指针关联 Linux 设备模型 用户空间通过 ioctl 操作 var/fix,通过 mmap 访问 screen_base 指向的显存

2.2 fb_ops 回调函数:驱动与内核的契约

fb_ops 结构体定义了帧缓冲设备驱动必须实现或可选实现的回调函数集合。内核在用户空间发起系统调用(如 open、ioctl、mmap)时,通过 fb_info.fbops 指针调用对应的驱动函数。

2.2.1 核心回调函数分类

回调函数 触发时机 必须实现 典型行为
fb_open 用户空间 open("/dev/fb0") 否 增加引用计数、初始化硬件状态
fb_release 用户空间 close(fd) 否 减少引用计数、清理资源
fb_read 用户空间 read(fd, buf, count) 否 从显存读取数据到用户缓冲区
fb_write 用户空间 write(fd, buf, count) 否 从用户缓冲区写入数据到显存
fb_setcolreg ioctl: FBIOPUTCMAP 是(伪彩色) 设置硬件颜色查找表(CLUT)
fb_blank ioctl: FBIOBLANK 否 控制显示面板的开关/休眠
fb_pan_display ioctl: FBIOPAN_DISPLAY 否 实现硬件滚动(panning)
fb_set_par ioctl: FBIOPUT_VSCREENINFO 是 根据 var 配置硬件时序、分辨率、位深
fb_check_var ioctl: FBIOPUT_VSCREENINFO 前 是 校验用户传入的 var 参数是否合法,并调整到最接近的值
fb_mmap 用户空间 mmap(fd, ...) 否 自定义显存映射方式(默认使用 fb_deferred_io_mmap)
常见陷阱: 许多简单 fbdev 驱动只实现 fb_set_par、fb_check_var 和 fb_setcolreg,而忽略 fb_blank。这会导致系统进入休眠后,显示器无法正常关闭或唤醒,出现“黑屏但背光亮”的异常现象。

2.2.2 fb_ops 回调调用流程

fb_ops 回调函数调用流程 用户空间应用程序 open / ioctl / mmap fbmem.c (fb_ioctl / fb_mmap 分发) fb_ops 回调函数 (驱动实现) 硬件寄存器 / 显存操作 说明:fbmem.c 是 fbdev 核心层,负责系统调用分发;驱动只需实现 fb_ops 中的具体回调 fb_check_var 在 fb_set_par 之前被调用,用于参数校验和修正

2.3 注册与注销流程:设备生命周期管理

fbdev 驱动的注册和注销遵循标准的 Linux 设备模型,核心函数为 register_framebuffer 和 unregister_framebuffer。驱动开发者需要理解这两个函数背后的完整流程,以避免资源泄漏和设备冲突。

2.3.1 注册流程 (register_framebuffer)

  1. 分配 fb_info: 使用 framebuffer_alloc 分配 fb_info 结构体,并设置 screen_base、screen_size、var、fix 等字段。
  2. 填充 fb_ops: 将驱动实现的回调函数赋值给 fb_info.fbops。
  3. 设置私有数据: 通过 fb_info.par 指向驱动私有数据结构(如寄存器基址、时钟配置)。
  4. 调用 register_framebuffer: 内核执行以下操作:
    • 检查 fb_info 合法性(fbops 不能为 NULL)。
    • 分配设备节点编号 node,创建 /dev/fb<N> 设备文件。
    • 调用 fb_set_par 初始化硬件时序和分辨率。
    • 将 fb_info 加入全局链表 registered_fb。
    • 注册 sysfs 属性,允许用户空间通过 /sys/class/graphics/fb<N> 访问设备信息。
  5. 初始化完成: 设备进入 FBINFO_STATE_RUNNING 状态,用户空间可以打开设备。

2.3.2 注销流程 (unregister_framebuffer)

  1. 调用 unregister_framebuffer: 传入要注销的 fb_info 指针。
  2. 内核执行:
    • 从全局链表 registered_fb 中移除该设备。
    • 删除 /dev/fb<N> 设备文件和 sysfs 属性。
    • 等待所有用户空间文件描述符关闭(通过引用计数)。
    • 调用 fb_blank(如果实现)关闭显示面板。
  3. 释放资源: 驱动调用 framebuffer_release 释放 fb_info 结构体及其关联内存。
最佳实践: 在模块卸载函数中,务必先调用 unregister_framebuffer,再调用 framebuffer_release。顺序错误会导致内核访问已释放的内存,引发 Oops 或系统崩溃。

2.3.3 注册与注销流程状态图

fbdev 注册与注销流程状态图 注册流程 framebuffer_alloc 填充 fb_info / fb_ops register_framebuffer 设备就绪 /dev/fb<N> 注销流程 unregister_framebuffer 移除设备 / 清理 sysfs 等待 fd 关闭 (引用计数) framebuffer_release 注册与注销关键注意事项 1. register_framebuffer 内部会调用 fb_set_par,驱动需确保此时硬件已初始化 2. unregister_framebuffer 不会自动释放 fb_info,必须手动调用 framebuffer_release 3. 注销时若仍有用户空间 fd 打开,内核会等待,可能导致卸载阻塞 4. 建议在驱动 probe 函数中调用 register_framebuffer,在 remove 函数中调用 unregister

2.4 典型驱动代码骨架

以下是一个简化的 fbdev 驱动注册与注销代码示例,展示了 fb_info 的分配、fb_ops 的填充以及注册/注销函数的调用顺序。

#include <linux/fb.h>
#include <linux/module.h>

static struct fb_info *my_fb_info;

static int my_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
    /* 校验并修正分辨率、位深等参数 */
    if (var->xres > 1920 || var->yres > 1080)
        return -EINVAL;
    return 0;
}

static int my_fb_set_par(struct fb_info *info)
{
    /* 根据 info->var 配置硬件时序寄存器 */
    /* 更新 info->fix.line_length */
    info->fix.line_length = info->var.xres * info->var.bits_per_pixel / 8;
    return 0;
}

static struct fb_ops my_fb_ops = {
    .owner          = THIS_MODULE,
    .fb_check_var   = my_fb_check_var,
    .fb_set_par     = my_fb_set_par,
    .fb_setcolreg   = NULL,  /* 真彩色模式不需要 */
    .fb_fillrect    = sys_fillrect,
    .fb_copyarea    = sys_copyarea,
    .fb_imageblit   = sys_imageblit,
};

static int my_fb_probe(struct platform_device *pdev)
{
    /* 1. 分配 fb_info */
    my_fb_info = framebuffer_alloc(0, &pdev->dev);
    if (!my_fb_info)
        return -ENOMEM;

    /* 2. 设置固定参数 */
    strcpy(my_fb_info->fix.id, "my_fb");
    my_fb_info->fix.smem_start = 0x10000000;  /* 物理地址 */
    my_fb_info->fix.smem_len   = 8 * 1024 * 1024;  /* 8MB */

    /* 3. 设置可变参数默认值 */
    my_fb_info->var.xres = 1024;
    my_fb_info->var.yres = 768;
    my_fb_info->var.bits_per_pixel = 32;

    /* 4. 映射显存 */
    my_fb_info->screen_base = ioremap(my_fb_info->fix.smem_start,
                                       my_fb_info->fix.smem_len);
    my_fb_info->screen_size = my_fb_info->fix.smem_len;

    /* 5. 挂载 fb_ops */
    my_fb_info->fbops = &my_fb_ops;

    /* 6. 注册帧缓冲设备 */
    if (register_framebuffer(my_fb_info) < 0) {
        iounmap(my_fb_info->screen_base);
        framebuffer_release(my_fb_info);
        return -EINVAL;
    }

    return 0;
}

static int my_fb_remove(struct platform_device *pdev)
{
    /* 1. 注销帧缓冲设备 */
    unregister_framebuffer(my_fb_info);

    /* 2. 取消显存映射 */
    iounmap(my_fb_info->screen_base);

    /* 3. 释放 fb_info */
    framebuffer_release(my_fb_info);

    return 0;
}
代码要点: 注意 framebuffer_alloc 的第二个参数是 struct device *,用于关联设备模型。在 my_fb_set_par 中更新 fix.line_length 是常见遗漏点,该字段直接影响用户空间计算每行像素的偏移量。

2.5 fbdev 框架的局限性

虽然 fbdev 框架简单易用,但在现代嵌入式系统中存在明显不足,这也是推动迁移到 DRM KMS 的主要原因:

局限性 具体表现 对开发的影响
单缓冲模型 不支持硬件多缓冲(double/triple buffer) 画面撕裂(tearing)难以避免
缺少合成能力 不支持硬件图层叠加(overlay/plane) 无法实现高效的 UI 合成
无原子更新 参数配置不是原子操作,中间状态可见 分辨率切换时可能出现闪屏
缺少 VBlank 同步 没有标准的垂直消隐信号管理 动画