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() 函数完成绑定。
struct fb_info,调用 register_framebuffer() 将设备注册到 fbmem.c 维护的全局链表 registered_fb 中。fbmem.c 随后创建设备节点 /dev/fbX,并将用户空间的系统调用(open、read、write、ioctl、mmap)通过 fb_ops 回调到具体驱动。
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 驱动的核心入口,负责完成以下关键步骤:
- 获取硬件资源: 从
platform_device中提取 I/O 内存基址、中断号、时钟等资源。 - 分配 fb_info 结构体: 调用
framebuffer_alloc()分配struct fb_info及其私有数据区。 - 初始化 fb_ops: 填充
struct fb_ops回调函数表,包括fb_open、fb_release、fb_setcolreg、fb_blank、fb_pan_display等。 - 设置可变/固定参数: 填充
fb_var_screeninfo和fb_fix_screeninfo,描述分辨率、像素格式、显存地址等。 - 分配 DMA 缓冲区: 使用
dma_alloc_coherent()或alloc_pages_exact()分配连续物理内存作为显存。 - 注册帧缓冲设备: 调用
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;
}
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;
}
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()释放。