4. DRM核心对象模型
在 Linux DRM(Direct Rendering Manager)子系统中,显示管线并不是一个“大而全”的单一对象,而是被拆分为若干职责明确、彼此协作的核心对象。理解这些对象的职责边界与连接关系,是读懂 STM32MP 平台显示驱动、使用 modetest 枚举资源、以及后续排查显示问题的前提。本节先建立对象模型,再结合 modetest 在 STM32MP 上的典型输出逐项解读。
一、六个核心对象的职责
drm_device 是整个 DRM 实例的根对象,通常对应一个显示控制器(如 STM32MP 的 LTDC 或 DSI 控制器)。它持有 mode_config,管理本设备上的 CRTC、encoder、connector、plane、framebuffer 等资源列表,并负责文件句柄、GEM 内存管理、中断与 IOCTL 分发。可以把它理解为“显示子系统的总入口”。
drm_crtc 代表一个显示扫描输出通道,负责从 framebuffer 取像素、按 mode 时序产生扫描信号,并驱动 encoder。它持有当前 mode、framebuffer 引用、扫描位置等状态。STM32MP 的 LTDC 通常对应一个 CRTC,若同时使用 DSI 输出,则可能对应另一个 CRTC。
drm_encoder 负责把 CRTC 产生的像素时序转换为某种物理接口信号,例如 RGB 并行、DSI、HDMI 等。它本身不产生时序,而是“翻译”时序。一个 encoder 只能被一个 CRTC 驱动,但可以被多个 connector 共享(实际中通常一对一)。
drm_connector 代表最终对外连接的物理接口或显示终端,例如 DSI 面板、HDMI 接口、VGA 接口。它负责探测连接状态(connected/disconnected)、读取 EDID、暴露可用 mode 列表,并作为用户空间设置显示模式的入口。connector 通过 encoder 与 CRTC 建立链路。
drm_plane 是图层抽象,负责把 framebuffer 的某块区域合成到 CRTC 的扫描输出上。它分为 primary、overlay、cursor 三类。primary plane 通常与 CRTC 一一对应,是显示主画面;overlay plane 用于叠加视频或 UI 层;cursor plane 用于硬件光标。plane 支持位置、缩放、格式、alpha 等属性。
drm_framebuffer 是显存中的一块像素缓冲区,包含宽、高、像素格式、pitch、offset 以及底层 GEM 对象引用。它不关心显示时序,只描述“图像数据长什么样”。CRTC 或 plane 通过引用 framebuffer 来获取要显示的像素。
二、对象之间的连接关系
从数据流角度看,framebuffer 提供像素,plane 负责合成,CRTC 负责扫描输出,encoder 负责接口转换,connector 负责对外连接。用户空间通过 connector 选择 mode,通过 CRTC 设置时序,通过 plane 绑定 framebuffer,最终形成一条完整的显示链路。下图给出这六个对象在 STM32MP 平台上的典型关系。
三、用 modetest 枚举 STM32MP 显示资源
modetest 是 libdrm 提供的调试工具,它通过 DRM IOCTL 读取当前设备的资源列表,并以文本形式打印。在 STM32MP 开发板上,通常先确认 DRM 设备节点:
ls /dev/dri/
# 典型输出:card0 renderD128
modetest -M stm -c
其中 -M stm 指定使用 STM 驱动模块,-c 表示只枚举 connector。实际使用时也可以直接 modetest -M stm 输出全部资源。下面给出一段典型的枚举输出片段(示例数据,用于说明格式,并非某块板卡的实测值):
Connectors:
id encoder status name size (mm) modes encoders
38 37 connected DSI-1 154x86 1 37
mode: "800x480": 60 33200 800 840 888 928 480 493 496 502 0x0 0x0
props:
1 EDID: flags=immutable blob_id=0
2 DPMS: flags=enum enum_values=["On","Standby","Suspend","Off"]
5 link-status: flags=enum enum_values=["Good","Bad"]
6 non-desktop: flags=immutable range=[0,1]
Encoders:
id crtc type possible crtcs possible clones
37 35 DSI 0x00000001 0x00000001
CRTCs:
id fb pos size
35 40 (0,0) (800,480)
mode: "800x480": 60 33200 800 840 888 928 480 493 496 502 0x0 0x0
props:
7 ACTIVE: flags=range value=1
8 MODE_ID: flags=blob blob_id=0
Planes:
id crtc fb CRTC x,y x,y gamma size possible crtcs
31 35 40 0,0 0,0 0 0x00000001
formats: AR24 XR24 RG16
props:
9 type: flags=enum enum_values=["Overlay","Primary","Cursor"]
10 IN_FORMATS: flags=immutable blob_id=0
Framebuffers:
id size pitch
40 800x480 3200
四、逐项解读输出
Connectors 段:id=38 是 connector 编号,encoder=37 表示它当前绑定到编号 37 的 encoder,status=connected 表示面板已连接。name=DSI-1 说明这是 DSI 接口。size 154x86 是面板物理尺寸(毫米)。modes 数量为 1,下面列出具体 mode:800x480@60,像素时钟 33200 kHz,后面依次是水平有效、水平前肩、水平同步、水平后肩,以及垂直有效、垂直前肩、垂直同步、垂直后肩。这些参数正是 CRTC 扫描时序的来源。
Encoders 段:id=37,crtc=35 表示它当前被 CRTC 35 驱动,type=DSI 表示接口类型,possible crtcs=0x00000001 表示它只能被 CRTC 0 驱动(位掩码)。
CRTCs 段:id=35,fb=40 表示当前绑定的 framebuffer 编号,pos=(0,0) 是扫描起始位置,size=(800,480) 是当前输出分辨率。mode 行与 connector 中一致,说明该 CRTC 正在使用这个时序。ACTIVE=1 表示 CRTC 已启用。
Planes 段:id=31,crtc=35 表示它当前挂在 CRTC 35 上,fb=40 表示绑定了 framebuffer 40,x,y=(0,0) 是图层在屏幕上的位置。formats 列出支持的像素格式:AR24(ARGB8888)、XR24(XRGB8888)、RG16(RGB565)。type 属性说明它是 Primary 还是 Overlay。
Framebuffers 段:id=40,size=800x480,pitch=3200 字节。pitch 是每行字节数,800 像素 × 4 字节/像素 = 3200,说明当前使用 32 位像素格式,与 AR24/XR24 一致。
五、从枚举到实际设置
枚举只是“看”,真正设置显示需要把上述对象串起来。一个典型流程是:先根据 connector 的 mode 列表选定 800x480@60,然后创建 framebuffer(或使用已有 fb),再通过 atomic 或 legacy IOCTL 把 CRTC、plane、connector 的状态一次性提交。modetest 也支持直接设置:
modetest -M stm -s 38:800x480 -P 31@35:800x480+0+0
其中 -s 38:800x480 表示把 connector 38 设置为 800x480 模式,-P 31@35:800x480+0+0 表示把 plane 31 绑定到 CRTC 35,位置 (0,0),尺寸 800x480。执行后如果屏幕出现测试图案,说明整条链路 framebuffer→plane→crtc→encoder→connector 已经打通。
六、对象模型带来的工程意义
把显示拆成六个对象,最大的好处是“可组合、可替换、可调试”。例如更换面板只需改 connector 的 mode 和 EDID;调整图层叠加只需改 plane 属性;切换接口只需换 encoder 类型;而 framebuffer 的格式与 pitch 则决定了内存带宽和显示效果。在 STM32MP 上,LTDC 通常提供 primary plane 和 overlay plane,DSI 作为 encoder,面板作为 connector,理解这些对应关系后,阅读驱动代码和排查显示异常都会更有方向。
最后需要强调:modetest 输出中的 id 是运行时动态分配的,不同板卡、不同内核版本可能不同,但对象类型和字段含义是稳定的。掌握“看类型、看状态、看绑定关系”这三步,就能从任意一段 modetest 输出中还原出当前的显示拓扑。