3. FBdev 驱动框架:struct fb_info 结构体详解,fb_ops 回调函数集,注册与注销 fb_info

帧缓冲(FrameBuffer,FBdev)驱动框架的核心是 struct fb_info 结构体。该结构体是驱动开发者与内核帧缓冲层之间的桥梁,它封装了显示设备的硬件信息、操作函数指针、状态标志以及辅助数据结构。理解 fb_info 的布局与生命周期管理,是编写稳定、高效的 FBdev 驱动的基础。

3.1 struct fb_info 结构体详解

struct fb_info 定义在 linux/fb.h 头文件中,是帧缓冲设备在内核中的核心表示。每个注册的帧缓冲设备都对应一个 fb_info 实例。该结构体包含以下关键字段:

字段 类型 说明
fbops struct fb_ops * 指向操作函数集的指针,驱动必须实现
fb_var_screeninfo struct fb_var_screeninfo 可变屏幕信息:分辨率、位深、时序等
fb_fix_screeninfo struct fb_fix_screeninfo 固定屏幕信息:显存物理地址、长度、行宽
screen_base void __iomem * 显存在内核空间的虚拟基地址
screen_size unsigned long 显存总大小(字节)
device struct device * 关联的硬件设备结构体
par void * 驱动私有数据指针,用于扩展
state int 设备状态标志(如挂起/恢复)
node int 设备编号(/dev/fb<node>)
flags int 设备特性标志(如 FBINFO_FLAG_DEFAULT)
关键字段解读:
  • fb_var_screeninfo 与 fb_fix_screeninfo 是用户态通过 ioctl 直接交互的对象,驱动需确保两者的一致性。
  • par 字段是驱动私有数据的“万能口袋”,通常用于存放硬件寄存器基址、时钟配置、GPIO 句柄等。
  • screen_base 是驱动进行像素操作的核心指针,必须通过 ioremap 或 dma_alloc_coherent 获得。

3.1.1 可变信息 vs 固定信息

帧缓冲驱动将显示参数分为“可变”与“固定”两类,这种分离设计使得用户态程序可以动态调整分辨率,而无需重新加载驱动。

对比维度 fb_var_screeninfo(可变) fb_fix_screeninfo(固定)
典型字段 xres, yres, bits_per_pixel, pixclock smem_start, smem_len, line_length, type
修改权限 用户态可通过 FBIOPUT_VSCREENINFO 修改 只读,由驱动在初始化时设定
更新时机 每次模式切换时更新 设备生命周期内不变
驱动职责 校验参数合法性,重新配置硬件时序 正确映射显存物理地址与长度
驱动开发提示: 在实现 fb_set_par 回调时,必须根据 fb_var_screeninfo 中的新参数重新计算 fb_fix_screeninfo.line_length,并更新硬件寄存器。如果硬件不支持请求的分辨率,应返回 -EINVAL。

3.2 fb_ops 回调函数集

struct fb_ops 是驱动必须实现的操作函数集合,内核帧缓冲层通过该结构体调用驱动提供的硬件操作。每个回调函数都有明确的语义和调用时机。

struct fb_ops {
    /* 文件操作 */
    int (*fb_open)(struct fb_info *info, int user);
    int (*fb_release)(struct fb_info *info, int user);

    /* 读取/写入显存(可选) */
    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_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_fillrect)(struct fb_info *info,
                       const struct fb_fillrect *rect);
    int (*fb_copyarea)(struct fb_info *info,
                       const struct fb_copyarea *area);
    int (*fb_imageblit)(struct fb_info *info,
                        const struct fb_image *image);

    /* 电源管理 */
    int (*fb_blank)(int blank_mode, struct fb_info *info);

    /* 扩展 IOCTL */
    int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
                    unsigned long arg);
};
必须实现的回调: 至少需要实现 fb_check_var、fb_set_par 和 fb_setcolreg。如果硬件不支持硬件加速,fb_fillrect、fb_copyarea、fb_imageblit 可以设置为 NULL,内核会使用软件模拟版本(cfb_fillrect、cfb_copyarea、cfb_imageblit)。

3.2.1 核心回调流程

下图展示了用户态发起模式切换时,内核帧缓冲层与驱动回调之间的交互流程:

fb_ops 核心回调调用流程 用户态程序 内核帧缓冲层 FBdev 驱动 FBIOPUT_VSCREENINFO fb_check_var 校验结果 fb_set_par 配置硬件寄存器 返回 0 流程说明 用户态 → 内核层 → fb_check_var(校验) → fb_set_par(配置硬件) → 返回成功

3.3 注册与注销 fb_info

帧缓冲设备的生命周期管理通过 register_framebuffer 和 unregister_framebuffer 两个核心函数完成。驱动在探测(probe)阶段注册设备,在移除(remove)阶段注销设备。

3.3.1 注册流程

注册一个帧缓冲设备需要经过以下步骤:

  1. 分配 fb_info 结构体: 使用 framebuffer_alloc 函数分配并初始化 fb_info 实例。
  2. 填充固定信息: 设置 fb_fix_screeninfo 中的显存地址、长度、行宽等。
  3. 填充可变信息: 设置默认分辨率、位深、时序参数。
  4. 设置操作函数集: 将 fb_ops 结构体赋值给 fb_info->fbops。
  5. 映射显存: 通过 ioremap 或 dma_alloc_coherent 获得 screen_base。
  6. 调用注册函数: 执行 register_framebuffer(fb_info) 将设备注册到系统。
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;
    par->dev = &pdev->dev;

    /* 2. 设置固定信息 */
    info->fix.smem_start = par->fb_phys;
    info->fix.smem_len = SZ_4M;
    info->fix.line_length = 1024 * (32 / 8);
    strscpy(info->fix.id, "myfb", sizeof(info->fix.id));

    /* 3. 设置可变信息 */
    info->var.xres = 1024;
    info->var.yres = 768;
    info->var.bits_per_pixel = 32;

    /* 4. 设置操作函数 */
    info->fbops = &myfb_ops;

    /* 5. 映射显存 */
    info->screen_base = ioremap(par->fb_phys, SZ_4M);
    if (!info->screen_base) {
        ret = -ENOMEM;
        goto err_free;
    }

    /* 6. 注册帧缓冲设备 */
    ret = register_framebuffer(info);
    if (ret < 0) {
        dev_err(&pdev->dev, "register_framebuffer failed\n");
        goto err_unmap;
    }

    platform_set_drvdata(pdev, info);
    return 0;

err_unmap:
    iounmap(info->screen_base);
err_free:
    framebuffer_release(info);
    return ret;
}

3.3.2 注销流程

注销设备时,必须按照注册的逆序释放资源:

static int myfb_remove(struct platform_device *pdev)
{
    struct fb_info *info = platform_get_drvdata(pdev);

    /* 1. 注销帧缓冲设备 */
    unregister_framebuffer(info);

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

    /* 3. 释放 fb_info 结构体 */
    framebuffer_release(info);

    return 0;
}
注销注意事项:
  • unregister_framebuffer 会等待所有打开该设备的用户态文件描述符关闭后才返回,因此不要在持有自旋锁或中断上下文中调用。
  • 必须先调用 unregister_framebuffer,再释放 screen_base 和 fb_info,否则可能导致内核访问已释放的内存。
  • 如果驱动使用了 dma_alloc_coherent 分配显存,应使用 dma_free_coherent 释放,而不是 iounmap。

3.3.3 注册与注销状态机

下图展示了 fb_info 从分配到释放的完整状态转换:

fb_info 生命周期状态机 分配 framebuffer_alloc 初始化 填充 fb_ops/var/fix 已注册 register_framebuffer 已注销 unregister_framebuffer 填充字段 注册 注销 framebuffer_release 关键规则 分配 → 初始化 → 注册 → 注销 → 释放,顺序不可颠倒,否则会导致内存泄漏或内核崩溃

3.4 本章小结

  • struct fb_info 是帧缓冲驱动的核心数据结构,包含可变/固定屏幕信息、操作函数集、显存映射和私有数据。
  • fb_ops 回调函数集定义了驱动与内核层的交互接口,fb_check_var 和 fb_set_par 是模式切换的关键路径。
  • 注册流程 遵循“分配 → 填充 → 映射 → 注册”的顺序,注销流程 遵循“注销 → 取消映射 → 释放”的逆序。
  • 驱动开发者应特别注意 screen_base 的正确映射与释放,以及 fb_var_screeninfo 与 fb_fix_screeninfo 的一致性维护。