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回调)
- 分配fb_info:调用
framebuffer_alloc(size, device)分配fb_info及其私有数据空间。 - 填充fb_info:设置
fbops、var、fix、screen_base、screen_size等字段。 - 硬件初始化:映射寄存器、申请中断、初始化控制器、分配DMA缓冲区。
- 注册设备:调用
register_framebuffer(info)将设备注册到内核,生成/dev/fbX节点。 - 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回调)
- 注销设备:调用
unregister_framebuffer(info)移除设备节点。 - 硬件去初始化:释放中断、取消映射寄存器、关闭时钟、释放DMA缓冲区。
- 释放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 回调由内核设备模型在设备和驱动匹配成功后自动调用。
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由内核自动调用。