3. FBDEV 驱动架构回顾:Linux FBDEV 框架
在切入 DRM/KMS 之前,必须彻底理解 FBDEV(Framebuffer Device)的运作机制。FBDEV 是 Linux 内核中最早的标准显示框架,其核心思想是将显示缓冲区抽象为一个字符设备,用户空间通过 mmap 直接操作帧缓冲,实现图形输出。本章将深入分析 fbmem.c 核心层、fb_ops 结构体、注册/注销流程以及关键操作 mmap 与 pan_display 的实现细节。
3.1 FBDEV 整体架构
FBDEV 采用分层设计,从上到下依次为:用户空间应用程序、字符设备层(fbmem.c)、硬件驱动层(fb_ops)以及物理显示硬件。核心文件 fbmem.c 负责设备注册、文件操作接口(open/read/write/mmap/ioctl)的注册与分发,而具体的硬件操作则委托给驱动实现的 fb_ops 结构体。
/dev/fb0、/dev/fb1 等设备节点。
3.2 fbmem.c 核心层分析
fbmem.c 位于 drivers/video/fbdev/core/ 目录下,是 FBDEV 框架的中枢。它主要完成以下工作:
- 设备管理: 维护全局的
registered_fb[]数组,最多支持FB_MAX(32)个帧缓冲设备。 - 文件操作接口: 实现
fb_fops结构体,提供open/read/write/mmap/ioctl等标准字符设备操作。 - ioctl 分发: 处理
FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIO_PAN_DISPLAY等标准命令。 - 注册/注销: 提供
register_framebuffer()和unregister_framebuffer()供驱动调用。
struct fb_info 是核心结构体,包含 fb_var_screeninfo(可变参数)、fb_fix_screeninfo(固定参数)、fb_ops(操作函数指针)以及 screen_base(帧缓冲虚拟地址)等关键成员。
3.3 fb_ops 结构体详解
fb_ops 是驱动开发者需要实现的核心接口,定义在 include/linux/fb.h 中。下表列出了最常用的回调函数:
| 回调函数 | 功能描述 | 必须实现 |
|---|---|---|
fb_open |
设备打开时的初始化操作 | 否 |
fb_release |
设备关闭时的清理操作 | 否 |
fb_read |
从帧缓冲读取数据 | 否(默认使用 fb_sys_read) |
fb_write |
向帧缓冲写入数据 | 否(默认使用 fb_sys_write) |
fb_mmap |
将帧缓冲物理内存映射到用户空间 | 是 |
fb_pan_display |
实现显示起始地址偏移(画面平移) | 否(但实现后可支持硬件滚动) |
fb_set_par |
设置显示参数(分辨率、位深、时序) | 是 |
fb_check_var |
校验用户设置的显示参数是否合法 | 是 |
fb_blank |
控制屏幕开关/休眠 | 否 |
fb_set_par 和 fb_mmap,忽略了 fb_check_var 的校验逻辑,导致用户空间设置非法参数时内核崩溃或显示异常。
3.4 注册与注销流程
FBDEV 驱动的注册流程遵循标准的 Linux 设备模型,核心函数调用链如下:
- 分配
struct fb_info: 使用framebuffer_alloc()分配并初始化fb_info结构体。 - 填充
fb_ops: 设置fbops字段,指向驱动实现的fb_ops结构体。 - 设置固定参数: 填充
fb_fix_screeninfo,包括smem_start(物理地址)、smem_len(内存长度)、line_length(行字节数)等。 - 设置可变参数: 填充
fb_var_screeninfo的默认值,包括xres、yres、bits_per_pixel等。 - 注册设备: 调用
register_framebuffer(struct fb_info *fb_info),将设备加入内核管理。 - 注销设备: 调用
unregister_framebuffer(struct fb_info *fb_info),移除设备并释放资源。
3.5 mmap 实现细节
mmap 是 FBDEV 最核心的用户空间交互方式,它允许应用程序直接将帧缓冲物理内存映射到进程地址空间,实现零拷贝渲染。驱动中的 fb_mmap 回调典型实现如下:
static int myfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
unsigned long start = info->fix.smem_start;
unsigned long len = vma->vm_end - vma->vm_start;
unsigned long pfn = start >> PAGE_SHIFT;
/* 设置页属性:非缓存、可写、可读 */
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
/* 映射物理内存到用户空间 */
if (io_remap_pfn_range(vma, vma->vm_start, pfn, len, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
pgprot_writecombine() 设置写合并属性,可显著提升 CPU 写入帧缓冲的性能。对于不支持写合并的架构,可使用 pgprot_noncached() 替代。
fbmem.c 中的 fb_mmap 默认实现会检查驱动是否提供了 fb_mmap 回调,如果未提供则使用默认的 fb_default_mmap,该函数直接映射 info->screen_base 对应的物理内存。
3.6 pan_display 实现分析
pan_display 用于实现硬件支持的画面平移(panning),即在不改变帧缓冲物理地址的情况下,通过调整显示控制器的起始读取地址来实现快速滚动。这在游戏、终端模拟器、视频播放等场景中非常有用。
驱动中的 fb_pan_display 回调原型如下:
static int myfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
{
unsigned long offset;
/* 计算新的显示起始偏移(以字节为单位) */
offset = var->yoffset * info->fix.line_length
+ var->xoffset * (info->var.bits_per_pixel / 8);
/* 写入硬件寄存器,更新显示起始地址 */
writel(info->fix.smem_start + offset, info->screen_base + PAN_OFFSET_REG);
return 0;
}
用户空间通过 FBIOPAN_DISPLAY ioctl 触发该操作,传入新的 xoffset 和 yoffset 值。内核中的处理路径如下:
fb_ioctl()接收到FBIOPAN_DISPLAY命令。- 调用
fb_pan_display()进行参数校验(检查偏移是否超出虚拟分辨率范围)。 - 如果驱动实现了
fb_pan_display回调,则调用之;否则返回-EINVAL。
pan_display 实现双缓冲时,应用程序只需在后台缓冲区绘制,然后通过 pan_display 切换显示起始地址,避免了昂贵的 memcpy 操作,帧率可提升 2-3 倍。
3.7 FBDEV 与 DRM/KMS 的对比
在进入后续章节之前,先通过下表建立 FBDEV 与 DRM/KMS 的初步对比,帮助理解为何需要迁移:
| 对比维度 | FBDEV | DRM/KMS |
|---|---|---|
| 架构设计 | 单一字符设备,所有操作集中 | 分层设计:DRM Core + KMS + GEM |
| 多进程支持 | 弱,缺乏原子性操作 | 强,支持原子提交(Atomic Modeset) |
| 硬件抽象 | 仅抽象帧缓冲 | 抽象 CRTC、Encoder、Connector、Plane |
| 内存管理 | 简单物理内存映射 | GEM/DUMB Buffer,支持 DMA-BUF 共享 |
| 显示特性 | 仅支持基本画面平移 | 支持多图层叠加、旋转、缩放、色彩管理 |
| 现代 GPU 支持 | 不支持 | 原生支持,可与 GPU 驱动协同 |
3.8 本章小结
FBDEV 框架虽然简单直接,但其设计理念与现代显示需求已存在较大差距。通过本章的学习,你应该掌握:
- FBDEV 的分层架构:
fbmem.c核心层与fb_ops驱动层的职责划分。 - 驱动注册/注销的标准流程:从
framebuffer_alloc到register_framebuffer的完整链路。 mmap的实现要点:物理内存映射、页属性设置、写合并优化。pan_display的工作原理:通过硬件偏移实现高效画面滚动。- FBDEV 与 DRM/KMS 的核心差异,为后续章节的迁移实战打下基础。