3. fbdev 驱动实战:基于简单帧缓冲设备的驱动编写示例

在理解了 fbdev 核心数据结构与注册流程后,本节通过一个完整的虚拟帧缓冲驱动示例,将理论付诸实践。该驱动不依赖真实硬件,而是在内存中分配一块缓冲区模拟显存,适用于开发初期的原型验证与功能测试。

3.1 驱动整体架构

虚拟帧缓冲驱动遵循标准的 Linux 平台驱动模型,其核心组件包括:

  • 模块初始化/退出:注册/注销平台驱动。
  • 探测函数:分配内存、填充 fb_info 结构体、注册帧缓冲设备。
  • 移除函数:注销设备并释放资源。
  • 文件操作回调:实现 fb_ops 中的必要函数,如 fb_fillrect、fb_copyarea、fb_imageblit。
虚拟帧缓冲驱动架构 module_init / module_exit platform_driver probe() remove() 分配虚拟显存 填充 fb_info 设置 fb_ops register_framebuffer 用户空间:/dev/fbX → mmap / ioctl

3.2 关键数据结构定义

驱动首先需要定义平台驱动结构体以及用于描述虚拟设备信息的私有数据结构:

/* 虚拟帧缓冲设备私有数据 */
struct vfb_device {
    void __iomem *vmem;        /* 虚拟显存基地址 */
    size_t vmem_size;          /* 显存大小 */
    struct fb_info *info;      /* 帧缓冲信息结构体 */
};

/* 平台驱动结构体 */
static struct platform_driver vfb_driver = {
    .probe  = vfb_probe,
    .remove = vfb_remove,
    .driver = {
        .name = "vfb",
        .owner = THIS_MODULE,
    },
};

3.3 探测函数实现

vfb_probe 是驱动的核心,负责初始化所有资源。以下为关键步骤:

  1. 分配 fb_info 结构体:使用 framebuffer_alloc 分配,并关联私有数据。
  2. 分配虚拟显存:通过 kzalloc 分配一块连续内存,模拟硬件显存。
  3. 填充固定信息 (fix):设置 smem_start、smem_len、type、visual 等字段。
  4. 填充可变信息 (var):设置分辨率、位深、行宽等默认参数。
  5. 设置操作函数集 (fb_ops):实现绘图加速回调。
  6. 注册帧缓冲设备:调用 register_framebuffer。
static int vfb_probe(struct platform_device *pdev)
{
    struct fb_info *info;
    struct vfb_device *vfb;
    int ret;

    /* 1. 分配 fb_info */
    info = framebuffer_alloc(sizeof(struct vfb_device), &pdev->dev);
    if (!info)
        return -ENOMEM;

    vfb = info->par;
    vfb->info = info;

    /* 2. 分配虚拟显存:模拟 1024x768x32bpp */
    vfb->vmem_size = SZ_4M;
    vfb->vmem = kzalloc(vfb->vmem_size, GFP_KERNEL);
    if (!vfb->vmem) {
        framebuffer_release(info);
        return -ENOMEM;
    }

    /* 3. 填充固定信息 */
    strscpy(info->fix.id, "Virtual FB", sizeof(info->fix.id));
    info->fix.smem_start = (unsigned long)vfb->vmem;
    info->fix.smem_len   = vfb->vmem_size;
    info->fix.type       = FB_TYPE_PACKED_PIXELS;
    info->fix.visual     = FB_VISUAL_TRUECOLOR;
    info->fix.line_length = 1024 * 4; /* 1024 pixels * 4 bytes */

    /* 4. 填充可变信息 */
    info->var.xres = 1024;
    info->var.yres = 768;
    info->var.bits_per_pixel = 32;
    info->var.red.offset   = 16; info->var.red.length   = 8;
    info->var.green.offset = 8;  info->var.green.length = 8;
    info->var.blue.offset  = 0;  info->var.blue.length  = 8;
    info->var.transp.offset = 24; info->var.transp.length = 8;

    /* 5. 设置 fb_ops */
    info->fbops = &vfb_ops;

    /* 6. 注册帧缓冲设备 */
    ret = register_framebuffer(info);
    if (ret) {
        kfree(vfb->vmem);
        framebuffer_release(info);
        return ret;
    }

    platform_set_drvdata(pdev, info);
    dev_info(&pdev->dev, "Virtual framebuffer registered\n");
    return 0;
}

3.4 fb_ops 操作函数集

fbdev 驱动必须实现 fb_ops 结构体。对于虚拟设备,至少需要提供以下回调:

回调函数 作用 虚拟驱动实现方式
fb_fillrect 填充矩形区域 调用 sys_fillrect 通用辅助函数
fb_copyarea 复制矩形区域 调用 sys_copyarea 通用辅助函数
fb_imageblit 绘制图像 调用 sys_imageblit 通用辅助函数
fb_check_var 校验可变参数 检查分辨率与位深是否在支持范围内
fb_set_par 应用参数设置 更新 fix.line_length 等字段
fb_mmap 内存映射 使用 dma_mmap_coherent 或 remap_pfn_range
static struct fb_ops vfb_ops = {
    .owner          = THIS_MODULE,
    .fb_fillrect    = sys_fillrect,
    .fb_copyarea    = sys_copyarea,
    .fb_imageblit   = sys_imageblit,
    .fb_check_var   = vfb_check_var,
    .fb_set_par     = vfb_set_par,
    .fb_mmap        = vfb_mmap,
};
实践要点: 对于不涉及真实硬件加速的虚拟驱动,直接复用内核提供的 sys_* 系列辅助函数即可。这些函数基于软件算法实现绘图操作,代码简洁且正确性高。

3.5 内存映射实现

用户空间程序通过 mmap 系统调用直接访问帧缓冲内存。驱动中的 fb_mmap 回调需要将内核分配的虚拟显存映射到用户空间:

static int vfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
    struct vfb_device *vfb = info->par;
    unsigned long size = vma->vm_end - vma->vm_start;

    /* 确保映射大小不超过显存 */
    if (size > vfb->vmem_size)
        return -EINVAL;

    /* 设置内存为 uncached,避免缓存一致性问题 */
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    /* 映射物理页帧到用户空间 */
    if (remap_pfn_range(vma, vma->vm_start,
                        virt_to_phys(vfb->vmem) >> PAGE_SHIFT,
                        size, vma->vm_page_prot))
        return -EAGAIN;

    return 0;
}

3.6 模块注册与注销

驱动入口与出口函数负责平台驱动的注册与注销:

static int __init vfb_init(void)
{
    return platform_driver_register(&vfb_driver);
}

static void __exit vfb_exit(void)
{
    platform_driver_unregister(&vfb_driver);
}

module_init(vfb_init);
module_exit(vfb_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux Display Expert");
MODULE_DESCRIPTION("Virtual Framebuffer Driver for fbdev");

3.7 驱动加载与验证

编译并加载驱动后,可以通过以下步骤验证其工作状态:

  1. 检查设备节点:ls -l /dev/fb* 应出现新的 /dev/fb1 或后续编号。
  2. 查看系统信息:cat /proc/fb 显示已注册的帧缓冲设备列表。
  3. 使用 fbset 工具:fbset -i 查看当前帧缓冲参数。
  4. 直接写屏测试:dd if=/dev/urandom of=/dev/fb1 bs=1024 count=768 填充随机像素。
验证输出示例:
# cat /proc/fb
0 Virtual FB

# fbset -i
mode "1024x768"
    geometry 1024 768 1024 768 32
    timings 0 0 0 0 0 0 0
    accel false
    rgba 8/16,8/8,8/0,8/24
endmode

3.8 与真实硬件驱动的差异对比

对比维度 虚拟帧缓冲驱动 真实硬件帧缓冲驱动
显存来源 kzalloc 分配的系统内存 硬件寄存器映射或 DMA 缓冲区
硬件交互 无,纯软件模拟 需要操作控制寄存器、中断处理
时钟/时序 不需要设置 需要配置 PLL、像素时钟、同步信号
电源管理 通常不需要 需要实现 suspend/resume 回调
调试难度 低,无硬件不确定性 高,需配合逻辑分析仪、示波器
注意事项: 虚拟帧缓冲驱动虽然便于原型验证,但无法模拟真实硬件的时序约束、中断行为以及 DMA 性能特征。在迁移到真实硬件时,务必重新验证显存分配方式、缓存一致性以及寄存器访问顺序。

3.9 小结

本节通过一个完整的虚拟帧缓冲驱动示例,展示了 fbdev 驱动的标准编写流程:从模块入口、平台驱动注册,到 probe 函数中的资源分配与设备注册,再到 fb_ops 的实现。该示例可作为开发真实硬件驱动的起点,帮助开发者快速理解 fbdev 框架的核心机制。