4. DRM框架入门

DRM(Direct Rendering Manager)是Linux内核中负责图形显示与GPU渲染资源管理的子系统。它最初用于解决多进程直接操作显卡硬件带来的冲突问题,如今已演化为一个分层清晰、职责明确的显示框架。在Android系统中,SurfaceFlinger、HWC以及各类GPU驱动都通过DRM接口与内核显示硬件交互。理解DRM,核心是理解它如何把"一帧画面从内存送到屏幕"这件事拆解成若干可组合、可复用的对象。

DRM = KMS(显示控制) + GEM(显存管理)。KMS负责"怎么显示",GEM负责"显示什么、放在哪"。

一、KMS:内核模式设置

KMS(Kernel Mode Setting)是DRM中负责显示管线配置与控制的子系统。它把显示硬件抽象为一组对象,每个对象承担明确职责,并通过属性(Property)机制对外暴露可配置参数。KMS的核心对象包括CRTC、Encoder、Connector、Plane和Bridge。

  • CRTC:显示控制器,代表一个扫描输出引擎。它从Framebuffer读取像素,按时序产生扫描信号。一个CRTC对应一路独立的显示输出,负责分辨率、刷新率、扫描时序等。
  • Encoder:编码器,把CRTC输出的像素时序转换为Connector可接收的信号格式,例如RGB并行、LVDS、MIPI DSI、HDMI TMDS等。
  • Connector:连接器,代表物理显示接口,如HDMI口、MIPI DSI排线座、eDP屏线。它负责读取EDID、检测热插拔、报告连接状态。
  • Plane:图层,代表一个可独立叠加的像素来源。它关联Framebuffer,支持位置、缩放、旋转、Alpha混合、Z序等属性。硬件通常有Primary、Overlay、Cursor等Plane。
  • Bridge:桥接器,用于连接非标准显示链路,例如DSI到LVDS转换芯片、MIPI DSI到HDMI桥、Type-C转接芯片等。Bridge把Encoder与Connector之间的信号做转换或中继。

这些对象之间形成一条清晰的信号链:Framebuffer → Plane → CRTC → Encoder → Bridge(可选)→ Connector → 物理屏幕。KMS的职责就是根据用户空间请求,把这条链路上的对象连接起来并配置属性。

DRM 显示管线对象关系图 GEM 显存管理 Dumb Buffer GEM Object Framebuffer 像素格式/宽高/Stride Plane 位置/缩放/Alpha/Z序 CRTC 扫描时序/刷新率 Encoder RGB/LVDS/DSI/TMDS Bridge 信号转换/中继 Connector EDID/热插拔/状态 物理屏幕 LCD / OLED / HDMI 可选路径 信号链: Framebuffer → Plane → CRTC → Encoder → Bridge(可选) → Connector → 屏幕 职责划分: GEM 管内存与缓冲,KMS 管显示对象连接与属性配置 用户空间通过 DRM ioctl / Atomic API 提交配置,内核完成校验后生效

二、GEM:显存与缓冲管理

GEM(Graphics Execution Manager)负责显存对象的分配、共享与生命周期管理。它不关心显示时序,只关心"一块内存如何被GPU、显示控制器和用户空间安全地共享"。

GEM的核心概念是GEM Object,它代表一块可被GPU访问的显存区域。用户空间通过DRM_IOCTL_GEM_CREATE创建对象,通过mmap映射到用户地址空间,通过PRIME机制在进程间或设备间共享。Android中常见的Dumb Buffer是一种最简单的GEM对象,用于CPU可访问的线性缓冲,常用于早期bring-up和简单测试。

/* 典型 GEM 创建与映射流程(伪代码) */
struct drm_mode_create_dumb create = {
    .width  = 1920,
    .height = 1080,
    .bpp    = 32,
};
ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);

struct drm_mode_map_dumb map = { .handle = create.handle };
ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
void *addr = mmap(0, create.size, PROT_READ | PROT_WRITE,
                  MAP_SHARED, fd, map.offset);

GEM与KMS的分工非常清晰:GEM提供Framebuffer背后的内存对象,KMS把Framebuffer绑定到Plane,再由CRTC扫描输出。没有GEM,KMS就没有像素来源;没有KMS,GEM分配的内存也无法变成屏幕上的画面。

三、Atomic模式与属性配置

传统DRM使用Legacy ioctl逐个设置CRTC、Plane、Connector,容易出现中间状态和竞态。现代DRM推荐使用Atomic模式:用户空间一次性提交一组属性变更,内核先做原子校验(Atomic Check),全部通过后再提交生效(Atomic Commit)。

对象核心职责关键属性示例
CRTC扫描输出与时序生成mode、active、out_fence_ptr
Plane图层叠加与像素来源fb_id、crtc_id、src_x/y、crtc_x/y、alpha、zpos
Connector物理接口与状态检测crtc_id、dpms、EDID、link_status
Encoder信号格式转换possible_crtcs、possible_clones
Bridge非标准链路转换bridge chain、timings、format

四、典型场景:Android开机显示初始化

以Android设备开机为例,显示驱动通常经历以下阶段:

  1. 内核启动,DRM驱动probe,注册CRTC、Plane、Encoder、Connector、Bridge等对象。
  2. Connector检测到屏幕连接,读取EDID或使用Panel固定时序。
  3. Bootloader或内核早期通过简单Framebuffer显示开机Logo。
  4. SurfaceFlinger启动,通过DRM Atomic API接管显示,创建GEM Buffer并绑定到Plane。
  5. HWC根据图层合成策略,决定使用GPU合成还是Overlay Plane直出。
  6. 每次提交通过Atomic Commit生效,CRTC按VSync节奏扫描输出。
调试显示问题时,优先确认对象拓扑是否正确:modetest -M xxx -p 可以列出所有CRTC、Encoder、Connector及其连接关系;modetest -M xxx -c 查看Connector状态;modetest -M xxx -a 查看所有属性。

五、常见误区与复盘

  • 把Bridge当成Connector:Bridge是信号转换器,Connector才是物理接口。DSI到HDMI桥芯片应注册为Bridge,最终仍需要一个HDMI Connector。
  • 认为一个CRTC只能接一个Connector:一个CRTC同一时刻只能驱动一个Connector,但可以通过possible_crtcs与多个Encoder关联,实现灵活路由。
  • 忽略Plane的格式与缩放限制:并非所有Plane都支持任意格式和缩放比例,Atomic Check会拒绝不支持的配置,导致提交失败。
  • 混淆GEM与Framebuffer:GEM Object是内存,Framebuffer是对内存的显示描述(格式、宽高、Stride)。一个GEM Object可以创建多个Framebuffer。
  • 忘记处理Atomic Commit的Fence:异步提交需要等待out_fence,否则会出现撕裂或提交顺序错乱。
在Android HWC与DRM集成中,最常见的失败原因是Atomic Check返回-EINVAL。此时应逐项检查Plane的格式、缩放能力、CRTC的mode是否匹配、Connector是否已连接,而不是盲目重试提交。

掌握DRM的对象模型,是后续进行Android HWC集成、多屏异显、Overlay优化和显示问题定位的基础。KMS负责"怎么显示",GEM负责"显示什么",Atomic API负责"如何安全地一次性提交",三者共同构成现代Linux显示管线的核心骨架。