3、DRM框架核心概念:DRM对象模型与驱动初始化流程
好,咱们进入DRM框架的核心地带。说实话,很多从FBDEV转过来的朋友,第一次看到DRM的对象模型都会有点懵——怎么这么多结构体?又是CRTC又是Encoder的。别急,我当初刚接触时也花了整整一周才理清楚。今天咱们就把这些概念掰开揉碎,讲明白。
3.1 DRM对象模型:四驾马车
DRM的核心对象模型,说白了就是四个角色:CRTC、Encoder、Connector、Plane。它们共同完成一件事——把内存里的像素数据,变成显示器上你能看到的画面。
我习惯用一个比喻来理解:
- CRTC 就像投影仪的光机,负责从内存读取数据,生成视频时序信号
- Encoder 就像信号转换器,把CRTC输出的信号转成显示器能接受的格式(比如HDMI、DP、LVDS)
- Connector 就是物理接口,你插显示器的那地方
- Plane 是图层,每个平面可以独立叠加、缩放、旋转
你想想看,这四者之间是怎么连接的?一条典型的显示链路是这样的:
Plane → CRTC → Encoder → Connector → 显示器
数据从Plane(内存中的帧缓冲)流向CRTC,CRTC生成时序后送给Encoder,Encoder转换信号格式,最后通过Connector物理接口输出到显示器。
关键点:一个CRTC可以连接多个Encoder,但同一时刻只能激活一条链路。我曾经在调试双屏扩展时,就是因为没搞清楚CRTC和Encoder的绑定关系,折腾了两天才发现是Encoder的possible_crtc掩码没配对。
3.2 各对象的职责与关键字段
咱们逐个来看,每个对象在代码里长什么样。
3.2.1 CRTC(显示控制器)
CRTC是显示链路的核心。它负责:
- 从帧缓冲读取像素数据
- 生成行场同步信号
- 控制刷新率、分辨率、色彩格式
在代码中,每个CRTC对应一个 drm_crtc 结构体。我建议你重点关注这几个字段:
| 字段 | 说明 | 我的经验 |
|---|---|---|
port |
设备树端口,用于匹配硬件 | 配错这个,CRTC根本不会使能 |
enabled |
使能状态 | 调试时先看这个位 |
mode |
当前显示模式 | 包含分辨率、刷新率等 |
funcs |
操作函数集 | 必须实现 set_config 和 gamma_set |
3.2.2 Encoder(编码器)
Encoder负责信号格式转换。比如你的CRTC输出的是RGB并行信号,但显示器要的是HDMI信号,中间就需要Encoder来转。
关键字段:
possible_crtcs:掩码,表示这个Encoder可以连接到哪些CRTCpossible_clones:克隆模式支持encoder_type:类型,如DRM_MODE_ENCODER_TMDS、DRM_MODE_ENCODER_LVDS
避坑指南:我曾经在移植一个HDMI驱动时,发现Encoder始终无法使能。查了两天,最后发现是 possible_crtcs 掩码写错了——我用的位偏移和硬件实际连接的CRTC索引对不上。记住,这个掩码是按bit位算的,不是按数组下标。
3.2.3 Connector(连接器)
Connector代表物理接口。它负责:
- 检测显示器是否插入(热插拔检测)
- 读取显示器的EDID信息
- 提供可用的显示模式列表
常见的Connector类型:
| 类型 | 说明 | 典型应用 |
|---|---|---|
DRM_MODE_CONNECTOR_HDMIA |
HDMI Type A | 电视、显示器 |
DRM_MODE_CONNECTOR_DisplayPort |
DisplayPort | 笔记本、高端显示器 |
DRM_MODE_CONNECTOR_LVDS |
LVDS | 嵌入式屏幕 |
DRM_MODE_CONNECTOR_eDP |
嵌入式DP | 笔记本内置屏 |
3.2.4 Plane(平面)
Plane是DRM框架中比较晚引入的概念(从KMS开始)。每个Plane对应一个硬件图层,你可以把不同的内容叠加显示。
Plane有三种类型:
- Primary Plane:主平面,每个CRTC必须有一个
- Cursor Plane:光标平面,专门用于鼠标指针
- Overlay Plane:叠加平面,用于视频播放、UI叠加等
我个人的经验是:如果你的硬件支持多个图层,一定要用Overlay Plane来做视频播放。这样视频数据不用经过CPU合成,直接由硬件叠加,性能提升非常明显。
3.3 drm_device结构体:一切从这里开始
drm_device 是整个DRM驱动的根结构体。你可以把它理解成驱动的大管家,所有核心对象都挂在这个结构体下面。
关键字段一览:
| 字段 | 类型 | 作用 |
|---|---|---|
dev |
struct device * |
指向内核设备模型中的device |
driver |
struct drm_driver * |
驱动操作函数集 |
mode_config |
struct drm_mode_config |
模式配置管理,包含所有CRTC/Encoder/Connector/Plane的链表 |
filelist |
链表头 | 管理所有打开DRM设备的文件句柄 |
irq_enabled |
bool |
中断是否已使能 |
初始化 drm_device 的典型代码:
struct drm_device *drm;
int ret;
// 1. 分配drm_device
drm = drm_dev_alloc(&my_driver, &pdev->dev);
if (IS_ERR(drm))
return PTR_ERR(drm);
// 2. 设置驱动私有数据
drm->dev_private = my_priv;
// 3. 注册设备
ret = drm_dev_register(drm, 0);
if (ret)
goto err_free;
注意:在调用 drm_dev_register 之前,你必须已经完成了所有CRTC、Encoder、Connector、Plane的初始化。否则注册后用户空间访问时,会发现对象列表不完整,导致崩溃。
3.4 驱动初始化流程:一步一步来
DRM驱动的初始化流程,我总结为五个步骤。每一步都有坑,咱们一个一个说。
- 分配并初始化drm_device
- 调用
drm_dev_alloc分配结构体 - 设置
drm_driver函数集
- 调用
- 初始化模式配置
- 调用
drm_mode_config_init - 设置
min_width、max_width等限制
- 调用
- 创建核心对象
- 初始化CRTC:
drm_crtc_init - 初始化Encoder:
drm_encoder_init - 初始化Connector:
drm_connector_init - 初始化Plane:
drm_universal_plane_init
- 初始化CRTC:
- 建立对象连接关系
- 设置Encoder的
possible_crtcs - 调用
drm_connector_attach_encoder - 设置Connector的
polled属性(热插拔检测)
- 设置Encoder的
- 注册设备
- 调用
drm_dev_register - 使能中断
- 调用
嗯,这里要注意第三步的顺序。我见过不少新手把CRTC和Encoder的初始化顺序搞反,结果在 drm_encoder_init 时因为CRTC还没注册,导致possible_crtcs无法正确设置。记住:先CRTC,后Encoder,再Connector,最后Plane。
3.5 知识结构总览
下面这张图,把DRM对象模型和初始化流程串起来了。我建议你保存下来,写驱动时对照着看。
3.6 实战中的几个关键点
最后,分享几个我在实际项目中踩过的坑:
关于中断:DRM驱动通常需要两个中断——VBlank中断和热插拔中断。VBlank中断用于垂直同步,热插拔中断用于检测显示器插拔。我建议你在 drm_dev_register 之后才使能中断,否则设备还没注册,中断来了也没地方处理。
关于内存分配:CRTC和Plane都需要分配帧缓冲。如果你的硬件支持DMA-BUF,尽量用 drm_gem_cma_create 或 drm_gem_shmem_create 来分配。我曾经图省事直接用 kmalloc,结果在4K分辨率下频繁出现画面撕裂——因为内存没有连续物理地址,DMA传输会断断续续。