1. DRM框架概述
1.1 DRM发展历史
DRM(Direct Rendering Manager)是Linux内核中负责图形显示和渲染管理的核心框架。它的发展历程反映了Linux图形栈从简单到复杂、从封闭到开放的演进过程。
| 时间 | 版本/事件 | 里程碑意义 |
|---|---|---|
| 1999年 | DRI项目启动 | XFree86引入DRI(Direct Rendering Infrastructure),首次在Linux上实现直接渲染 |
| 2004年 | Linux 2.6.9 | DRM作为独立内核模块被合入主线,提供GPU内存管理和命令提交基础 |
| 2008年 | KMS引入 | Kernel Mode Setting(KMS)从用户态移入内核,DRM开始接管显示模式设置 |
| 2012年 | Linux 3.3 | Atomic Mode Set(原子模式设置)概念提出,DRM进入原子化时代 |
| 2015年 | Linux 4.2 | Atomic API正式稳定,DRM框架支持无撕裂、无闪烁的显示更新 |
| 2018年至今 | 持续演进 | 引入HDCP、HDR、VRR(可变刷新率)、DisplayPort MST等高级特性 |
关键转折点:KMS从用户态(X Server)移入内核态是DRM历史上最重要的决策。这一变化使得显示模式切换不再依赖X Server,嵌入式设备可以在没有完整桌面环境的情况下直接驱动显示硬件,为Android、Yocto等嵌入式系统铺平了道路。
1.2 DRM在Linux图形栈中的位置
DRM位于Linux图形栈的中间层,向上对接用户态图形库,向下控制具体显示硬件。理解它的位置对于驱动开发至关重要。
嵌入式视角:在嵌入式Linux系统中,DRM是连接硬件和上层图形框架的桥梁。与桌面不同,嵌入式系统通常没有X Server,Wayland/Weston或Android的HWC(Hardware Composer)直接通过DRM的Atomic API与内核交互。这意味着嵌入式DRM驱动开发者需要同时理解硬件寄存器级操作和DRM框架的抽象层。
1.3 DRM核心组件概念介绍
DRM/KMS框架将显示硬件抽象为五个核心组件:Framebuffer、CRTC、Encoder、Connector和Plane。这五个组件构成了现代显示管线的完整数据流。
1.3.1 核心组件总览
| 组件 | 内核结构体 | 功能描述 | 硬件对应 |
|---|---|---|---|
| Framebuffer | struct drm_framebuffer |
存储一帧完整的像素数据,包含颜色格式、分辨率、内存布局 | 显存中的帧缓冲区 |
| CRTC | struct drm_crtc |
控制显示时序生成,负责从Framebuffer读取数据并生成视频信号 | 显示控制器(Display Controller) |
| Encoder | struct drm_encoder |
将CRTC输出的并行视频信号编码为特定接口的串行信号 | HDMI/DVI/LVDS/MIPI DSI发送器 |
| Connector | struct drm_connector |
代表物理显示接口,检测连接状态、读取EDID、管理显示模式 | HDMI/DP/VGA物理接口 |
| Plane | struct drm_plane |
硬件叠加层,支持独立缩放、旋转、颜色转换,实现多图层合成 | 硬件Overlay/合成器 |
1.3.2 显示管线数据流
五个组件按照固定的管线顺序协同工作,形成从内存到屏幕的完整数据通路:
1.3.3 各组件详解
Framebuffer(帧缓冲区)
Framebuffer是显示数据的源头,它封装了内存中一帧图像的完整描述。驱动开发者需要关注:
- 像素格式:ARGB8888、XRGB8888、RGB565、NV12(YUV)等
- 内存布局:线性布局、Tiled布局、Swizzle布局
- 多平面格式:NV12等YUV格式需要多个内存平面(plane)描述
- DMA-BUF:现代DRM驱动通过DMA-BUF实现Framebuffer的跨设备共享
// Framebuffer创建示例(简化)
struct drm_framebuffer *fb;
struct drm_mode_fb_cmd2 r = {
.width = 1920,
.height = 1080,
.pixel_format = DRM_FORMAT_XRGB8888,
.handles[0] = gem_handle,
.pitches[0] = 1920 * 4,
};
fb = drm_internal_framebuffer_create(dev, &r, NULL);
CRTC(显示控制器)
CRTC是显示管线的核心调度器,负责:
- 时序生成:产生VSYNC、HSYNC、DE(数据使能)等同步信号
- 扫描输出:按行/帧从Framebuffer读取像素数据
- 模式设置:配置分辨率、刷新率、时钟频率
- Gamma校正:内置LUT(查找表)进行颜色校正
嵌入式常见陷阱:很多嵌入式SoC的显示控制器支持多个CRTC,但每个CRTC只能连接一个Encoder。在Device Tree中,CRTC通常对应
display-controller节点,其port子节点描述与Encoder的连接关系。错误的连接配置会导致显示无输出。
Encoder(编码器)
Encoder将CRTC输出的并行RGB/YCbCr信号转换为特定显示接口所需的串行信号:
| Encoder类型 | 输出接口 | 典型芯片 | 嵌入式常见场景 |
|---|---|---|---|
DRM_MODE_ENCODER_TMDS |
HDMI/DVI | IT66121, ADV7511 | HDMI输出 |
DRM_MODE_ENCODER_LVDS |
LVDS | SN75LVDS83 | 工业屏/车载屏 |
DRM_MODE_ENCODER_DSI |
MIPI DSI | SoC内置DSI控制器 | 手机/平板/智能家居 |
DRM_MODE_ENCODER_DPI |
RGB并行 | SoC内置RGB接口 | 小尺寸LCD屏 |
DRM_MODE_ENCODER_DP |
DisplayPort | ANX7688, Parade PS8640 | 高分辨率/高刷新率 |
Connector(连接器)
Connector是DRM框架中与物理世界交互的接口,承担以下职责:
- 热插拔检测:通过HPD(Hot Plug Detect)引脚检测显示器连接/断开
- EDID读取:通过I2C读取显示器的EDID数据,获取支持的分辨率列表
- 模式列表管理:维护显示器支持的所有显示模式(
drm_display_mode) - 属性暴露:向用户态暴露连接状态、物理尺寸、宽高比等属性
// Connector初始化示例(嵌入式DSI屏)
static const struct drm_connector_funcs my_connector_funcs = {
.detect = my_connector_detect, // 检测连接状态
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = my_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
// 对于固定连接的嵌入式屏,detect直接返回connected
static enum drm_connector_status my_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_connected; // 固定连接,始终返回已连接
}
Plane(硬件叠加层)
Plane是DRM框架中较晚引入但非常重要的组件,它代表了硬件的图层合成能力:
- 主平面(Primary Plane):每个CRTC至少有一个主平面,是默认的显示层
- 光标平面(Cursor Plane):专门用于鼠标光标,支持硬件光标
- 叠加平面(Overlay Plane):额外的硬件层,支持独立缩放、Alpha混合、颜色键
Plane在嵌入式中的价值:嵌入式SoC通常有2-4个硬件叠加层。合理使用Plane可以实现视频播放、UI叠加、画中画等功能,而无需CPU参与图层合成。例如,在Rockchip RK3399上,通过配置Video Plane和UI Plane,可以实现4K视频播放与系统UI的无缝叠加,CPU占用率降低80%以上。
1.3.4 组件间的连接关系
在DRM框架中,五个组件通过可能的连接关系(possible clones/possible crtcs)形成拓扑结构。驱动初始化时需要明确声明这些连接关系:
// 驱动初始化时声明组件连接关系
static int my_display_init(struct drm_device *drm)
{
struct drm_plane *primary_plane;
struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector *connector;
// 1. 创建Plane,声明它可以连接到哪些CRTC(位掩码)
primary_plane = my_plane_init(drm, DRM_PLANE_TYPE_PRIMARY);
primary_plane->possible_crtcs = 1 << 0; // 只能连接到CRTC 0
// 2. 创建CRTC,绑定Plane
crtc = my_crtc_init(drm, primary_plane);
// 3. 创建Encoder,声明它可以连接到哪些CRTC
encoder = my_encoder_init(drm);
encoder->possible_crtcs = 1 << 0; // 只能连接到CRTC 0
encoder->possible_clones = 0; // 不支持克隆(多显复制)
// 4. 创建Connector,绑定Encoder
connector = my_connector_init(drm);
drm_connector_attach_encoder(connector, encoder);
return 0;
}
1.4 核心组件交互流程
当用户态应用请求显示一帧图像时,DRM框架内部经历以下完整流程:
- 分配Framebuffer:用户态通过
drmModeAddFB2()创建Framebuffer,内核分配GEM对象并映射到DMA地址 - 提交Atomic请求:用户态构造Atomic请求,包含Plane与Framebuffer的绑定关系、CRTC模式、Connector状态等
- 校验(Check):内核调用各个组件的
.atomic_check()回调,验证资源是否充足、状态是否合法 - 提交(Commit):通过
.atomic_commit()将配置写入硬件寄存器,等待VSYNC信号完成切换 - 完成通知:硬件VSYNC中断触发后,内核发送
DRM_EVENT_FLIP_COMPLETE事件通知用户态
1.5 本章小结
DRM框架是嵌入式Linux显示驱动的基石,理解其核心组件是后续深入学习的前提:
- 历史脉络:从DRI到KMS再到Atomic,DRM经历了20年的演进,每一次变革都解决了特定的显示痛点
- 架构位置:DRM位于内核态,向上提供统一的
/dev/dri/card0接口,向下适配各类显示硬件 - 五大组件:Framebuffer(数据源)、Plane(图层合成)、CRTC(时序控制)、Encoder(信号编码)、Connector(物理接口)构成了完整的显示管线
- Atomic机制:通过一次原子提交完成所有组件的状态切换,从根本上解决了显示撕裂和闪烁问题