2、显示驱动框架:Linux DRM/KMS框架、全志显示驱动分层、关键数据结构
好,咱们进入正题。这一章我打算聊聊显示驱动框架。说实话,很多做嵌入式显示开发的朋友,一上来就被DRM/KMS给绕晕了。我当年刚接触全志A133的时候,也花了整整一周才把整个数据流理清楚。今天我就把这块掰开了讲,希望能帮你少走弯路。
2.1 Linux DRM/KMS框架概览
DRM,全称是Direct Rendering Manager。KMS是Kernel Mode Setting。这两个东西合在一起,就是Linux下显示驱动的标准框架。说白了,DRM负责管理显存和渲染,KMS负责管理显示模式和连接器。
为什么会设计成这样?你想想看,早期的Linux显示驱动,每个厂商都自己搞一套,上层应用根本没法统一调用。后来社区就搞了DRM/KMS,让所有显卡驱动都遵循同一套接口。嗯,这个设计思路其实很聪明。
核心概念:DRM/KMS把显示系统抽象成几个关键对象:CRTC、Encoder、Connector、Plane、Framebuffer。理解这几个东西,你就掌握了显示驱动的半壁江山。
我在项目中遇到过一个问题:客户说他们的LCD屏幕在开机时闪了一下。排查到最后,发现是KMS初始化时,CRTC和Encoder的时序没对齐。这种问题,如果你不理解框架,根本无从下手。
2.2 全志显示驱动分层
全志的显示驱动,在DRM/KMS框架下做了自己的分层。我个人习惯把它分成三层:
- 底层硬件抽象层:直接操作DE(Display Engine)和TCON(Timing Controller)寄存器。这部分全志叫它"sunxi-drm"。
- 中间层DRM驱动:实现DRM/KMS标准接口,把底层硬件包装成CRTC、Plane这些对象。
- 上层应用接口:用户空间通过libdrm或直接ioctl调用。
我建议你重点关注中间层。因为全志的很多坑,都藏在这一层里。比如A133的DE版本是2.0,和之前的A64、H3都不一样。它的混合器(Mixer)通道数、图层叠加策略都有变化。
个人经验:调试全志显示驱动时,建议先看drivers/gpu/drm/sunxi/目录下的代码。重点关注sun4i_drv.c和sun8i_mixer.c这两个文件。前者是入口,后者是核心。
2.3 关键数据结构
好,接下来我们看代码。DRM/KMS框架里,有几个数据结构你必须烂熟于心。
2.3.1 struct drm_device
这是整个DRM设备的顶层结构体。每个显卡对应一个。全志的驱动里,一般会把它嵌入到一个自定义结构体中:
struct sun4i_drv {
struct drm_device *drm;
struct drm_fbdev_cma *fbdev;
struct sun4i_backend *backend;
struct sun4i_tcon *tcon;
// ... 其他成员
};
嗯,这里要注意:全志的drm_device初始化顺序有讲究。必须先初始化backend,再初始化tcon。我曾经因为顺序搞反,导致屏幕死活点不亮。查了两天才发现是这个问题。
2.3.2 struct drm_crtc
CRTC代表显示控制器。它负责从Framebuffer读取数据,生成时序信号。全志的CRTC实现主要在sun4i_crtc.c里:
struct sun4i_crtc {
struct drm_crtc crtc;
struct sun4i_backend *backend;
struct sun4i_tcon *tcon;
// 寄存器基地址、时钟等
};
我建议你重点关注crtc->funcs里的enable和disable回调。这两个函数里,全志做了很多硬件初始化的工作。比如时钟使能、PLL配置、DE模块复位等。
2.3.3 struct drm_plane
Plane是图层。A133的DE支持多个硬件图层,每个图层对应一个Plane。全志的Plane实现支持RGB和YUV格式,但要注意:
避坑指南:我曾经在A133上做视频播放,发现YUV格式的图层叠加到RGB背景上时,颜色完全不对。后来发现是Plane的color_space属性没设置。全志默认是BT.601,但我的视频源是BT.709。这个坑,文档里根本没写。
2.3.4 struct drm_connector
Connector代表物理显示接口。HDMI、LVDS、MIPI DSI都对应不同的Connector。全志的Connector实现里,有个重要的函数:
static int sun4i_connector_get_modes(struct drm_connector *connector)
{
// 从EDID读取显示模式
// 或者从设备树获取固定模式
// 返回支持的显示模式列表
}
嗯,这里有个细节:如果你的屏幕是固定的LCD,建议在设备树里直接指定display-timings节点。这样驱动会直接使用你给的时序,省去EDID解析的麻烦。
2.3.5 struct drm_framebuffer
Framebuffer就是显存里的帧缓冲区。全志的Framebuffer分配,我建议使用dma_alloc_writecombine。为什么?因为writecombine模式在ARM架构下性能更好,而且避免了cache一致性问题。
| 数据结构 | 作用 | 全志对应文件 |
|---|---|---|
| drm_device | 顶层设备 | sun4i_drv.c |
| drm_crtc | 显示控制器 | sun4i_crtc.c |
| drm_plane | 图层 | sun8i_mixer.c |
| drm_connector | 显示接口 | sun4i_connector.c |
| drm_framebuffer | 帧缓冲区 | sun4i_fb.c |
2.4 数据流全景
最后,我带你走一遍完整的数据流。从用户空间到像素点亮,大概是这样:
- 用户程序通过ioctl调用drmModeSetCrtc,设置CRTC和Framebuffer。
- DRM核心层调用全志的crtc->funcs->set_config回调。
- 全志驱动配置DE的Mixer,把Framebuffer的数据路由到对应的图层。
- DE从DDR读取像素数据,经过格式转换、缩放、叠加等处理。
- 处理后的数据送到TCON,TCON生成LCD/HDMI需要的时序信号。
- 最终,像素点亮在屏幕上。
你想想看,这一整套流程,任何一个环节出问题,屏幕都可能显示异常。我调试过最头疼的一个bug,是DE的带宽不够,导致高分辨率下画面撕裂。最后是通过调整Plane的优先级和开启DE的智能仲裁才解决的。
调试技巧:全志的DE有个debugfs接口,在/sys/kernel/debug/dri/0/下。你可以通过cat sun4i_backend_regs查看DE寄存器的实时状态。这个在排查硬件配置问题时特别有用。
好了,这一章的内容就到这里。DRM/KMS框架和全志的分层设计,是后续所有调优工作的基础。下一章我会讲GPU加速,到时候你会看到,这些数据结构是怎么和GPU交互的。