一,DRM简介
linux内核中包含两类图形显示设备驱动框架:
-
FB设备:Framebuffer图形显示框架;
-
DRM:直接渲染管理器(Direct Rendering Manager),是linux目前主流的图形显示框架;
1,Frambebuffer驱动
Frambebuffer驱动具有以下特征:
-
直接控制显卡的帧缓冲区,提供基本的显卡输出功能;
-
使用一些内核数据结构和API来管理图形界面,并提供一组接口与用户空间的应用程序进行通信;
-
相对简单,适合于嵌入式系统或者不需要高性能图形的应用场景。
2,DRM驱动
相比FB(Framebuffer)架构,DRM更能适应当前日益更新的显示硬件;
-
提供一种分离的图形驱动架构,将硬件驱动程序、内核模块和用户空间驱动程序进行分离;
-
支持多个应用程序同时访问显卡,并提供了更丰富的图形功能,例如硬件加速和3D加速;
-
提供了一些内核接口,可以让用户空间应用程序与驱动程序进行交互;
-
支持多显示器(Display)和多GPU的配置;
总之,一句话,DRM是Linux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬。尽管FB退出历史舞台,但是并未将其遗弃,而是集合到DRM中,供部分嵌入式设备使用。
二,DRM框架
DRM是Linux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬件。比如FB原生不支持多层合成,不支持VSYNC,不支持DMA-BUF,不支持异步更新,不支持fence机制等等,而这些功能DRM原生都支持。同时DRM可以统一管理GPU和Display驱动,使得软件架构更为统一,方便管理和维护
1,DRM模块划分
DRM从模块上划分,可以简单分为3部分:libdrm、KMS、GEM
1)libdrm
对底层接口进行封装,向上层提供通用的API接口,主要是对各种IOCTL接口进行封装。
2)KMS
Kernel Mode Setting,所谓Mode setting,其实说白了就两件事:更新画面和设置显示参数。
更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置。
设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等。
3)GEM
Graphic Execution Manager,主要负责显示buffer的分配和释放,也是GPU唯一用到DRM的地方。
2,基本元素
DRM框架涉及到的元素很多,大致如下:
KMS:CRTC,ENCODER,CONNECTOR,PLANE,FB,VBLANK,property
GEM:DUMB、PRIME、fence
object | 说明 |
plane | 硬件图层,有的Display硬件支持多层合成显示,但所有的Display Controller至少要有1个plane |
CRTC | 对显示buffer进行扫描,并产生时序信号的硬件模块,通常指Display Controller |
encoder | 负责将CRTC输出的timing时序转换成外部设备所需要的信号的模块,如HDMI转换器或DSI Controller |
connector | 连接物理显示设备的连接器,如HDMI、DisplayPort、DSI总线,通常和Encoder驱动绑定在一起 |
framebuffer | Framebuffer,单个图层的显示内容,唯一一个和硬件无关的基本元素 |
VBLANK | 软件和硬件的同步机制,RGB时序中的垂直消影区,软件通常使用硬件VSYNC来实现 |
property | 任何你想设置的参数,都可以做成property,是DRM驱动中最灵活、最方便的Mode setting机制 |
DUMB | 只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景 |
PRIME | 连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景 |
fence | buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题 |
学习DRM驱动其实就是学习上面各个元素的实现及用法。
三,objects
在开始编写 DRM 驱动程序之前,我有必要对 DRM 内部的 Objects 进行一番介绍。因为这些 Objects 是 DRM 框架的核心,它们缺一不可。
上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象。虚线以上的为 drm_mode_object,虚线以下为 drm_gem_object。
这些 objects 的概念:
object | 说明 |
crtc | RGB 信号发生源(TCON),显存切换控制器(Dislay Controller) |
plane | Display Controller 的数据源通道,每个 crtc 至少要有一个 plane |
encoder | RGB 信号转换器(DSI Contoller),同时也控制显示设备的休眠唤醒 |
connector | 凡是能获取到显示参数的硬件设备,但通常和 encoder 绑定在一起 |
framebuffer | 只用于描述显存信息(如 format、pitch、size 等),不负责显存的分配释放 |
property | atomic 操作的基础,任何想要修改的参数都可以做成 property,供用户空间使用 |
gem | 负责显存的分配、映射和释放 |
这些 objects 之间的关系:
通过上图可以看到,plane 是连接 framebuffer 和 crtc 的纽带,而 encoder 则是连接 crtc 和 connector 的纽带。与物理 buffer 直接打交道的是 gem 而不是 framebuffer。
需要注意的是,上图蓝色部分即使没有实际的硬件与之对应,在软件驱动中也需要实现这些 objects,否则 DRM 子系统无法正常运行。
四,drm_panel
drm_panel 不属于 objects 的范畴,它只是一堆回调函数的集合。但它的存在降低了 LCD 驱动与 encoder 驱动之间的耦合度。
耦合的产生:
(1)connector 的主要作用就是获取显示参数,所以会在 LCD 驱动中去构造 connector object。但是 connector 初始化时需要 attach 上一个 encoder object,而这个 encoder object 往往是在另一个硬件驱动中生成的,为了访问该 encoder object,势必会产生一部分耦合的代码。
(2)encoder 除了扮演信号转换的角色,还担任着通知显示设备休眠唤醒的角色。因此,当 encoder 通知 LCD 驱动执行相应的 enable/disable 操作时,就一定会调用 LCD 驱动导出的全局函数,这也必然会产生一部分的耦合代码。
为了解决该耦合的问题,DRM 子系统为开发人员提供了 drm_panel 结构体,该结构体封装了 connector & encoder 对 LCD 访问的常用接口。
于是,原来的 Encoder 驱动和 LCD 驱动之间的耦合,就转变成了上图中 Encoder 驱动与 drm_panel、drm_panel 与 LCD 驱动之间的“耦合”,从而实现了 Encoder 驱动与 LCD 驱动之间的解耦合。
为了方便驱动程序设计,通常都将 encoder 与 connector 放在同一个驱动中初始化,即 encoder 在哪,connector 就在哪。
五,如何抽象硬件
对于初学者来说,往往让他们迷惑的不是 DRM 中 objects 的概念,而是如何去建立这些 objects 与实际硬件的对应关系。因为并不是所有的 Display 硬件都能很好的对应上 plane/crtc/encoder/connector 这些 objects。下面我们就来一起学习,如何去抽象显示硬件到具体的 DRM object。
1,MIPI DSI 接口
下图为一个典型的 MIPI DSI 接口屏的硬件连接框图:
它在软件架构上与 DRM object 的对应关系如下图:
多余的细节不做介绍,这里只说明为何如此分配 drm object:
object | 说明 |
crtc | RGB timing的产生,以及显示数据的更新,都需要访问 Dislay Controller 硬件寄存器,因此放在 Display Controller 驱动中 |
plane | 对 Overlay 硬件的抽象,同样需要访问 Display Controller 寄存器,因此也放在 Display Controller 驱动中 |
encoder | 将 RGB 并行信号转换为 DSI 串行信号,需要配置 DSI 硬件寄存器,因此放在 DSI Controller 驱动中 |
connector | 可以通过 drm_panel 来获取 LCD 的 mode 信息,但是 encoder 在哪,connector 就在哪,因此放在 DSI Controller 驱动中 |
drm_panel | 用于获取 LCD mode 参数,并提供 LCD 休眠唤醒的回调接口,供 encoder 调用,因此放在 LCD 驱动中 |
驱动参考:https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
2,MIPI DPI 接口
DPI 接口也就是我们常说的 RGB 并行接口,Video 数据通过 RGB 并行总线传输,控制命令(如初始化、休眠、唤醒等)则通过 SPI/I2C 总线传输,比如早期的 S3C2440 SoC 平台。下图为一个典型的 MIPI DPI 接口屏的硬件连接框图:
该硬件连接在软件架构上与 DRM object 的对应关系如下图:
多余的细节不做介绍,这里只说明为何如此分配 drm object:
object | 说明 |
crtc | RGB timing的产生,以及显示数据的更新,都需要访问 LCD Controller 硬件寄存器,因此放在 LCD Controller 驱动中 |
plane | LCDC 没有 Overlay 硬件,它只有一个数据源通道,被抽象为 Primary Plane,同样需要访问 LCDC 硬件寄存器,因此放在 LCDC 驱动中 |
encoder | 由于 DPI 接口本身不需要对 RGB 信号做任何转换,因此没有哪个硬件与之对应。但是 drm objects 又缺一不可,因此实现了一个虚拟的 encoder object。至于为什么要放在 LCDC 驱动中实现,纯粹只是为了省事而已,你也可以放在一个虚拟的平台驱动中去实现该 encoder object。 |
connector | encoder 在哪,connector 就在哪,没什么好说的了 |
drm_panel | 用于获取 LCD mode 参数,并提供 LCD 休眠唤醒的回调接口,供 encoder 调用,因此放在 LCD 驱动中 |
驱动参考:https://elixir.bootlin.com/linux/v5.0/source/drivers/gpu/drm/panel/panel-sitronix-st7789v.c
3,MIPI DBI 接口
DBI 接口也就是我们平时常说的 MCU 或 SPI 接口屏,这类屏的 VIDEO 数据和控制命令都是通过同一总线接口(I80、SPI接口)进行传输,而且这类屏幕必须内置 GRAM 显存,否则屏幕无法维持正常显示。
下图为一个典型的 DBI 接口屏的硬件连接框图:
该硬件连接在软件架构上与 DRM object 的对应关系如下:
上图参考 kernel4.19 tinydrm 软件架构。
object | 说明 |
crtc | 这类硬件本身不需要任何 RGB timing 信号,因此也没有实际的硬件与之对应。但是 drm objects 缺一不可,需要实现一个虚拟的 crtc object。由于更新图像数据的动作需要通过 SPI 总线发送命令才能完成,因此放在了 LCD 驱动中 |
plane | 没有实际的硬件与之对应,但 crtc 初始化时需要一个 plane object 作为参数传递,因此和 crtc 放在一起 |
encoder | 没有实际的硬件与之对应,使用虚拟的 encoder object。因为这类硬件并不是将 RGB 信号转换为 SPI 信号,而是根本就没有 RGB 信号源,也就无从谈起 encoder 设备。但是为了通知 LCD 休眠唤醒,需要调用 LCD 驱动的相应接口,因此放在 LCD 驱动中 |
connector | 由于没有了 drm_panel,需要调用 LCD 接口来获取 mode 参数,因此放在 LCD 驱动中 |
驱动参考:https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/tinydrm/ili9341.c
六,总结
-
这 7 个 objects 缺一不可
-
framebuffer 只是负责描述显存信息,gem 则负责显存的分配/释放等操作
-
encoder 在哪里,connector 就在哪里
参考链接:
DRM(Direct Rendering Manager)学习简介-CSDN博客
DRM 驱动程序开发(开篇)_spi 何小龙 csdn-CSDN博客