3. FrameBuffer驱动框架:注册/注销流程、probe/remove回调、file_operations实现

FrameBuffer驱动是Linux显示子系统的基石,它通过/dev/fbX设备节点向用户空间暴露显存缓冲区,允许应用程序直接进行像素操作。理解其驱动框架的注册/注销流程、probe/remove回调机制以及file_operations的实现,是掌握显示驱动开发的第一步,也是后续迁移到DRM架构的基础。

3.1 驱动核心数据结构:struct fb_info

FrameBuffer驱动的核心是struct fb_info结构体,它代表一个FrameBuffer设备实例。驱动开发者需要分配并填充该结构体,然后将其注册到内核框架中。

字段 类型 说明
fbops struct fb_ops * 硬件操作函数集,包括fb_open、fb_release、fb_read、fb_write、fb_ioctl、fb_mmap、fb_check_var、fb_set_par、fb_blank等
var struct fb_var_screeninfo 可变屏幕参数,如分辨率、位深、像素时钟、时序参数等
fix struct fb_fix_screeninfo 固定屏幕参数,如显存物理地址、长度、行宽、类型等
screen_base char __iomem * 显存虚拟基地址,用户空间通过mmap映射此区域
screen_size unsigned long 显存总大小(字节)
device struct device * 关联的Linux设备模型设备,用于电源管理和sysfs
par void * 驱动私有数据,通常指向硬件寄存器基址、时钟结构等
关键提示: fb_info 的 par 字段是驱动私有数据的“藏身之处”,建议在 probe 中分配一个包含硬件资源、互斥锁、定时器等的结构体,并通过 par 指针挂载,避免使用全局变量。

3.2 注册与注销流程

FrameBuffer驱动的生命周期管理遵循Linux设备模型的标准流程:probe 时分配并注册 fb_info,remove 时注销并释放资源。

3.2.1 注册流程(probe回调)

  1. 分配fb_info:调用 framebuffer_alloc(size, device) 分配 fb_info 及其私有数据空间。
  2. 填充fb_info:设置 fbops、var、fix、screen_base、screen_size 等字段。
  3. 硬件初始化:映射寄存器、申请中断、初始化控制器、分配DMA缓冲区。
  4. 注册设备:调用 register_framebuffer(info) 将设备注册到内核,生成 /dev/fbX 节点。
  5. sysfs创建:可选的 device_create 调用,用于创建额外的属性文件。
// 典型probe回调示例
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. 设置固定/可变参数
    strcpy(info->fix.id, "myfb");
    info->fix.smem_start = ...;  // 物理地址
    info->fix.smem_len = ...;    // 显存大小
    info->fix.line_length = ...; // 行字节数
    info->var.xres = 1024;
    info->var.yres = 600;
    info->var.bits_per_pixel = 32;
    // 4. 硬件初始化(寄存器映射、时钟使能等)
    par->regs = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(par->regs)) {
        ret = PTR_ERR(par->regs);
        goto err_free_info;
    }
    // 5. 注册FrameBuffer
    ret = register_framebuffer(info);
    if (ret < 0) {
        dev_err(&pdev->dev, "Failed to register framebuffer\n");
        goto err_free_info;
    }
    platform_set_drvdata(pdev, info);
    return 0;

err_free_info:
    framebuffer_release(info);
    return ret;
}

3.2.2 注销流程(remove回调)

  1. 注销设备:调用 unregister_framebuffer(info) 移除设备节点。
  2. 硬件去初始化:释放中断、取消映射寄存器、关闭时钟、释放DMA缓冲区。
  3. 释放fb_info:调用 framebuffer_release(info) 释放内存。
// 典型remove回调示例
static int myfb_remove(struct platform_device *pdev)
{
    struct fb_info *info = platform_get_drvdata(pdev);

    // 1. 注销FrameBuffer设备
    unregister_framebuffer(info);
    // 2. 硬件去初始化(示例)
    // iounmap(par->regs);
    // free_irq(...);
    // 3. 释放fb_info
    framebuffer_release(info);
    return 0;
}
注意事项: unregister_framebuffer 会等待所有打开的设备节点关闭后才返回,因此不要在持有自旋锁或中断上下文中调用。如果硬件支持热插拔,需要在 remove 中先禁用控制器,再注销设备。

3.3 probe/remove回调与设备模型绑定

FrameBuffer驱动通常挂载在平台总线(platform bus)或I2C/SPI总线上。probe 回调由内核设备模型在设备和驱动匹配成功后自动调用。

FrameBuffer驱动 probe/remove 生命周期 设备与驱动匹配成功 probe() 被调用 framebuffer_alloc() 填充 fb_info 字段 register_framebuffer() 设备移除或驱动卸载 remove() 被调用 unregister_framebuffer() 硬件去初始化 framebuffer_release() probe 和 remove 必须成对实现,确保资源正确分配与释放

3.4 file_operations 实现

FrameBuffer驱动通过 struct fb_ops 间接实现 file_operations。内核的 fb_fops 是默认的 file_operations,它会将系统调用分发到 fb_ops 中的具体回调。

系统调用 fb_ops 回调 典型实现逻辑
open fb_open 增加引用计数,初始化硬件(如清屏、设置默认模式)
release fb_release 减少引用计数,可选的硬件复位
read fb_read 从显存拷贝数据到用户缓冲区,支持按行读取
write fb_write 从用户缓冲区拷贝数据到显存,支持按行写入
ioctl fb_ioctl 处理 FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO、FBIOPAN_DISPLAY 等标准命令
mmap fb_mmap 将显存物理地址映射到用户空间,实现直接像素操作
poll fb_poll 可选,用于等待vsync等事件

3.4.1 fb_mmap 实现要点

fb_mmap 是FrameBuffer驱动性能的关键。它通过 remap_pfn_range 将物理显存映射到用户空间,避免数据拷贝。

// fb_mmap 典型实现
static int myfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
    unsigned long start = vma->vm_start;
    unsigned long size = vma->vm_end - vma->vm_start;
    unsigned long pfn = info->fix.smem_start >> PAGE_SHIFT;

    // 设置映射属性:可读可写、非缓存(适合帧缓冲)
    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
    // 禁止换出
    vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;

    // 建立页表映射
    if (remap_pfn_range(vma, start, pfn, size, vma->vm_page_prot))
        return -EAGAIN;

    return 0;
}
性能建议: 使用 pgprot_writecombine 而非 pgprot_noncached,前者允许写合并(write-combining),能显著提升CPU连续写入显存的性能,适合帧缓冲场景。

3.4.2 fb_ioctl 核心命令处理

fb_ioctl 是用户空间配置显示参数的主要入口。以下是最常处理的命令:

  • FBIOGET_VSCREENINFO:获取当前可变参数(分辨率、位深、时序)。
  • FBIOPUT_VSCREENINFO:设置新的可变参数,驱动需验证参数合法性并调用 fb_set_par 重新配置硬件。
  • FBIOGET_FSCREENINFO:获取固定参数(显存地址、行宽、类型)。
  • FBIOPAN_DISPLAY:实现双缓冲或三缓冲的页面切换,仅需更新显示起始地址。
  • FBIOBLANK:控制屏幕开关(节能模式)。
// fb_ioctl 核心命令处理片段
static int myfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
    struct myfb_par *par = info->par;

    switch (cmd) {
    case FBIOGET_VSCREENINFO: {
        if (copy_to_user((void __user *)arg, &info->var,
                         sizeof(info->var)))
            return -EFAULT;
        return 0;
    }
    case FBIOPUT_VSCREENINFO: {
        struct fb_var_screeninfo var;
        if (copy_from_user(&var, (void __user *)arg, sizeof(var)))
            return -EFAULT;
        // 验证参数合法性
        if (var.xres > 1920 || var.yres > 1080)
            return -EINVAL;
        // 更新info->var
        info->var = var;
        // 调用硬件配置函数
        myfb_set_par(info);
        return 0;
    }
    case FBIOBLANK: {
        int blank_mode = (int)arg;
        myfb_blank(blank_mode, info);
        return 0;
    }
    default:
        return -EINVAL;
    }
}

3.5 驱动模块的注册与注销

除了设备级别的 probe/remove,驱动模块本身也需要向内核注册/注销平台驱动。

// 平台驱动定义
static struct platform_driver myfb_driver = {
    .probe  = myfb_probe,
    .remove = myfb_remove,
    .driver = {
        .name = "myfb",
        .of_match_table = myfb_of_match, // 设备树匹配表
    },
};

// 模块入口/出口
module_platform_driver(myfb_driver);

MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple FrameBuffer Driver");
MODULE_LICENSE("GPL");
兼容性提示: 在旧版内核(3.x及更早)中,部分FrameBuffer驱动使用 module_init/module_exit 手动调用 platform_driver_register/platform_driver_unregister。新版内核推荐使用 module_platform_driver 宏简化代码。

3.6 本章小结

FrameBuffer驱动框架的核心是围绕 struct fb_info 的生命周期管理:

  • 注册流程:probe → framebuffer_alloc → 填充字段 → register_framebuffer。
  • 注销流程:remove → unregister_framebuffer → 硬件释放 → framebuffer_release。
  • 用户空间交互:通过 fb_ops 实现 read/write/ioctl/mmap,其中 mmap 是高性能像素操作的关键。
  • 设备模型绑定:驱动通过平台驱动或I2C/SPI驱动与硬件设备关联,probe/remove 由内核自动调用。