3. Framebuffer驱动基础框架:platform_driver与fbmem.c的交互、probe函数实现、file_operations注册

Framebuffer 驱动在 Linux 内核中属于字符设备驱动范畴,但其注册与交互方式具有鲜明的框架特征。本章聚焦驱动与内核帧缓冲核心层 fbmem.c 的协作机制,详细拆解 platform_driver 的绑定流程、probe 函数的实现要点以及 file_operations 的注册路径。理解这三部分,是掌握 Framebuffer 驱动从设备树匹配到用户空间 mmap 映射的完整链路的基础。

3.1 驱动框架全景:platform_driver 与 fbmem.c 的交互

Framebuffer 驱动采用分层架构:底层是硬件相关的 platform_driver,负责初始化控制器、分配显存;上层是内核通用帧缓冲核心 fbmem.c,负责设备号管理、file_operations 分发以及 ioctl 处理。两者通过 register_framebuffer() 函数完成绑定。

核心交互流程: platform_driver 在 probe 中填充 struct fb_info,调用 register_framebuffer() 将设备注册到 fbmem.c 维护的全局链表 registered_fb 中。fbmem.c 随后创建设备节点 /dev/fbX,并将用户空间的系统调用(open、read、write、ioctl、mmap)通过 fb_ops 回调到具体驱动。
platform_driver 与 fbmem.c 交互架构 用户空间应用 系统调用层 (VFS) fbmem.c 核心层 platform_driver 驱动层 (probe / remove / suspend / resume) struct fb_info (fb_ops / screen_base / var) LCD 控制器 / DMA 缓冲区 关键注册函数 register_framebuffer(struct fb_info *fb_info) unregister_framebuffer(struct fb_info *fb_info)

3.2 platform_driver 的注册与匹配

现代嵌入式 Framebuffer 驱动通常基于 platform_driver 框架,通过设备树(Device Tree)或 ACPI 完成硬件描述与驱动绑定。驱动开发者只需实现 probe 和 remove 回调,内核平台总线会自动完成匹配。

组件 作用 关键字段/函数
platform_driver 定义驱动的 probe/remove 回调及匹配表 .driver.of_match_table
of_device_id 设备树 compatible 字符串匹配表 { .compatible = "vendor,lcd-controller" }
platform_device 由设备树节点或板级代码生成 struct resource * 获取 I/O 内存
module_platform_driver 宏封装 module_init / module_exit module_platform_driver(xxx_driver)
最佳实践: 使用 module_platform_driver() 宏可以避免手动编写 module_init 和 module_exit 样板代码。该宏在驱动加载时自动注册 platform_driver,卸载时自动注销。

3.3 probe 函数实现:从设备树到 fb_info

probe 函数是 Framebuffer 驱动的核心入口,负责完成以下关键步骤:

  1. 获取硬件资源: 从 platform_device 中提取 I/O 内存基址、中断号、时钟等资源。
  2. 分配 fb_info 结构体: 调用 framebuffer_alloc() 分配 struct fb_info 及其私有数据区。
  3. 初始化 fb_ops: 填充 struct fb_ops 回调函数表,包括 fb_open、fb_release、fb_setcolreg、fb_blank、fb_pan_display 等。
  4. 设置可变/固定参数: 填充 fb_var_screeninfo 和 fb_fix_screeninfo,描述分辨率、像素格式、显存地址等。
  5. 分配 DMA 缓冲区: 使用 dma_alloc_coherent() 或 alloc_pages_exact() 分配连续物理内存作为显存。
  6. 注册帧缓冲设备: 调用 register_framebuffer() 将 fb_info 注册到系统。
static int lcdfb_probe(struct platform_device *pdev)
{
    struct fb_info *info;
    struct lcdfb_par *par;
    struct resource *res;
    int ret;

    /* 1. 获取 I/O 内存资源 */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENODEV;

    /* 2. 分配 fb_info + 私有数据 */
    info = framebuffer_alloc(sizeof(struct lcdfb_par), &pdev->dev);
    if (!info)
        return -ENOMEM;

    par = info->par;
    par->regs = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(par->regs)) {
        ret = PTR_ERR(par->regs);
        goto err_free_info;
    }

    /* 3. 初始化 fb_ops */
    info->fbops = &lcdfb_ops;

    /* 4. 设置屏幕参数 */
    info->var.xres = 800;
    info->var.yres = 480;
    info->var.bits_per_pixel = 32;
    info->fix.line_length = info->var.xres * (info->var.bits_per_pixel / 8);

    /* 5. 分配 DMA 缓冲区 */
    par->screen_size = info->var.xres * info->var.yres *
                       (info->var.bits_per_pixel / 8);
    par->screen_base = dma_alloc_coherent(info->device,
                                           par->screen_size,
                                           &par->screen_dma,
                                           GFP_KERNEL);
    if (!par->screen_base) {
        ret = -ENOMEM;
        goto err_free_info;
    }
    info->screen_base = par->screen_base;
    info->fix.smem_start = par->screen_dma;
    info->fix.smem_len = par->screen_size;

    /* 6. 注册帧缓冲设备 */
    ret = register_framebuffer(info);
    if (ret < 0)
        goto err_free_dma;

    platform_set_drvdata(pdev, info);
    dev_info(&pdev->dev, "LCD Framebuffer registered\n");
    return 0;

err_free_dma:
    dma_free_coherent(info->device, par->screen_size,
                      par->screen_base, par->screen_dma);
err_free_info:
    framebuffer_release(info);
    return ret;
}
probe 函数执行流程 probe 函数入口 platform_get_resource() framebuffer_alloc() 填充 fb_ops 回调表 dma_alloc_coherent() register_framebuffer() 返回 0 (成功) 错误处理路径 framebuffer_release() dma_free_coherent() 返回负错误码

3.4 file_operations 注册:fbmem.c 的分发机制

Framebuffer 驱动不需要直接注册 file_operations 结构体。fbmem.c 在初始化时已经注册了主设备号 FB_MAJOR (29) 和统一的 fb_fops。当用户空间打开 /dev/fb0 时,fbmem.c 根据次设备号找到对应的 fb_info,然后调用 fb_info->fbops 中的具体回调。

系统调用 fbmem.c 处理函数 驱动 fb_ops 回调 典型实现
open fb_open() fb_ops->fb_open 时钟使能、电源管理
release fb_release() fb_ops->fb_release 时钟关闭、引用计数递减
read fb_read() 直接操作 screen_base 从显存拷贝到用户空间
write fb_write() 直接操作 screen_base 从用户空间拷贝到显存
ioctl fb_ioctl() fb_ops->fb_ioctl 设置分辨率、色彩深度
mmap fb_mmap() fb_ops->fb_mmap 将 DMA 缓冲区映射到用户空间
注意: 驱动开发者不需要也不应该直接调用 register_chrdev() 或 cdev_add() 来注册字符设备。fbmem.c 已经完成了主设备号的注册和 file_operations 的分发。驱动只需通过 register_framebuffer() 将 fb_info 挂入内核链表即可。

3.5 fb_ops 回调表:驱动与核心层的契约

struct fb_ops 是驱动与 fbmem.c 之间的核心契约。驱动必须实现 fb_fillrect、fb_copyarea、fb_imageblit 这三个加速函数,否则内核会使用软实现(效率较低)。

static struct fb_ops lcdfb_ops = {
    .owner          = THIS_MODULE,
    .fb_open        = lcdfb_open,
    .fb_release     = lcdfb_release,
    .fb_read        = fb_sys_read,      /* 使用系统辅助函数 */
    .fb_write       = fb_sys_write,
    .fb_check_var   = lcdfb_check_var,
    .fb_set_par     = lcdfb_set_par,
    .fb_setcolreg   = lcdfb_setcolreg,
    .fb_blank       = lcdfb_blank,
    .fb_pan_display = lcdfb_pan_display,
    .fb_fillrect    = cfb_fillrect,      /* 使用内核通用加速 */
    .fb_copyarea    = cfb_copyarea,
    .fb_imageblit   = cfb_imageblit,
    .fb_mmap        = lcdfb_mmap,        /* 自定义 mmap 实现 */
};
关键点: fb_mmap 回调是后续章节(mmap 映射与 DMA 缓冲区管理)的核心。驱动在此回调中调用 dma_mmap_coherent() 或 remap_pfn_range(),将内核 DMA 缓冲区映射到用户空间。如果驱动不实现 fb_mmap,fbmem.c 会使用默认的 fb_mmap_default(),该函数只能映射 ioremap 得到的 I/O 内存,无法正确处理 DMA 一致性缓冲区。

3.6 驱动卸载与资源清理

remove 函数是 probe 的逆操作,必须确保所有资源被正确释放,避免内存泄漏或内核崩溃。

static int lcdfb_remove(struct platform_device *pdev)
{
    struct fb_info *info = platform_get_drvdata(pdev);
    struct lcdfb_par *par = info->par;

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

    /* 2. 释放 DMA 缓冲区 */
    dma_free_coherent(info->device, par->screen_size,
                      par->screen_base, par->screen_dma);

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

    return 0;
}
Framebuffer 驱动生命周期 module_init probe 执行 设备运行中 remove 执行 module_exit 资源释放顺序 1. unregister_framebuffer() 2. dma_free_coherent() 3. framebuffer_release()

3.7 本章小结

  • 分层架构: platform_driver 负责硬件初始化,fbmem.c 负责设备节点与系统调用分发,两者通过 register_framebuffer() 解耦。
  • probe 函数: 是驱动初始化的核心,必须按顺序完成资源获取、fb_info 分配、fb_ops 填充、DMA 缓冲区分配和注册。
  • file_operations: 驱动不直接注册字符设备,而是通过 fb_ops 回调表间接提供服务。fbmem.c 作为代理层,将用户空间请求路由到具体驱动。
  • 资源管理: probe 中分配的资源必须在 remove 中逆序释放,特别是 DMA 缓冲区必须使用对应的 dma_free_coherent() 释放。