3. FBdev 驱动框架:struct fb_info 结构体详解,fb_ops 回调函数集,注册与注销 fb_info
帧缓冲(FrameBuffer,FBdev)驱动框架的核心是 struct fb_info 结构体。该结构体是驱动开发者与内核帧缓冲层之间的桥梁,它封装了显示设备的硬件信息、操作函数指针、状态标志以及辅助数据结构。理解 fb_info 的布局与生命周期管理,是编写稳定、高效的 FBdev 驱动的基础。
3.1 struct fb_info 结构体详解
struct fb_info 定义在 linux/fb.h 头文件中,是帧缓冲设备在内核中的核心表示。每个注册的帧缓冲设备都对应一个 fb_info 实例。该结构体包含以下关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
fbops |
struct fb_ops * |
指向操作函数集的指针,驱动必须实现 |
fb_var_screeninfo |
struct fb_var_screeninfo |
可变屏幕信息:分辨率、位深、时序等 |
fb_fix_screeninfo |
struct fb_fix_screeninfo |
固定屏幕信息:显存物理地址、长度、行宽 |
screen_base |
void __iomem * |
显存在内核空间的虚拟基地址 |
screen_size |
unsigned long |
显存总大小(字节) |
device |
struct device * |
关联的硬件设备结构体 |
par |
void * |
驱动私有数据指针,用于扩展 |
state |
int |
设备状态标志(如挂起/恢复) |
node |
int |
设备编号(/dev/fb<node>) |
flags |
int |
设备特性标志(如 FBINFO_FLAG_DEFAULT) |
关键字段解读:
fb_var_screeninfo与fb_fix_screeninfo是用户态通过ioctl直接交互的对象,驱动需确保两者的一致性。par字段是驱动私有数据的“万能口袋”,通常用于存放硬件寄存器基址、时钟配置、GPIO 句柄等。screen_base是驱动进行像素操作的核心指针,必须通过ioremap或dma_alloc_coherent获得。
3.1.1 可变信息 vs 固定信息
帧缓冲驱动将显示参数分为“可变”与“固定”两类,这种分离设计使得用户态程序可以动态调整分辨率,而无需重新加载驱动。
| 对比维度 | fb_var_screeninfo(可变) | fb_fix_screeninfo(固定) |
|---|---|---|
| 典型字段 | xres, yres, bits_per_pixel, pixclock |
smem_start, smem_len, line_length, type |
| 修改权限 | 用户态可通过 FBIOPUT_VSCREENINFO 修改 | 只读,由驱动在初始化时设定 |
| 更新时机 | 每次模式切换时更新 | 设备生命周期内不变 |
| 驱动职责 | 校验参数合法性,重新配置硬件时序 | 正确映射显存物理地址与长度 |
驱动开发提示: 在实现
fb_set_par 回调时,必须根据 fb_var_screeninfo 中的新参数重新计算 fb_fix_screeninfo.line_length,并更新硬件寄存器。如果硬件不支持请求的分辨率,应返回 -EINVAL。
3.2 fb_ops 回调函数集
struct fb_ops 是驱动必须实现的操作函数集合,内核帧缓冲层通过该结构体调用驱动提供的硬件操作。每个回调函数都有明确的语义和调用时机。
struct fb_ops {
/* 文件操作 */
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
/* 读取/写入显存(可选) */
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos);
/* 屏幕参数设置 */
int (*fb_check_var)(struct fb_var_screeninfo *var,
struct fb_info *info);
int (*fb_set_par)(struct fb_info *info);
/* 色彩映射表 */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp,
struct fb_info *info);
/* 硬件加速(可选) */
int (*fb_fillrect)(struct fb_info *info,
const struct fb_fillrect *rect);
int (*fb_copyarea)(struct fb_info *info,
const struct fb_copyarea *area);
int (*fb_imageblit)(struct fb_info *info,
const struct fb_image *image);
/* 电源管理 */
int (*fb_blank)(int blank_mode, struct fb_info *info);
/* 扩展 IOCTL */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
};
必须实现的回调: 至少需要实现
fb_check_var、fb_set_par 和 fb_setcolreg。如果硬件不支持硬件加速,fb_fillrect、fb_copyarea、fb_imageblit 可以设置为 NULL,内核会使用软件模拟版本(cfb_fillrect、cfb_copyarea、cfb_imageblit)。
3.2.1 核心回调流程
下图展示了用户态发起模式切换时,内核帧缓冲层与驱动回调之间的交互流程:
3.3 注册与注销 fb_info
帧缓冲设备的生命周期管理通过 register_framebuffer 和 unregister_framebuffer 两个核心函数完成。驱动在探测(probe)阶段注册设备,在移除(remove)阶段注销设备。
3.3.1 注册流程
注册一个帧缓冲设备需要经过以下步骤:
- 分配 fb_info 结构体: 使用
framebuffer_alloc函数分配并初始化fb_info实例。 - 填充固定信息: 设置
fb_fix_screeninfo中的显存地址、长度、行宽等。 - 填充可变信息: 设置默认分辨率、位深、时序参数。
- 设置操作函数集: 将
fb_ops结构体赋值给fb_info->fbops。 - 映射显存: 通过
ioremap或dma_alloc_coherent获得screen_base。 - 调用注册函数: 执行
register_framebuffer(fb_info)将设备注册到系统。
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;
par->dev = &pdev->dev;
/* 2. 设置固定信息 */
info->fix.smem_start = par->fb_phys;
info->fix.smem_len = SZ_4M;
info->fix.line_length = 1024 * (32 / 8);
strscpy(info->fix.id, "myfb", sizeof(info->fix.id));
/* 3. 设置可变信息 */
info->var.xres = 1024;
info->var.yres = 768;
info->var.bits_per_pixel = 32;
/* 4. 设置操作函数 */
info->fbops = &myfb_ops;
/* 5. 映射显存 */
info->screen_base = ioremap(par->fb_phys, SZ_4M);
if (!info->screen_base) {
ret = -ENOMEM;
goto err_free;
}
/* 6. 注册帧缓冲设备 */
ret = register_framebuffer(info);
if (ret < 0) {
dev_err(&pdev->dev, "register_framebuffer failed\n");
goto err_unmap;
}
platform_set_drvdata(pdev, info);
return 0;
err_unmap:
iounmap(info->screen_base);
err_free:
framebuffer_release(info);
return ret;
}
3.3.2 注销流程
注销设备时,必须按照注册的逆序释放资源:
static int myfb_remove(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);
/* 1. 注销帧缓冲设备 */
unregister_framebuffer(info);
/* 2. 取消显存映射 */
iounmap(info->screen_base);
/* 3. 释放 fb_info 结构体 */
framebuffer_release(info);
return 0;
}
注销注意事项:
unregister_framebuffer会等待所有打开该设备的用户态文件描述符关闭后才返回,因此不要在持有自旋锁或中断上下文中调用。- 必须先调用
unregister_framebuffer,再释放screen_base和fb_info,否则可能导致内核访问已释放的内存。 - 如果驱动使用了
dma_alloc_coherent分配显存,应使用dma_free_coherent释放,而不是iounmap。
3.3.3 注册与注销状态机
下图展示了 fb_info 从分配到释放的完整状态转换:
3.4 本章小结
- struct fb_info 是帧缓冲驱动的核心数据结构,包含可变/固定屏幕信息、操作函数集、显存映射和私有数据。
- fb_ops 回调函数集定义了驱动与内核层的交互接口,
fb_check_var和fb_set_par是模式切换的关键路径。 - 注册流程 遵循“分配 → 填充 → 映射 → 注册”的顺序,注销流程 遵循“注销 → 取消映射 → 释放”的逆序。
- 驱动开发者应特别注意
screen_base的正确映射与释放,以及fb_var_screeninfo与fb_fix_screeninfo的一致性维护。