DRM显示子系统:核心对象与显示链路拆分
DRM 显示子系统看起来对象很多,本质上是在按职责拆链路。拆得越清楚,驱动越容易复用,状态也越容易验证。
1. drm_device
所有对象最后都挂在 drm_device 下面。它可以理解成这套 DRM 设备在内核里的总入口,既承接用户态 ioctl,也管理对象生命周期、模式配置和事件系统。
真正和显示模式设置强相关的是 drm_device 里的 mode_config。驱动初始化时,通常会先把 mode_config 搭起来,再去注册 plane、crtc、encoder、connector 这些对象。
如果把一套显示驱动想成一栋楼:
drm_device是整栋楼;mode_config是这栋楼的平面图和物业规则;- 下面那些对象才是住在楼里的具体住户。
2. drm_framebuffer
在 DRM 里,framebuffer 更像一层“显示视图”:它描述一块图像该怎么被显示,但并不等于那块内存本身。
struct drm_framebuffer 里比较关键的字段大致是这样:
struct drm_framebuffer {
struct drm_device *dev;
struct list_head head;
struct drm_mode_object base;
const struct drm_framebuffer_funcs *funcs;
unsigned int pitches[DRM_FORMAT_MAX_PLANES];
unsigned int offsets[DRM_FORMAT_MAX_PLANES];
uint64_t modifier;
const struct drm_format_info *format;
unsigned int width;
unsigned int height;
int hot_x;
int hot_y;
...
};
其中:
width/height:这块图像的尺寸;format:像素格式,比如RGB888、NV12;pitches[]:每个平面的行步长;offsets[]:每个平面相对底层缓冲区起始位置的偏移;modifier:像素在内存里的布局修饰信息,比如线性布局、压缩布局或厂商私有 tiled 布局。
行步长(pitch / stride)
表示图像每一行在内存里实际占用多少字节。它可能大于“宽度 × 每像素字节数”,因为很多硬件要求行对齐。
modifier
描述像素在内存中的实际组织方式。两块同样是 1920×1080 的图像,一块可以线性排布,另一块也可以是压缩或 tiled 布局。对显示硬件来说,这一点非常关键。
framebuffer 是显示元数据,底下真正承载像素的是 GEM buffer 或其他后端内存对象。
3. drm_plane
Plane 负责从 framebuffer 取数据,然后决定这块图像怎么放进最终画面。常见的动作包括:
- 裁剪源图像;
- 设定屏幕上的显示位置;
- 缩放;
- 像素格式转换;
- 和其他 plane 一起混合。
用户态看到一个 plane,本质上是在说:“我想把这块图像,以这样的裁剪和缩放方式,摆到这个 CRTC 的某个位置上。”
Plane 不直接决定时序,它决定的是图像如何进入显示控制器。
关于 Plane 的一些术语:
primary plane
主图层。传统上每个 CRTC 至少会有一个 primary plane,用来承载主画面。
overlay plane
叠加图层。常用来做视频层、UI 叠加层、局部刷新层。
cursor plane
光标专用图层。因为光标很小、更新很频繁,很多硬件会给它单独做一条轻量路径。
4. drm_crtc
CRTC 这个名字来自老式 CRT 时代,可以直接把它理解成“显示扫描与时序控制中心”。
一个 CRTC 主要做两件事:
- 接住一个或多个 plane 的结果;
- 按设定好的模式生成最终输出时序。
它既管“什么时候开始一行、什么时候结束一行”,也管“这一帧的图层结果什么时候真正切到屏幕上”。
和 CRTC 相关的基础对象回调在 drm_crtc_funcs 里。下面只摘几个经常会被碰到的字段:
struct drm_crtc_funcs {
void (*reset)(struct drm_crtc *crtc);
int (*set_config)(struct drm_mode_set *set,
struct drm_modeset_acquire_ctx *ctx);
int (*page_flip)(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
uint32_t flags,
struct drm_modeset_acquire_ctx *ctx);
int (*enable_vblank)(struct drm_crtc *crtc);
void (*disable_vblank)(struct drm_crtc *crtc);
struct drm_crtc_state *(*atomic_duplicate_state)(struct drm_crtc *crtc);
void (*atomic_destroy_state)(struct drm_crtc *crtc,
struct drm_crtc_state *state);
...
};
这里先记住一件事:CRTC 不是“最终显示器”,它主要负责“按节奏往外吐像素流”。
真正和 atomic enable / disable / flush 这类显示流程强相关的回调,通常是在 drm_crtc_helper_funcs 里。
5. drm_encoder
Encoder 负责把 CRTC 产出的像素流引到某条具体输出路径上。对某些硬件来说,它确实对应一个真实模块;对另一些硬件来说,它更偏向框架里的路由节点。
和前面几个对象比,encoder 往往更容易被误解。它的重点不是“存像素”,而是“表示这条输出路线上的一个发送端角色”。
struct drm_encoder 里和理解关系最直接的字段大致如下:
struct drm_encoder {
struct drm_device *dev;
struct list_head head;
struct drm_mode_object base;
char *name;
int encoder_type;
uint32_t possible_crtcs;
uint32_t possible_clones;
const struct drm_encoder_funcs *funcs;
struct drm_crtc *crtc;
...
};
这几个字段里最值得注意的是:
possible_crtcs:这个 encoder 允许挂到哪些 CRTC 上;crtc:当前实际绑定的是哪一个 CRTC。
注意一点:是 encoder 关联 CRTC,不是 CRTC 里塞一个 encoder 成员把链路写死。
6. drm_connector
Connector 是用户态最容易直接感知的对象。显示器连没连上、支持哪些模式、当前状态是不是 connected,用户态通常都要通过 connector 来看。
它不是面板本身,也不是桥接芯片本身,而是“这条输出在用户态视角下的接口对象”。
对外部显示器来说,connector 的存在感很强;对内嵌屏幕来说,connector 依然常常存在,只是它背后连接的是固定 panel,而不是一个会插拔的显示器。
7. drm_bridge
Bridge 的概念其实很朴素:凡是位于 encoder 后、panel 前,负责补足一段链路功能的,都可以抽象成 bridge。
它可以是:
- MIPI DSI 到 LVDS 的转换器;
- SerDes serializer / deserializer;
- eDP / LVDS / RGB 之间的协议桥;
- 甚至是一段需要单独管理时序和上电过程的输出中间件。
Bridge 的价值在于,它把“中间这一段链路”从大驱动里拆了出来。这样不同 SoC、不同输出口、不同外设之间就能组合,而不是彼此焊死。
8. drm_panel
Panel 一般对应最终显示面板。它主要关心两类事:
- 提供显示模式;
- 管理 prepare / enable / disable / unprepare 这套上下电流程。
drm_panel_funcs 很短,也正因为短,反而特别清楚:
struct drm_panel_funcs {
int (*prepare)(struct drm_panel *panel);
int (*enable)(struct drm_panel *panel);
int (*disable)(struct drm_panel *panel);
int (*unprepare)(struct drm_panel *panel);
int (*get_modes)(struct drm_panel *panel,
struct drm_connector *connector);
enum drm_panel_orientation (*get_orientation)(struct drm_panel *panel);
};
这一组回调的职责边界很干净:
prepare:把电源、复位、寄存器初始化这类“点亮前准备”做好;enable:真正把画面亮起来,常见动作是开背光;disable:先熄灭画面;unprepare:再撤掉电源和链路资源;get_modes:把 panel 支持的模式交给 connector。
注意:connector 和 panel 不是一回事。
connector 是用户态看到的输出端,panel 是物理显示终端。很多时候二者会在同一条链路上出现,但职责完全不同。

浙公网安备 33010602011771号