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 中,包含数十个成员。按功能可划分为以下六大类:

struct fb_info 核心成员分类架构 struct fb_info ① 设备模型与锁 dev / class_flag / lock ② 固定与可变参数 fix / var ③ 操作函数集 fbops ④ 屏幕与颜色 screen_base / pseudo_palette ⑤ 驱动私有数据 par / fbcon_par ⑥ 辅助功能 fbdefio / tileops 配置时机:probe() 中分配 → 逐一填充 → fb_set_var() 动态更新 → register_framebuffer() 注册 部分成员(如 var)可在运行时通过 ioctl 修改,但 fix 和 fbops 通常在 probe 阶段一次性设定

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 的配置流程

这两个结构体是用户空间获取/修改显示参数的直接接口。它们的配置遵循严格的先后顺序:

fb_info.fix 与 fb_info.var 配置流程 ① 硬件探测 读取设备树/PCI 配置 ② 填充 fix smem_start / smem_len / line_length ③ 设置默认 var xres / yres / bits_per_pixel ④ fb_check_var 验证 修正为硬件支持的值 ⑤ fb_set_par 配置硬件 写寄存器:时钟/时序/DMA ⑥ register_framebuffer 注册到内核子系统 运行时循环:用户空间 FBIOPUT_VSCREENINFO fb_check_var → fb_set_par → 硬件重配置 → 返回用户空间

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 无泄漏