4. 帧缓冲驱动注册与注销:platform_driver框架、register_framebuffer()/unregister_framebuffer()函数详解

帧缓冲设备驱动在Linux内核中通常以platform_driver的形式存在,通过标准的驱动模型完成设备的匹配、探测与移除。本章将深入剖析驱动注册与注销的核心机制,重点讲解register_framebuffer()与unregister_framebuffer()两个关键API的工作流程与使用规范。

4.1 platform_driver框架与帧缓冲驱动的绑定

嵌入式系统中,LCD控制器通常作为平台设备挂载在系统总线上。帧缓冲驱动通过struct platform_driver结构体与内核驱动模型对接,实现设备的自动探测与资源管理。

核心数据结构: struct platform_driver 包含 probe()、remove()、shutdown() 等回调函数,以及 id_table 用于设备匹配。
/* 典型的帧缓冲platform_driver定义 */
static struct platform_driver fb_platform_driver = {
    .probe  = fb_probe,
    .remove = fb_remove,
    .shutdown = fb_shutdown,
    .driver = {
        .name = "my_fb_driver",
        .of_match_table = of_match_ptr(fb_of_match),
    },
};

/* 模块入口/出口宏 */
module_platform_driver(fb_platform_driver);

下表列出了platform_driver各回调函数在帧缓冲驱动中的典型职责:

回调函数 触发时机 帧缓冲驱动中的典型操作
probe() 设备与驱动匹配成功时 分配fb_info、初始化硬件、注册帧缓冲设备
remove() 设备移除或驱动卸载时 注销帧缓冲设备、释放内存、关闭硬件
shutdown() 系统关机或重启时 关闭显示控制器、进入低功耗状态
suspend() 系统进入休眠时 保存寄存器状态、关闭时钟
resume() 系统从休眠唤醒时 恢复寄存器状态、重新使能显示
最佳实践: 在 probe() 中优先使用 devm_* 系列API(如 devm_kzalloc()、devm_ioremap_resource()),可以简化错误处理路径,避免资源泄漏。

4.2 register_framebuffer() 函数详解

register_framebuffer() 是帧缓冲驱动的核心注册函数,它将一个已填充好的 struct fb_info 实例注册到内核帧缓冲子系统,并创建对应的 /dev/fbX 设备节点。

/* register_framebuffer 函数原型 */
extern int register_framebuffer(struct fb_info *fb_info);

该函数的执行流程可分为以下关键步骤:

  1. 参数校验: 检查 fb_info 指针及 fbops 是否为空,校验 var 结构中的分辨率、位深等参数合法性。
  2. 分配设备号: 调用 fb_alloc_chrdev() 或使用固定主设备号 FB_MAJOR (29),分配次设备号。
  3. 创建字符设备: 通过 cdev_init() 和 cdev_add() 将帧缓冲设备注册到VFS层。
  4. 创建设备节点: 在 /dev 目录下生成 fb0、fb1 等设备文件。
  5. 加入全局链表: 将 fb_info 添加到内核维护的 registered_fb 数组中,索引即为 fb_info->node。
  6. 通知上层: 发送 FB_EVENT_FB_REGISTERED 事件,通知用户空间或其他内核模块。
注意: 调用 register_framebuffer() 之前,必须确保 fb_info 中的 fbops、var、fix、screen_base、screen_size 等字段已正确初始化。注册成功后,驱动不应再修改 fb_info->node 字段。
register_framebuffer() 内部执行流程 入口:参数校验 分配设备号 (FB_MAJOR) cdev_init + cdev_add 创建 /dev/fbX 设备节点 加入 registered_fb 数组 发送 FB_EVENT_FB_REGISTERED 注册成功后,fb_info->node 记录次设备号,驱动可通过 fb_info->node 访问设备

4.3 unregister_framebuffer() 函数详解

unregister_framebuffer() 是注册的逆操作,在驱动卸载或设备移除时调用,负责从系统中移除帧缓冲设备并释放相关资源。

/* unregister_framebuffer 函数原型 */
extern int unregister_framebuffer(struct fb_info *fb_info);

该函数的执行流程如下:

  • 检查状态: 确认 fb_info 当前处于已注册状态,防止重复注销。
  • 通知控制台: 调用 fbcon_fb_unregistered() 通知帧缓冲控制台子系统,切换或关闭控制台显示。
  • 移除设备节点: 删除 /dev/fbX 设备文件,销毁对应的 cdev 设备。
  • 从全局链表移除: 将 fb_info 从 registered_fb 数组中清除,对应位置置为 NULL。
  • 释放资源: 释放设备号,调用 fb_destroy_modelist() 清理模型链表。
  • 发送事件: 发送 FB_EVENT_FB_UNREGISTERED 事件。
关键差异对比: register_framebuffer() 与 unregister_framebuffer() 的对称性设计
操作阶段 register_framebuffer() unregister_framebuffer()
设备号 分配次设备号 释放次设备号
字符设备 cdev_add 注册 cdev_del 删除
设备节点 device_create 创建 device_destroy 销毁
全局数组 写入 registered_fb[index] registered_fb[index] = NULL
事件通知 FB_EVENT_FB_REGISTERED FB_EVENT_FB_UNREGISTERED
控制台处理 fbcon_fb_registered fbcon_fb_unregistered

4.4 probe() 与 remove() 中的典型调用序列

在 platform_driver 的 probe() 和 remove() 回调中,帧缓冲驱动的注册与注销遵循固定的调用顺序,确保资源管理的正确性。

/* probe() 中的典型注册序列 */
static int fb_probe(struct platform_device *pdev)
{
    struct fb_info *fbi;
    struct myfb_par *par;
    int ret;

    /* 1. 分配 fb_info 结构体 */
    fbi = framebuffer_alloc(sizeof(struct myfb_par), &pdev->dev);
    if (!fbi)
        return -ENOMEM;

    par = fbi->par;
    platform_set_drvdata(pdev, fbi);

    /* 2. 初始化 fb_info 字段 */
    fbi->fbops = &myfb_ops;
    fbi->var = myfb_default_var;
    fbi->fix = myfb_default_fix;

    /* 3. 申请IO内存并映射 */
    par->regs = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(par->regs)) {
        ret = PTR_ERR(par->regs);
        goto err_free_fb;
    }

    /* 4. 分配帧缓冲内存 */
    fbi->screen_size = myfb_default_var.xres *
                       myfb_default_var.yres *
                       myfb_default_var.bits_per_pixel / 8;
    fbi->screen_base = dma_alloc_coherent(fbi->device,
                                           fbi->screen_size,
                                           &par->dma_handle,
                                           GFP_KERNEL);
    if (!fbi->screen_base) {
        ret = -ENOMEM;
        goto err_free_fb;
    }

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

    dev_info(&pdev->dev, "fb%d registered successfully\n", fbi->node);
    return 0;

err_free_dma:
    dma_free_coherent(fbi->device, fbi->screen_size,
                      fbi->screen_base, par->dma_handle);
err_free_fb:
    framebuffer_release(fbi);
    return ret;
}

/* remove() 中的典型注销序列 */
static int fb_remove(struct platform_device *pdev)
{
    struct fb_info *fbi = platform_get_drvdata(pdev);
    struct myfb_par *par = fbi->par;

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

    /* 2. 释放DMA内存 */
    dma_free_coherent(fbi->device, fbi->screen_size,
                      fbi->screen_base, par->dma_handle);

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

    return 0;
}
probe() 与 remove() 资源管理对称性 probe() 注册序列 ① framebuffer_alloc() ② 初始化 fbops/var/fix ③ 申请IO内存 + DMA内存 ④ register_framebuffer() remove() 注销序列 ① unregister_framebuffer() ② 释放DMA内存 ③ 释放IO内存 (devm自动) ④ framebuffer_release() 对称原则:remove() 以 probe() 的逆序释放资源,确保无内存泄漏 devm_* 资源由内核自动释放,无需在 remove() 中显式处理

4.5 常见错误与调试技巧

  • 重复注册: 多次调用 register_framebuffer() 会导致内核警告,应在调用前检查 fb_info->node 是否为 -1。
  • 注销顺序错误: 先释放 screen_base 再调用 unregister_framebuffer(),可能导致控制台访问已释放的内存,引发内核崩溃。
  • 设备节点残留: 驱动卸载后 /dev/fbX 仍存在,通常是因为 unregister_framebuffer() 未被正确调用。
  • 调试方法: 使用 ls /dev/fb* 查看设备节点;通过 cat /proc/fb 查看已注册的帧缓冲设备列表;使用 dmesg 查看驱动注册/注销日志。
调试命令速查:
# cat /proc/fb — 显示已注册的帧缓冲设备列表
# ls -l /dev/fb* — 查看帧缓冲设备节点
# dmesg | grep fb — 过滤帧缓冲相关内核日志