本篇主要以Xilinx的xc7z010 的SOPC(zybo的开发板)为硬件平台,在以下几方面介绍:
1、以zynq 7000的逻辑资源(PL)搭建CRTC/Encoder/Connector硬件模块,以HDMI输出接口为例,介绍各个模块的接口特性(Framebuffer对应着物理的DDR部分);
2、会先给出DRM+KMS驱动框架下的主要模块,并针对上述硬件子模块分析对应的内核驱动部分;
DRI在片上系统的硬件构成在各类SOC上,CRTC+Endode+Connector一般是集成在一个外设模块挂在系统总线上,以ARM为例,CRTC/Endoder等需要配置的外设模块,配置接口挂在APB总线,数据接口直接在AHB总线上,实现和Framebuffer的高速通信。
我们按照connector–>encoder–>crtc–>framebuffer的顺序倒过来介绍吧。
ConnectorConnector其实就是和显示器连接的物理接口,常见的有VGA/HDMI/DVI/DP等。以HDMI为例,HDMI的接口信号主要由以下几组信号组成:
1. 1组TMDS clock:差分时钟用于同步信号驱动;
2. 3组TMDS data:查分数据传输视频信号;
3. 1组I2C:用于EDID的获取;
4. 1组音频总线;
(注:EDID全称是Extended Display Identification Data(扩展显示标识数据),目的是让视频信号输出设备输出前获取到存储在显示器内部的相关参数,如支持的分辨率、帧率、图像格式:RGB等,因此,整个输出的控制参数是由以下几个部分综合决定的:
1、通过connector读出的显示器支持的参数;
2、内核静态配置或devicetree传入的参数;
3、用户空间输入的参数)
HDMI类型的connector的任务就是输出显示器解码芯片所需的信号时序(主要是TMDS clock以及TMDS data)。
EncoderEncoder比较好理解,在此处其实就是将一定格式的图像信号(如RGB、YUV等)编码成connector需要输出的信号。以HDMI为例,帧/行同步/显示内容都是通过TMDS data的串行总线输出的,那么并行的时序按照HDMI的标准编码为串行顺序则是Encoder的任务;
在本片中的XC7Z010 SOPC中Encoder+Connector如下:
CRTCCRTC的任务是从Framebuffer中读出待显示的图像,并按照相应的格式输出给Encoder(本处的CRTC功能受限,相关格式配置只能通过配置硬件IP参数来改变,而不能通过内核)。在本例中,CRTC的硬件构成如下:
1. AXI Video Direct Memory Access IP,通过AXI4总线获取DDR中Framebuffer数据,转为video-stream流格式的图像信息;
2. Video Timing Controller IP,根据内核相应配置输出与视频流匹配的帧、行同步信号;
3. AXI4-Stream to Video Out IP,将串行视频流转化为指定格式(IP配置)、指定分辨率(内核配置)的时序;
如下图:
PlanesPlane其实就是图层,实际输出的图像往往由多个图层叠加而成(想象一下photoshop的过程),比如主图层,显示光标的图层,其中有些图层由硬件加速模块生成,本例中不涉及,因此所有plane的相关操作都由软件实现,不涉及到任何硬件结构。
FramebufferFramebuffer对应着存储空间中的图像数据,此处对应硬件为DDR。
麻雀虽小,五脏俱全,次例程中的显示框架非常简单,但也包含了Framebuffer、CRTC、Planes、Encoder、Connector5个组件,片内硬件结构如下:
(PS:Dynamic Clock Generator生成显示子系统中各组件所需的驱动时钟,由Linux中的common clock framework统一管理)
DRI在Linux Kernel内的软件构成按照DRI中几个组件分别介绍。
Framebuffer我们知道Framebuffer是存储待显示图像信息的空间,因此,Framebuffer相关驱动中也就是对内存的操作,也就涉及到下面两个部分:
Ø 对内存的管理(如GEM,for Graphics Execution Manager)
Ø 内存中数据的更显方式(如DMA等)
对于第一点,GEM主要完成的事情是:
Ø 对图像内存(显存)的空间开辟、释放;
Ø 不同硬件对同一显存资源访问下的管理;
在Linux Kernel下的默认实现方式是CMA(Contiguous Memory Allocator)实现的,内核中对应代码是:
drivers/gpu/drm/drm_fb_cma_helper.c
这里稍微提一下CMA,CMA是个好东西,不仅在显存管理中有应用,在所有软硬件协 同处理中同样起这重要的作用。在一般的硬件(片内硬件加速模块)加速方案中,一般 实现方式如下:
- linux kernel通过device-tree传入CMA配置所需的配置参数供CMA开辟相应的物理内存 空间(不被Cache到),并且相应的信息(物理空间首地址,size等)通过/dev/device 映射到用户空间;
- DMA可以将加速模块预处理后的数据,根据已知参数(开辟的物理地址),传递到共享空间中供软件访问;
struct drm_framebuffer{
[…]
const struct drm_format_info *format;
[…]
}
- format用于描述内存空间的组成,使用FOURCC(four-character code)制式,结构体中的pitch、offset 4个元素分别用于计算FOURCC4个图层中的内存长宽参数;(width和height用于描述显示的长宽参数);
- DRI中支持下面这三种格式图像:
- RGB:R/G/B分别存于不同的图层中;
- YUV:不同压缩格式下的YUV存储方式不一;
- C8:通过存储一块映射到RGB的映射表来实现图像信息存储;
framebuffer中在不同格式下所需要处理的图层的数量不一,具体的显存处理、格式解析主要在下列源码表中:
(显存管理)
drivers/gpu/drm/drm_framebuffer.c
drivers/gpu/drm/drm_gem.c
drivers/gpu/drm/drm_gem_cma_helper.c
drivers/gpu/drm/drm_fb_cma_helper.c
drivers/gpu/drm/drm_fb_framebuffer_helper.c
drivers/gpu/drm/drm_fb_fourcc.c
drivers/gpu/drm/drm_fb_cma_helper.c
drivers/gpu/drm/drm_fb_cma_helper.c
drivers/gpu/drm/drm_fb_cma_helper.c
(显存更新驱动接口)
drivers/gpu/drm/ati_pcigart.c
drivers/gpu/drm/ati_agpsupport.c
CRTC虽然字面上意思为阴极射线显像管控制器,但CRT在普通显示设备中早已被淘汰,DRI中CRTC主要承担的作用:
Ø 配置适合显示器的分辨率(kernel)并输出相应时序(hardware logic);
Ø 扫描framebuffer送显到一个或多个显示设备中;
Ø 更新framebuffer;
上述功能的主要通过struct drm_crtc_funcs和struct drm_crtc_helper_funcs这两个描述符实现:
drm_crtc_funcs中的两个重要句柄set_config和page_flip,其中,
set_config主要任务是:
- 更新待送显的framebuffer中数据;
- 根据(之前说的EDID读出支持的配置,device-tree对内核的配置以及用户空间的配置)参数配置软件参数并控制相应的寄存器(在本例中的VDMA及VTC的行列像素值等寄存器);
- 将Encoder和connector的信息送给CRTC模块。
page_flip解决的问题很简单:
- 必须得保证CRTC在读取framebuffer的时候,framebuffer里的帧不会被修改而产生窜帧的情况,采用的解决方式也是软硬件异构处理时常见的乒乓缓存的方式。
当page_flip完成后,会通过event通知用户层准备好下一帧的数据;
Planes不涉及到GPU的话,planes没有那么复杂,主要是负责:
的创建、更新、销毁,其中图层的更新(多个图层的叠加),通过struct中的drm_plane_funcs来实现。
比较形象的例子如下:
Connector & Encoder由于Connector和Encoder是SOC与外设直接打交道的地方,因此对应着不同SOC也是驱动适配修改最频繁的地方。DRI中这两块通过适配struct drm_connector_helper_funcs和struct drm_encoder_helper_funcs来实现。
Linux Kernel中的DRM+KMS中涉及到的点太多,由于能力和时间问题,没能遍历一遍,只能根据自己认为的重点讨论一边,但有些点我认为比较重要的,但没能深入了解,比如:
- 硬件graphic加速和DRM的交互逻辑
- CRTC的Atomic刷新机制的具体实现方式
等,如果大家能够交流一下自己的理解,那再好不过了。
相应文章会同步更新至专栏中