zl程序教程

您现在的位置是:首页 >  系统

当前栏目

Linux驱动之Framebuffer子系统

Linux驱动 子系统
2023-09-14 09:16:15 时间

1. framebuffer

帧缓冲设备为图形硬件提供抽象。 它代表一些视频硬件的帧缓冲区,并允许应用软件通过一个定义良好的接口访问图形硬件,所以软件不需要知道任何关于低级(硬件寄存器)的东西。

设备通过特殊的设备节点访问,通常位于 /dev 目录中,即 /dev/fb*。

1.1 用户看到的/dev/fb*

从用户的角度来看,帧缓冲设备看起来就像 /dev 中的任何其他设备。 这是一个使用major 29的字符设备; minor指定帧缓冲区编号。

按照惯例,使用以下设备节点(数字表示设备次要编号):

0 = /dev/fb0    First frame buffer
1 = /dev/fb1    Second frame buffer
...
31 = /dev/fb31  32nd frame buffer

为了向后兼容,您可能需要创建以下符号链接:

/dev/fb0current -> fb0
/dev/fb1current -> fb1

帧缓冲设备也是“普通”存储设备,这意味着您可以读取和写入它们的内容。 例如,您可以通过以下方式制作屏幕快照:

# cp /dev/fb0 myfile

一次也可以有多个帧缓冲区,例如 如果除了内置硬件之外还有显卡。 相应的帧缓冲设备(/dev/fb0 和/dev/fb1 等)独立工作。

使用帧缓冲设备的应用程序软件(例如 X server)将默认使用 /dev/fb0(较旧的软件使用 /dev/fb0current)。 您可以通过将环境变量 $FRAMEBUFFER 设置为帧缓冲设备的路径名来指定替代的帧缓冲设备,例如 (对于 sh/bash 用户):

# export FRAMEBUFFER=/dev/fb1

or (for csh users)::

# setenv FRAMEBUFFER /dev/fb1

在此之后,X server将使用第二个帧缓冲区。

1.2 编程人员看到的/dev/fb*

如您所知,帧缓冲设备是一种类似于 /dev/mem 的内存设备,它具有相同的功能。 您可以读取它,写入它,寻找其中的某个位置并 mmap() 它(主要用途)。 不同的是,特殊文件中出现的内存并不是整个内存,而是一些视频硬件的帧缓冲区。

/dev/fb* 还允许在其上使用多个 ioctl,通过这些 ioctl 可以查询和设置有关硬件的大量信息。 颜色映射处理也通过 ioctls 工作。 查看 <linux/fb.h> 以获取有关存在哪些 ioctl 及其工作的数据结构的更多信息。 这里只是一个简短的概述:

  • 您可以请求有关硬件的不可更改的信息,例如名称、屏幕内存的组织(planes、packed pixels等)以及屏幕内存的地址和长度。

  • 您可以请求和更改有关硬件的可变信息,例如可见和虚拟geometry、深度、颜色映射格式、时间等。 如果您尝试更改该信息,驱动程序可能会舍入一些值以满足硬件的功能(如果不支持,则返回 EINVAL)。

  • 您可以获取和设置部分颜色图。 每个颜色部分(红色、绿色、蓝色、透明度)使用 16 位进行通信,以支持所有现有硬件,驱动程序执行将其应用于硬件所需的所有计算(将其舍入到更少的位,可能会丢弃透明度)。

所有这些硬件抽象使得应用程序的实现更容易和更便携。 例如,X server完全在 /dev/fb* 上工作,因此不需要知道具体硬件的颜色寄存器是如何组织的。 XF68_FBDev 是用于位图、非加速视频硬件的通用 X server,唯一必须内置到应用程序中的是屏幕组织(位平面或块状像素等),因为它直接作用于帧缓冲区图像数据。

未来计划将图形卡等的帧缓冲驱动程序实现为在运行时加载的内核模块。这样的驱动程序只需要调用 register_framebuffer() 并提供一些函数。 独立于内核编写和分发此类驱动程序将省去很多麻烦......

1.3 帧缓冲分辨率维护

使用程序“fbset”维护帧缓冲区分辨率,它可以更改帧缓冲设备的视频模式属性。它的主要用途是改变当前的视频模式,例如 在您的 /etc/rc.*/etc/init.d/* 文件之一的启动期间。

Fbset 使用存储在配置文件中的视频模式数据库,因此您可以轻松添加自己的模式并使用简单的标识符引用它们。

1.4 X server

X server(XF68_FBDev)是帧缓冲设备最著名的应用程序,从 XFree86 3.2 版开始,X server是 XFree86 的一部分,有两种模式:

  • 如果 /etc/XF86Config 文件中 fbdev 驱动程序的 Display 小节包含:

Modes "default"

X server将使用上面讨论的方案,即它将以 /dev/fb0(或 $FRAMEBUFFER,如果设置)确定的分辨率启动。不过,您仍然必须指定颜色深度(使用 Depth 关键字)和虚拟分辨率(使用 Virtual 关键字)。 这是 XFree86 提供的配置文件的默认设置。 这是最简单的配置,但也有一些限制。

  • 因此,也可以在 /etc/XF86Config 文件中指定分辨率,这允许即时分辨率切换,同时保持相同的虚拟桌面大小。使用的帧缓冲设备仍然是 /dev/fb0current(或 $FRAMEBUFFER),但可用的分辨率现在由 /etc/XF86Config 定义。 缺点是您必须以不同的格式指定时间(但fbset -x 可能会有所帮助)。

要调整视频模式,您可以使用 fbset 或 xvidtune。 请注意,xvidtune 不能 100% 使用 XF68_FBDev:报告的时钟值总是不正确的。

1.5 视频模式时序

显示器通过使用电子束(彩色型号为 3 条电子束,单色显示器为 1 条电子束)在屏幕上绘制图像。屏幕正面覆盖有彩色荧光粉(像素)图案。如果磷光体被电子击中,它会发射光子,从而变得可见。

电子束从屏幕的左到右,从上到下绘制水平线(扫描线)。通过修改电子束的强度,可以显示具有各种颜色和强度的像素。

在每条扫描线之后,电子束必须移回屏幕左侧和下一行:这称为水平回扫。整个屏幕(框架)绘制完成后,光束移回左上角:这称为垂直回扫。在水平和垂直回扫期间,电子束被关闭(消隐)。

电子束绘制像素的速度由图形板中的点时钟决定。对于例如点时钟28.37516 MHz(每秒数百万个周期),每个像素的长度为 35242 ps(皮秒):

1/(28.37516E6 Hz) = 35.242E-9 s

如果屏幕分辨率为 640x480,则需要:

640*35.242E-9 s = 22.555E-6 s

在一条扫描线上绘制 640 (xres) 像素。 但是水平回扫也需要时间(例如 272 像素),因此完整的扫描线需要:

(640+272)*35.242E-9 s = 32.141E-6 s

我们会说水平扫描速率约为 31 kHz::

1/(32.141E-6 s) = 31.113E3 Hz

全屏计数 480 (yres) 行,但我们也必须考虑垂直回溯(例如 49 行)。 所以全屏将需要:

(480+49)*32.141E-6 s = 17.002E-3 s

垂直扫描速率约为 59 Hz:

1/(17.002E-3 s) = 58.815 Hz

这意味着屏幕数据每秒刷新约 59 次。 为了获得稳定且无可见闪烁的图像,VESA 建议垂直扫描速率至少为 72 Hz。 但是感知到的闪烁非常依赖于人:有些人可以毫无困难地使用 50 Hz,而我会注意到它是否低于 80 Hz。

由于显示器不知道新的扫描线何时开始,因此图形板将为每个扫描线提供一个同步脉冲(水平同步或 hsync)。 类似地,它为每个新帧提供一个同步脉冲(垂直同步或垂直同步)。 屏幕上图像的位置受同步脉冲出现的时刻的影响。

下图总结了所有时间。 水平回扫时间是左边距、右边距和hsync长度之和,而垂直回扫时间是上边距、下边距和vsync长度之和:

+----------+---------------------------------------------+----------+-------+
|          |                ↑                            |          |       |
|          |                |upper_margin                |          |       |
|          |                ↓                            |          |       |
+----------###############################################----------+-------+
|          #                ↑                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|   left   #                |                            #  right   | hsync |
|  margin  #                |       xres                 #  margin  |  len  |
|<-------->#<---------------+--------------------------->#<-------->|<----->|
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |yres                        #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                |                            #          |       |
|          #                ↓                            #          |       |
+----------###############################################----------+-------+
|          |                ↑                            |          |       |
|          |                |lower_margin                |          |       |
|          |                ↓                            |          |       |
+----------+---------------------------------------------+----------+-------+
|          |                ↑                            |          |       |
|          |                |vsync_len                   |          |       |
|          |                ↓                            |          |       |
+----------+---------------------------------------------+----------+-------+

帧缓冲设备期望以点时钟数(以皮秒为单位,1E-12 s)表示所有水平时序,并以扫描线数量表示垂直时序。

1.6 转换 XFree86 时序值信息帧缓冲设备时序

XFree86 模式行包含以下字段:

 "800x600"     50      800  856  976 1040    600  637  643  666
< name >     DCF       HR  SH1  SH2  HFL     VR  SV1  SV2  VFL

帧缓冲设备使用以下字段:

  • pixclock:以 ps 为单位的像素时钟(皮秒)

  • left_margin:从同步到图片的时间

  • right_margin:从图片到同步的时间

  • upper_margin:从同步到图片的时间 -lower_margin:从图片到同步的时间

  • hsync_len:水平同步长度

  • vsync_len:垂直同步长度

  1. Pixelclock:

xfree: in MHz

fb: in picoseconds (ps)

pixclock = 1000000 / DCF

  1. horizontal timings:

left_margin = HFL - SH2

right_margin = SH1 - HR

hsync_len = SH2 - SH1

  1. vertical timings:

upper_margin = VFL - SV2

lower_margin = SV1 - VR

vsync_len = SV2 - SV1

可以在 XFree86 源代码树中的“xc/programs/Xserver/hw/xfree86/doc/modeDB.txt”下找到 VESA 计时的好例子。

2. The Framebuffer Console

顾名思义,帧缓冲区控制台 (fbcon) 是一个文本控制台运行在帧缓冲设备之上。 它具有任何标准文本控制台驱动程序(例如 VGA 控制台)的功能,并具有可归因于帧缓冲区的图形特性的附加功能。

在 x86 架构中,framebuffer 控制台是可选的,甚至有人把它当作玩具。 对于其他架构,它是唯一可用的显示设备、文本或图形。

fbcon 有什么特点? 帧缓冲控制台支持高分辨率、不同字体类型、显示旋转、原始多头等。理论上,多色字体、混合、混叠以及底层显卡提供的任何功能也是可能的。

2.1 配置

可以使用您最喜欢的内核配置工具启用帧缓冲区控制台。它位于Device Drivers->Graphics Support-> Console display driver support->Framebuffer Console Support。选择“y”以静态编译支持或选择“m”以获取模块支持。该模块将是 fbcon。

为了激活 fbcon,至少需要一个帧缓冲驱动程序,因此请从众多可用驱动程序中进行选择。对于 x86 系统,它们几乎普遍都有 VGA 卡,因此 vga16fb 和 vesafb 将始终可用。但是使用特定于芯片组的驱动程序将为您提供更高的速度和功能,例如动态更改视频模式的能力。

要显示企鹅徽标,请选择图形中可用的Graphics support->Bootup logo.

此外,您至少需要选择一种编译字体,但如果您什么都不做,内核配置工具会为您选择一种,通常是 8x16 字体。

GOTCHA:一个常见的错误报告是在不启用帧缓冲区控制台的情况下启用帧缓冲区。根据驱动程序的不同,您可能会看到空白或乱码显示,但系统仍会引导完成。如果您有幸拥有不改变图形芯片的驱动程序,那么您仍将获得 VGA 控制台。

2.2 加载

可能的场景:

  • 驱动程序和 fbcon 是静态编译的。通常,fbcon 会自动接管您的控制台。 值得注意的例外是 vesafb。 它需要使用 vga= 引导选项参数显式激活。

  • 驱动是静态编译的,fbcon是作为模块编译的。如上所述,根据驱动程序的不同,您会得到标准控制台或乱码显示。 要获得帧缓冲区控制台,请执行“modprobe fbcon”。

  • 驱动编译为模块,fbcon静态编译。你得到你的标准控制台。 一旦使用 'modprobe xxxfb' 加载驱动程序,fbcon 会自动接管控制台,但使用 fbcon=map:n 选项可能是个例外。 见下文。

  • 驱动程序和 fbcon 被编译为一个模块。您可以按任何顺序加载它们。 一旦两者都被加载,fbcon 将接管控制台。

2.3 启动选项

帧缓冲区控制台有几个很大程度上未知的引导选项,可以改变其行为。

  • fbcon=font:
    选择要使用的初始字体。 值“name”可以是任何编译字体:10x18、6x10、6x8、7x14、Acorn8x8、MINI4x6、PEARL8x8、ProFont6x11、SUN12x22、SUN8x16、TER16x32、VGA8x86、VGA88x86。

请注意,并非所有驱动程序都可以处理宽度不能被 8 整除的字体,例如 vga16fb。

  • fbcon=map:<0123>
    这是一个有趣的选择。 它告诉哪个驱动程序映射到哪个控制台。 值“0123”是一个重复的序列,直到总长度为 64,这是可用的控制台数量。 在上面的例子中,它被扩展为 012301230123... 并且映射将是:

  tty | 1 2 3 4 5 6 7 8 9 ...
  fb  | 0 1 2 3 0 1 2 3 0 ...

一个可能有用的副作用是使用超过加载的 fb 驱动程序数量的映射值。 例如,如果只有一个驱动程序可用,fb0,添加 fbcon=map:1 告诉 fbcon 不要接管控制台。

稍后,当您想将控制台映射到帧缓冲设备时,您可以使用 con2fbmap 实用程序。

  • fbcon=vc:-
    此选项告诉 fbcon 仅接管由值 'n1' 和 'n2' 指定的一系列控制台。 给定范围之外的其余控制台仍将由标准控制台驱动程序控制。

注意:对于 x86 机器,标准控制台是 VGA 控制台,它通常位于同一视频卡上。 这样,VGA控制台控制的控制台就会出现乱码。

  • fbcon=rotate:
    此选项更改控制台显示的方向角度。 值“n”接受以下内容:

  - 0 - 正常方向(0 度)
  - 1 - 顺时针方向(90 度)
  - 2 - 倒置方向(180 度)
  - 3 - 逆时针方向(270 度)

之后可以通过将相同的数字“回显”到 /sys/class/graphics/fbcon 中的 2 个属性之一来随时更改角度:

  - rotate - 旋转活动控制台的显示
  - rotate_all - 旋转所有控制台的显示

只有在内核中编译了 Framebuffer Console Rotation 支持时,控制台旋转才可用。

注意:这纯粹是控制台旋转。 使用帧缓冲区的任何其他应用程序将保持其“正常”方向。 实际上,底层的 fb 驱动程序完全不知道控制台旋转。

  • fbcon=margin:
    此选项指定边距的颜色。 边距是文本未使用的屏幕右侧和底部的剩余区域。 默认情况下,该区域为黑色。 'color' 值是一个整数,取决于所使用的帧缓冲驱动程序。

  • fbcon=nodefer
    如果内核是使用延迟 fbcon 接管支持编译的,通常由固件/引导加载程序留在原地的帧缓冲区内容将被保留,直到实际有一些文本输出到控制台。 此选项会导致 fbcon 立即绑定到 fbdev 设备。

  • fbcon=logo-pos:
    唯一可能的“位置”是“中心”(不带引号),当给出时,启动标志从默认的左上角位置移动到帧缓冲区的中心。 如果由于多个 CPU 导致显示多个徽标,则将收集的一行徽标作为一个整体移动。

  • fbcon=logo-count:
    值“n”覆盖启动徽标的数量。 0 禁用徽标,-1 给出默认值,即在线 CPU 的数量。

3. 数据结构

FrameBuffer实现的数据结构如下:

  • fb_info
    fb_info结构代表帧缓冲本身,是帧缓冲驱动程序的主要数据结构,仅存在于内核中。

  • fb_var_screeninfo
    代表可修改的LCD显示参数,如分辨率和像素比特数等。

  • fb_fix_screeninfo
    代表不可修改的LCD属性参数,如显示内存的物理地址和长度等。

  • fb_ops
    LCD底层硬件操作接口集。

  • fb_cmap
    fb_cmap指定颜色映射,用于以内核可以理解的方式存储用户的颜色定义。

4. 实现

Linux Framebuffer的驱动框架主要涉及以下文件:

  • drivers/video/fbmem.c:主要任务是创建graphics类、注册FB的字符设备驱动(主设备号是29)、提供register_framebuffer接口给具体framebuffer驱动编写着来注册fb设备的。

  • drivers/video/fbsys.c:是fbmem.c引出来的,处理fb在/sys/class/graphics/fb0目录下的一些属性文件的。

  • drivers/video/modedb.c:是fbmem.c引出来的,管理显示模式(譬如VGA、720P等就是显示模式)。

  • drivers/video/fb_notify.c:是由bmem.c引出来的。

Framebuffer本质上也是一个字符设备驱动程序,其file_operations结构体如下:

static const struct file_operations fb_fops = {
  .owner =    THIS_MODULE,
  .read =        fb_read,
  .write =    fb_write,
  .unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
  .compat_ioctl = fb_compat_ioctl,
#endif
  .mmap =        fb_mmap,
  .open =        fb_open,
  .release =    fb_release,
#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
    (defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
     !defined(CONFIG_MMU))
  .get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
  .fsync =    fb_deferred_io_fsync,
#endif
  .llseek =    default_llseek,
};

其注册位置在fbmem_init函数里,其实现逻辑是,对于fb设备驱动程序,驱动开发人员的工作就是分配注册一个fb_info结构体,每个结构体有不同的次设备号,以fb_open为例,这个函数会通过次设备号获得fb_info结构体,然后调用该fb_info结构体的fb_ops的fb_open函数,其他函数的实现与此类似。从这里可以看到其实fb的本质设计是很简单的,但是由于fb驱动器以及lcd等设备及gpu等复杂结构,接口也有并口,mipi、hdmi等,实际的drivers/video驱动中内容很多,这里本人没有过多研究,感兴趣的可以自行阅读源码。