4. RK3399 DRM 驱动框架:核心对象关系
在 RK3399 多屏异显场景中,理解 DRM (Direct Rendering Manager) 框架的对象关系是配置 DMA-BUF 的基础。DRM 子系统采用面向对象的设计,将显示流水线抽象为五个核心对象:drm_device、crtc、encoder、connector 和 plane。这些对象通过特定的拓扑结构组合,形成完整的显示通道。
核心概念: DRM 框架借鉴了传统显示硬件的管线模型,将软件对象与硬件模块一一映射。每个显示通道(Pipe)由一组 CRTC、Encoder、Connector 和 Plane 对象组成,而 drm_device 则是整个显示子系统的容器。
4.1 对象层次与职责
下表总结了 DRM 五大核心对象的职责及其在 RK3399 平台上的典型映射:
| DRM 对象 | 职责描述 | RK3399 硬件映射 | 关键数据结构 |
|---|---|---|---|
| drm_device | DRM 设备顶层抽象,管理所有子对象、内存管理、文件操作 | 对应 VOP (Video Output Processor) 整体 | struct drm_device |
| crtc | 显示控制器,负责扫描输出、时序生成、图层合成 | VOP0 / VOP1 / VOP2 (lite) | struct drm_crtc |
| encoder | 编码器,将 CRTC 输出的并行信号转换为外部接口协议 | HDMI TX / DP / MIPI DSI / eDP | struct drm_encoder |
| connector | 物理连接器,检测显示器热插拔、读取 EDID、管理显示模式 | HDMI 接口 / DP 接口 / MIPI 接口 | struct drm_connector |
| plane | 硬件图层(Overlay),管理帧缓冲与显示区域的映射 | VOP 的 Win0/Win1/Win2/Win3 图层 | struct drm_plane |
4.2 对象关系拓扑
DRM 对象之间通过“绑定”关系形成显示管线。一个典型的显示通道从 plane 开始,经过 crtc 合成,通过 encoder 编码,最终由 connector 输出到显示器。下图展示了 RK3399 双屏异显场景下的对象关系:
关键观察: 在 RK3399 上,一个 CRTC 可以绑定多个 Encoder(如 VOP0 同时连接 HDMI 和 eDP),但同一时刻只有一个 Encoder 被激活。这种 1:N 的绑定关系为多屏异显提供了灵活的拓扑选择。
4.3 对象绑定规则与数据流
DRM 对象的绑定遵循严格的层次规则,数据流从 drm_framebuffer 开始,经过 plane 映射,最终由 connector 输出。以下是对象绑定的核心规则:
- Plane → CRTC 绑定: 每个 Plane 必须绑定到一个 CRTC 才能参与显示。Plane 负责将 DMA-BUF 映射的帧缓冲内容输出到 CRTC 的扫描线。
- CRTC → Encoder 绑定: CRTC 通过
possible_encoders掩码声明可连接的 Encoder。RK3399 的 VOP0 通常连接 HDMI 和 eDP 编码器。 - Encoder → Connector 绑定: Encoder 通过
possible_clones和possible_crtcs掩码确定可用的 Connector。每个 Connector 在初始化时绑定一个默认 Encoder。 - Connector → 显示模式: Connector 通过读取 EDID 或固定时序确定显示分辨率、刷新率等参数,传递给 CRTC 生成时序。
数据流方向如下:
用户空间 (DMA-BUF fd)
↓
drm_framebuffer (GEM 对象 + 像素格式)
↓
drm_plane (裁剪/缩放/旋转)
↓
drm_crtc (时序生成 + 图层合成)
↓
drm_encoder (信号编码)
↓
drm_connector (物理输出)
↓
显示器
4.4 RK3399 特有的对象配置
RK3399 的 DRM 驱动(rockchip_drm_vop.c)在对象初始化时做了以下特殊配置:
| 配置项 | VOP0 (主屏) | VOP1 (副屏) | 说明 |
|---|---|---|---|
| CRTC 数量 | 1 (crtc.0) | 1 (crtc.1) | 每个 VOP 只有一个 CRTC |
| Plane 数量 | 4 (Win0~Win3) | 4 (Win0~Win3) | 支持 4 个硬件图层 |
| 最大分辨率 | 4096x2160 | 2560x1600 | VOP1 为 lite 版本 |
| 支持格式 | RGB888/YUV420/NV12 | RGB888/YUV420 | VOP1 不支持 NV16 |
| DMA-BUF 支持 | 是 | 是 | 通过 drm_gem_prime_import |
避坑提示: 在多屏异显配置中,务必确认每个 Plane 的
possible_crtcs 掩码。RK3399 的 VOP0 和 VOP1 的 Plane 不能跨 CRTC 使用。如果尝试将 VOP0 的 Plane 绑定到 VOP1 的 CRTC,驱动会返回 -EINVAL。
4.5 对象初始化流程(驱动视角)
在 RK3399 DRM 驱动的 bind() 函数中,对象的创建和绑定顺序如下:
// 伪代码:RK3399 DRM 对象初始化流程
static int vop_bind(struct device *dev, struct drm_device *drm_dev) {
struct vop *vop = dev_get_drvdata(dev);
// 1. 创建 CRTC 对象
drm_crtc_init_with_planes(drm_dev, &vop->crtc,
&vop->win[0]->plane, // 主 Plane
NULL, // 无 cursor plane
&vop_crtc_funcs, NULL);
// 2. 创建 Plane 对象(每个 Win 对应一个 Plane)
for (i = 0; i < VOP_WIN_MAX; i++) {
drm_universal_plane_init(drm_dev, &vop->win[i]->plane,
possible_crtcs_mask, // 只能绑定本 VOP 的 CRTC
&vop_plane_funcs,
formats, num_formats,
NULL, type, NULL);
}
// 3. 创建 Encoder 对象(由外部桥接芯片驱动完成)
// 例如 inno_hdmi_probe() 中调用 drm_encoder_init()
// 4. 创建 Connector 对象(由外部桥接芯片驱动完成)
// 例如 inno_hdmi_connector_init() 中调用 drm_connector_init()
// 5. 建立绑定关系(通过可能的掩码)
// crtc->possible_encoders = BIT(encoder_index);
// encoder->possible_crtcs = BIT(crtc_index);
}
实践要点: 在 DMA-BUF 配置中,用户空间通过
drmModeAtomicCommit 或 drmModeSetCrtc 建立对象之间的运行时绑定。驱动层通过 struct drm_crtc_state 和 struct drm_plane_state 维护每个对象的当前状态,确保 DMA-BUF 帧缓冲在正确的显示通道上呈现。
4.6 对象关系与 DMA-BUF 的关联
DMA-BUF 在多屏异显中的流转与 DRM 对象关系紧密相关:
- Plane 是 DMA-BUF 的消费者: 每个 Plane 通过
drm_gem_prime_import导入 DMA-BUF fd,将其映射为drm_framebuffer的 GEM 对象。 - CRTC 是合成调度器: CRTC 的
atomic_flush回调负责将多个 Plane 的 DMA-BUF 内容合成到最终的扫描输出。 - Encoder/Connector 是输出通道: 它们不直接操作 DMA-BUF,但决定了 DMA-BUF 内容最终输出到哪个物理显示器。
- drm_device 是内存管理器: 通过
struct drm_gem_object和struct drm_prime_file_private管理所有 DMA-BUF 的导入/导出操作。
进阶理解: 在 RK3399 双屏异显场景中,两个显示通道(VOP0 和 VOP1)各自拥有独立的 CRTC 和 Plane 集合。DMA-BUF 可以同时被两个通道的 Plane 引用(通过增加引用计数),实现同一帧缓冲在多个屏幕上显示。但要注意,VOP0 和 VOP1 的 DMA 引擎是独立的,需要确保内存带宽足够支撑双通道同时读取。