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 结构体。

架构要点: FBDEV 的字符设备主设备号为 29,次设备号从 0 开始,对应 /dev/fb0、/dev/fb1 等设备节点。
FBDEV 分层架构图 用户空间应用程序 open / read / write / mmap / ioctl fbmem.c (核心层) register_framebuffer / unregister_framebuffer / fb_open / fb_mmap / fb_ioctl fb_ops 结构体 (驱动层) fb_open / fb_release / fb_read / fb_write / fb_mmap / fb_pan_display 物理显示硬件 LCD / HDMI / VGA 控制器

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 设备模型,核心函数调用链如下:

  1. 分配 struct fb_info: 使用 framebuffer_alloc() 分配并初始化 fb_info 结构体。
  2. 填充 fb_ops: 设置 fbops 字段,指向驱动实现的 fb_ops 结构体。
  3. 设置固定参数: 填充 fb_fix_screeninfo,包括 smem_start(物理地址)、smem_len(内存长度)、line_length(行字节数)等。
  4. 设置可变参数: 填充 fb_var_screeninfo 的默认值,包括 xres、yres、bits_per_pixel 等。
  5. 注册设备: 调用 register_framebuffer(struct fb_info *fb_info),将设备加入内核管理。
  6. 注销设备: 调用 unregister_framebuffer(struct fb_info *fb_info),移除设备并释放资源。
FBDEV 驱动注册流程图 framebuffer_alloc() 填充 fb_ops 设置 fix / var 参数 register_framebuffer() 设备注册完成

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 驱动协同
迁移动机: 随着嵌入式设备对多窗口、GPU 加速、高分辨率、多屏异显等需求增加,FBDEV 的局限性日益凸显。DRM/KMS 已成为 Linux 显示驱动的标准框架,新硬件驱动应优先选择 DRM/KMS。

3.8 本章小结

FBDEV 框架虽然简单直接,但其设计理念与现代显示需求已存在较大差距。通过本章的学习,你应该掌握:

  • FBDEV 的分层架构:fbmem.c 核心层与 fb_ops 驱动层的职责划分。
  • 驱动注册/注销的标准流程:从 framebuffer_alloc 到 register_framebuffer 的完整链路。
  • mmap 的实现要点:物理内存映射、页属性设置、写合并优化。
  • pan_display 的工作原理:通过硬件偏移实现高效画面滚动。
  • FBDEV 与 DRM/KMS 的核心差异,为后续章节的迁移实战打下基础。