3. 核心数据结构 fb_info:struct fb_info 结构体详解
struct fb_info 是 Framebuffer 驱动中最重要的核心数据结构,它完整描述了一个帧缓冲设备的所有属性、操作函数指针、硬件参数、状态信息以及辅助功能。驱动开发者在 probe 阶段的主要工作,就是分配、填充并注册这个结构体实例。理解其每个成员变量的作用与配置时机,是成功移植适配 Framebuffer 驱动的关键。
核心原则:
fb_info 是驱动与内核 Framebuffer 子系统之间的唯一契约。所有用户空间通过 /dev/fbX 发起的操作,最终都会映射到这个结构体中的函数指针和数据成员上。
3.1 struct fb_info 整体架构
该结构体定义在 include/linux/fb.h 中,包含数十个成员。按功能可划分为以下六大类:
3.2 关键成员变量详解与配置时机
以下表格列出了 struct fb_info 中最核心的成员变量,以及它们在驱动生命周期中的配置时机:
| 成员变量 | 类型 | 作用描述 | 配置时机 |
|---|---|---|---|
fbops |
struct fb_ops * |
指向操作函数集,包含 open/release/ioctl/pan_display/fillrect 等回调 | probe 阶段:静态赋值,注册后不可更改 |
fix |
struct fb_fix_screeninfo |
固定屏幕信息:物理地址、内存长度、行宽、类型等硬件固定参数 | probe 阶段:硬件探测后一次性填充 |
var |
struct fb_var_screeninfo |
可变屏幕信息:分辨率、位深、时序、虚拟分辨率等用户可调参数 | probe 阶段:设置默认值;运行时:通过 FBIOGET_VSCREENINFO/FBIOPUT_VSCREENINFO 动态修改 |
screen_base |
char __iomem * |
帧缓冲在 CPU 虚拟地址空间中的映射基址,用户 mmap 的最终目标 | probe 阶段:ioremap() 或 dma_alloc_coherent() 后赋值 |
screen_size |
unsigned long |
帧缓冲内存的总大小(字节),必须与 fix.smem_len 一致 | probe 阶段:内存分配完成后设定 |
pseudo_palette |
u32 * |
伪调色板数组,用于 8/16 位色深下的颜色查找表加速 | probe 阶段:分配内存;运行时:通过 FBIOPUTCMAP 更新 |
par |
void * |
驱动私有数据指针,通常指向包含硬件寄存器基址、时钟、GPIO 等信息的自定义结构体 | probe 阶段:分配并初始化后赋值 |
dev |
struct device * |
指向平台设备或 PCI 设备的 device 结构体,用于 DMA/电源管理/设备树关联 | probe 阶段:由 platform_set_drvdata() 或注册时自动关联 |
fbcon_par |
struct fbcon_ops * |
控制台操作辅助结构体,用于 fbcon 软光标、滚动加速等 | 首次打开控制台时:由 fbcon 模块按需分配 |
fbdefio |
struct fb_deferred_io * |
延迟 IO 支持结构体,用于不需要实时刷新的显示设备(如 eInk、SPI 屏) | probe 阶段:按需初始化;非必须 |
state |
int |
当前显示状态:FBINFO_STATE_RUNNING / FBINFO_STATE_SUSPENDED | probe 阶段:设为 RUNNING;电源管理:suspend/resume 时切换 |
flags |
u32 |
特性标志位,如 FBINFO_FLAG_DEFAULT / FBINFO_HWACCEL_* 硬件加速标志 | probe 阶段:按硬件能力设置位掩码 |
实战提示:在移植适配时,
par 是最容易被忽视但最重要的成员。建议在自定义结构体中包含 struct fb_info *info 反向指针,方便在中断或回调中快速访问 fb_info。
3.3 fb_ops 操作函数集详解
struct fb_ops 是驱动与内核交互的执行引擎。以下是最常用的回调函数及其配置要求:
| 回调函数 | 原型 | 必须实现? | 典型实现内容 |
|---|---|---|---|
fb_open |
int (*fb_open)(struct fb_info *, int user) |
否 | 引用计数增加、硬件唤醒、电源管理 |
fb_release |
int (*fb_release)(struct fb_info *, int user) |
否 | 引用计数减少、延迟关闭计时器 |
fb_check_var |
int (*fb_check_var)(struct fb_var_screeninfo *, struct fb_info *) |
是 | 验证用户传入的 var 参数是否合法,修正为硬件支持的值 |
fb_set_par |
int (*fb_set_par)(struct fb_info *) |
是 | 根据 var 配置硬件寄存器:时钟、时序、位深、DMA 地址 |
fb_setcolreg |
int (*fb_setcolreg)(unsigned, unsigned, unsigned, unsigned, unsigned, struct fb_info *) |
否(伪彩色必须) | 更新硬件 CLUT 或 pseudo_palette 数组 |
fb_pan_display |
int (*fb_pan_display)(struct fb_var_screeninfo *, struct fb_info *) |
否 | 更新显示起始偏移地址,实现虚拟分辨率平滑滚动 |
fb_blank |
int (*fb_blank)(int, struct fb_info *) |
否 | 控制显示面板的开关/背光/休眠状态 |
fb_ioctl |
int (*fb_ioctl)(struct fb_info *, unsigned int, unsigned long) |
否 | 处理自定义私有 ioctl 命令 |
fb_mmap |
int (*fb_mmap)(struct fb_info *, struct vm_area_struct *) |
否 | 自定义 mmap 行为,默认使用 fb_deferred_io_mmap 或 dma_mmap_coherent |
常见错误:很多移植驱动只实现了
fb_set_par 而忽略了 fb_check_var。这会导致用户空间设置非法分辨率时,驱动直接使用未经验证的参数配置硬件,轻则显示异常,重则损坏 LCD 控制器时序。
3.4 fix 与 var 的配置流程
这两个结构体是用户空间获取/修改显示参数的直接接口。它们的配置遵循严格的先后顺序:
3.5 典型 probe 函数中的 fb_info 初始化代码
以下代码展示了一个标准 LCD 控制器驱动在 probe 阶段初始化 fb_info 的完整流程:
static int myfb_probe(struct platform_device *pdev)
{
struct fb_info *info;
struct myfb_par *par;
struct resource *res;
int ret;
// 1. 分配 fb_info + par 私有数据(一次性分配)
info = framebuffer_alloc(sizeof(struct myfb_par), &pdev->dev);
if (!info)
return -ENOMEM;
par = info->par;
par->info = info; // 反向指针
platform_set_drvdata(pdev, info);
// 2. 获取硬件资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
par->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(par->regs)) {
ret = PTR_ERR(par->regs);
goto err_release;
}
// 3. 分配帧缓冲内存(DMA 连续内存)
info->screen_base = dma_alloc_coherent(&pdev->dev,
SZ_4M,
&par->dma_handle,
GFP_KERNEL);
if (!info->screen_base) {
ret = -ENOMEM;
goto err_release;
}
info->screen_size = SZ_4M;
// 4. 填充 fix 固定信息
strlcpy(info->fix.id, "myfb", sizeof(info->fix.id));
info->fix.smem_start = par->dma_handle;
info->fix.smem_len = SZ_4M;
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.line_length = 800 * 4; // 800x480, 32bpp
// 5. 设置默认 var 可变信息
info->var.xres = 800;
info->var.yres = 480;
info->var.xres_virtual = 800;
info->var.yres_virtual = 480;
info->var.bits_per_pixel = 32;
info->var.red.offset = 16;
info->var.red.length = 8;
info->var.green.offset = 8;
info->var.green.length = 8;
info->var.blue.offset = 0;
info->var.blue.length = 8;
info->var.transp.offset = 24;
info->var.transp.length = 8;
info->var.activate = FB_ACTIVATE_NOW;
// 6. 绑定操作函数集
info->fbops = &myfb_ops;
// 7. 设置标志
info->flags = FBINFO_FLAG_DEFAULT;
// 8. 注册 framebuffer
ret = register_framebuffer(info);
if (ret < 0)
goto err_free_dma;
dev_info(&pdev->dev, "myfb registered: 800x480, 32bpp\n");
return 0;
err_free_dma:
dma_free_coherent(&pdev->dev, SZ_4M,
info->screen_base, par->dma_handle);
err_release:
framebuffer_release(info);
return ret;
}
关键点:使用
framebuffer_alloc() 而非直接 kzalloc() 分配 fb_info。前者会正确初始化内部引用计数、等待队列和锁,并自动将 par 指针指向结构体末尾的私有数据区域。
3.6 成员变量配置避坑指南
fix.smem_start与screen_base的一致性:smem_start必须是物理地址(DMA 或总线地址),而screen_base是 CPU 虚拟地址。两者必须指向同一块物理内存,否则 mmap 后用户空间看到的是错误数据。fix.line_length的计算:必须考虑硬件对齐要求。例如 800x480 32bpp,如果硬件要求每行 128 字节对齐,则line_length = ALIGN(800 * 4, 128),而非简单的800 * 4。var的时序参数:对于 RGB 接口 LCD,必须正确设置var中的pixclock、left_margin、right_margin、upper_margin、lower_margin、hsync_len、vsync_len。这些值通常来自设备树或硬件手册,错误设置会导致无显示或闪烁。pseudo_palette的分配时机:如果驱动支持 8/16 位色深,必须在fb_set_par中根据var.bits_per_pixel动态分配pseudo_palette数组(16 个u32),并在fb_setcolreg中更新。fbops的生命周期:fbops指向的结构体必须在fb_info生命周期内始终有效。建议定义为static const或在 probe 中分配、remove 中释放。
严重警告:不要在
fb_set_par 中重新分配 screen_base 或改变 fix.smem_start。这些值在注册后应视为不可变,否则会导致已建立的 mmap 映射失效,引发内核 panic 或用户空间段错误。
3.7 总结:fb_info 初始化检查清单
| 检查项 | 要求 | 验证方法 |
|---|---|---|
framebuffer_alloc |
使用专用分配函数,而非 kzalloc | 检查代码中是否包含 framebuffer_alloc |
fbops 完整性 |
至少实现 fb_check_var 和 fb_set_par |
编译无警告,运行时 cat /sys/class/graphics/fb0/ 可读 |
fix 与 screen_base 一致性 |
物理地址与虚拟地址指向同一内存 | 对比 fix.smem_start 与 virt_to_phys(screen_base) |
var 默认值合法性 |
分辨率、位深、时序在硬件支持范围内 | 调用 fb_check_var 后无错误返回 |
par 反向指针 |
私有数据中包含 struct fb_info * |
中断处理函数中可通过 par->info 访问 fb_info |
| 错误路径清理 | 分配失败时正确释放所有资源 | 注入内存分配失败,检查 kmemleak 无泄漏 |