3. fbdev 核心数据结构:struct fb_info、struct fb_var_screeninfo、struct fb_fix_screeninfo
帧缓冲设备驱动(fbdev)的核心在于三个相互关联的数据结构,它们共同构成了用户空间与硬件帧缓冲之间的抽象层。理解这三个结构体是掌握 fbdev 框架的基石。
核心关系:struct fb_info 是驱动层的“总控”结构,它内部包含了描述可变参数的 struct fb_var_screeninfo 和描述固定参数的 struct fb_fix_screeninfo。用户空间通过 ioctl 操作 fb_info,进而读写 var 和 fix 信息。
3.1 struct fb_info:帧缓冲设备的主控结构
struct fb_info 是 fbdev 框架中最重要的数据结构,每个注册的帧缓冲设备都对应一个该结构体的实例。它聚合了设备的所有属性、操作函数指针、底层内存管理以及私有数据。
struct fb_info {
atomic_t count; // 引用计数
int node; // 设备节点号 (如 /dev/fb0 的 0)
int flags; // 设备标志位
struct mutex lock; // 互斥锁,保护并发访问
struct fb_var_screeninfo var; // 可变屏幕信息(用户可修改)
struct fb_fix_screeninfo fix; // 固定屏幕信息(硬件决定)
struct fb_ops *fbops; // 操作函数集(关键)
struct device *device; // 内核设备模型指针
struct device *dev; // 父设备
int class_flag; // 类标志
struct fb_deferred_io *fbdefio; // 延迟 I/O 支持
struct fb_pixmap pixmap; // 像素图辅助
struct fb_pixmap sprite; // 光标/精灵图
struct fb_cursor cursor; // 光标状态
struct fb_monspecs monspecs; // 显示器规格
struct fb_videomode *mode; // 当前视频模式
struct fb_videomode *modelist; // 支持的模式列表
struct fb_ops *fbops; // 操作函数集
char __iomem *screen_base; // 帧缓冲内存的虚拟基地址
unsigned long screen_size; // 帧缓冲内存总大小(字节)
void *pseudo_palette; // 伪调色板
struct fb_info *par; // 驱动私有数据指针
// ... 其他辅助字段
};
关键字段解读:
- screen_base / screen_size:这是驱动映射后的虚拟地址和大小,用户空间通过 mmap 最终映射的就是这片内存。
- fbops:函数指针结构体,驱动必须实现其中的 fb_open、fb_release、fb_setcolreg、fb_blank、fb_pan_display 等回调。
- var / fix:这两个内嵌结构体是用户空间与内核交互的主要数据载体。
- par:驱动私有数据指针,通常指向驱动自定义的结构体,用于存放硬件寄存器地址、中断号等。
3.2 struct fb_var_screeninfo:可变屏幕信息
该结构体描述的是用户空间可以动态修改的显示参数,例如分辨率、颜色深度、时序参数等。当用户通过 FBIOPUT_VSCREENINFO ioctl 修改这些值时,驱动需要重新配置硬件。
struct fb_var_screeninfo {
__u32 xres; // 可见分辨率:水平像素数
__u32 yres; // 可见分辨率:垂直像素数
__u32 xres_virtual; // 虚拟分辨率:水平(可大于 xres,用于双缓冲)
__u32 yres_virtual; // 虚拟分辨率:垂直
__u32 xoffset; // 虚拟到可见的偏移:水平
__u32 yoffset; // 虚拟到可见的偏移:垂直
__u32 bits_per_pixel; // 每像素位数 (bpp)
__u32 grayscale; // 灰度标志
struct fb_bitfield red; // 红色通道位域
struct fb_bitfield green; // 绿色通道位域
struct fb_bitfield blue; // 蓝色通道位域
struct fb_bitfield transp; // 透明度通道位域
__u32 nonstd; // 非标准像素格式标志
__u32 activate; // 激活模式标志
__u32 height; // 显示器物理高度 (mm)
__u32 width; // 显示器物理宽度 (mm)
__u32 accel_flags; // 硬件加速标志
__u32 pixclock; // 像素时钟 (皮秒)
__u32 left_margin; // 行同步前沿
__u32 right_margin; // 行同步后沿
__u32 upper_margin; // 帧同步前沿
__u32 lower_margin; // 帧同步后沿
__u32 hsync_len; // 行同步脉冲长度
__u32 vsync_len; // 帧同步脉冲长度
__u32 sync; // 同步极性标志
__u32 vmode; // 视频模式 (隔行/逐行)
__u32 rotate; // 旋转角度
__u32 reserved[5]; // 保留字段
};
常见陷阱:修改 var 参数时,驱动必须验证时序参数的有效性(如 pixclock 不能为 0,分辨率不能超过硬件限制)。如果驱动不支持动态修改,应在 fb_ops->fb_check_var 中返回 -EINVAL。
3.3 struct fb_fix_screeninfo:固定屏幕信息
该结构体描述的是硬件决定的、用户空间通常不能修改的固定属性,例如帧缓冲物理地址、内存大小、像素格式类型、行跨度等。用户通过 FBIOGET_FSCREENINFO ioctl 获取这些信息。
struct fb_fix_screeninfo {
char id[16]; // 设备标识字符串 (如 "radeonfb")
unsigned long smem_start; // 帧缓冲物理起始地址
__u32 smem_len; // 帧缓冲内存总长度 (字节)
__u32 type; // 显示类型 (如 FB_TYPE_PACKED_PIXELS)
__u32 type_aux; // 辅助类型
__u32 visual; // 视觉类型 (真彩色/伪彩色/直接色)
__u16 xpanstep; // 水平 pan 步进 (0 表示不支持)
__u16 ypanstep; // 垂直 pan 步进
__u16 ywrapstep; // 垂直 wrap 步进
__u32 line_length; // 一行像素的字节数 (跨度)
unsigned long mmio_start; // MMIO 区域物理起始地址
__u32 mmio_len; // MMIO 区域长度
__u32 accel; // 加速器类型
__u16 capabilities; // 能力标志
__u16 reserved[2]; // 保留
};
3.4 var 与 fix 的对比与协作
| 对比维度 | struct fb_var_screeninfo | struct fb_fix_screeninfo |
|---|---|---|
| 修改权限 | 用户空间可通过 ioctl 修改 | 只读,由驱动在初始化时设置 |
| 典型内容 | 分辨率、bpp、时序、偏移 | 物理地址、内存大小、行跨度、视觉类型 |
| 更新时机 | 运行时动态切换(如 fbset 命令) | 驱动 probe 阶段固定,一般不变化 |
| 关联 ioctl | FBIOGET_VSCREENINFO / FBIOPUT_VSCREENINFO | FBIOGET_FSCREENINFO |
| 硬件依赖 | 部分依赖(时序需硬件支持) | 强依赖(物理地址、内存布局) |
3.5 驱动初始化中的典型填充流程
在驱动 probe 函数中,通常按照以下步骤填充这三个核心结构体:
- 分配 fb_info:调用 framebuffer_alloc() 分配 struct fb_info 实例,同时分配私有数据 par。
- 填充 fix 信息:设置 smem_start(物理地址)、smem_len(内存大小)、type、visual、line_length 等固定参数。
- 填充 var 默认值:设置默认分辨率、bpp、时序参数,确保 fb_check_var 能通过验证。
- 注册 fbops:将驱动实现的 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;
// 1. 分配 fb_info 和私有数据
info = framebuffer_alloc(sizeof(struct myfb_par), &pdev->dev);
if (!info)
return -ENOMEM;
par = info->par;
// 2. 填充固定信息
strcpy(info->fix.id, "myfb");
info->fix.smem_start = par->fb_phys;
info->fix.smem_len = SZ_4M;
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.line_length = 1024 * (32 / 8); // 1024x768, 32bpp
// 3. 填充可变信息默认值
info->var.xres = 1024;
info->var.yres = 768;
info->var.bits_per_pixel = 32;
info->var.xres_virtual = 1024;
info->var.yres_virtual = 768;
// ... 填充时序参数
// 4. 设置操作函数集
info->fbops = &myfb_ops;
// 5. 映射帧缓冲内存
info->screen_base = ioremap_wc(par->fb_phys, SZ_4M);
info->screen_size = SZ_4M;
// 6. 注册设备
if (register_framebuffer(info) < 0) {
iounmap(info->screen_base);
framebuffer_release(info);
return -EINVAL;
}
platform_set_drvdata(pdev, info);
return 0;
}
初始化顺序要点:
- 必须先填充 fix 和 var,再调用 register_framebuffer(),因为注册时内核会立即调用 fb_set_par() 应用参数。
- screen_base 必须在注册前映射完成,否则用户空间 mmap 会失败。
- fb_check_var 回调必须验证 var 中的所有参数,包括时序、分辨率、bpp 的合法性。
3.6 用户空间视角:通过 ioctl 访问
用户空间程序通过打开 /dev/fbX 设备文件,使用 ioctl 系统调用与这三个结构体交互:
| ioctl 命令 | 操作对象 | 功能描述 |
|---|---|---|
| FBIOGET_FSCREENINFO | struct fb_fix_screeninfo | 获取固定信息(物理地址、行跨度等) |
| FBIOGET_VSCREENINFO | struct fb_var_screeninfo | 获取当前可变参数 |
| FBIOPUT_VSCREENINFO | struct fb_var_screeninfo | 设置新的可变参数(触发模式切换) |
| FBIOGET_VBLANK | struct fb_vblank | 获取垂直消隐状态(用于同步) |
| FBIO_WAITFORVSYNC | __u32 crtc | 等待垂直同步信号 |
实用技巧:使用 fbset 命令可以快速查看和修改 var 参数。例如
fbset -fb /dev/fb0 -xres 1920 -yres 1080 -v 会触发 FBIOPUT_VSCREENINFO,驱动需要实现对应的 fb_set_par 回调来重新配置硬件时序。