DRM显示子系统:funcs 与 helper funcs 的分工
1. 为什么 DRM 要分成两层函数表
在 DRM 子系统中,对象旁边总挂着好几套函数表:funcs、helper_funcs、helper_private,有时还会有 atomic_* 回调分散在不同层里。
其中:
funcs更偏对象本身的基础能力和生命周期;helper funcs更偏 DRM/KMS 这套显示流程里的通用阶段划分;- 很多 atomic、modeset、bridge 链路相关的回调,真正落点是在 helper 层,而不是都塞进
funcs。
这套分层把两类事情拆开:
- 这个对象作为一个 DRM object,本身需要具备哪些基本能力;
- 这个对象在一次显示模式设置或 atomic 提交里,要在什么阶段参与。
如果没有 helper 层,驱动作者就得自己重复处理大量共性的显示流程:模式校验、状态遍历、对象 enable/disable 顺序、bridge 链调用、原子提交阶段切分。
有了 helper 层之后,DRM core 和 helper 库就能把“流程骨架”先搭好,驱动只负责把真正和硬件相关的那部分实现进去。
可以把它理解成这样:
funcs
-> 这个对象最基本该怎么活着:创建、释放、状态复制、事件、vblank、属性入口
helper funcs
-> 这个对象在显示流水线里该怎么工作:校验、设模式、开关链路、更新图层、统一 flush
所以 helper 不是“可有可无的工具函数集合”,它本质上是在给 DRM 提供一套统一的显示流程约定。
2. 一个对象为什么既有 funcs,又有 helper_private
以 CRTC、plane、encoder、connector 这些对象为例,驱动在初始化时通常会做两件事:
- 用
drm_*_init()之类的接口把对象注册到 DRM core; - 再用
drm_*_helper_add()把 helper 回调挂上去。
从对象关系上看,大致可以理解成:
drm_plane / drm_crtc / drm_encoder / drm_connector
-> funcs : core 层入口
-> helper_private : helper 层入口
也就是说,同一个对象同时挂着两套行为:
- 一套让 DRM core 知道“这个对象最基本怎么管理”;
- 一套让 KMS helper 知道“这个对象在显示提交流程里怎么参与”。
这也是为什么在驱动代码里经常能同时看到:
drm_crtc_init_with_planes(..., &my_crtc_funcs, ...);
drm_crtc_helper_add(crtc, &my_crtc_helper_funcs);
这两步不是重复,而是在给不同层注册不同的能力。
3. CRTC
CRTC 是最适合用来理解这件事的对象,因为它同时处在“对象管理”和“显示提交”两条线上。
先看 drm_crtc_funcs。这一层更像 CRTC 的基础对象能力:
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 状态怎么 reset;
- legacy 路径下怎么
set_config或page_flip; - vblank 中断怎么开关;
- atomic state 怎么复制和释放。
再看 drm_crtc_helper_funcs,这里就更像“CRTC 在 modeset / atomic 流程里的职责”:
struct drm_crtc_helper_funcs {
enum drm_mode_status (*mode_valid)(struct drm_crtc *crtc,
const struct drm_display_mode *mode);
bool (*mode_fixup)(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
int (*mode_set_nofb)(struct drm_crtc *crtc);
void (*atomic_enable)(struct drm_crtc *crtc,
struct drm_atomic_state *state);
void (*atomic_disable)(struct drm_crtc *crtc,
struct drm_atomic_state *state);
void (*atomic_begin)(struct drm_crtc *crtc,
struct drm_atomic_state *state);
void (*atomic_flush)(struct drm_crtc *crtc,
struct drm_atomic_state *state);
...
};
这一层关注的则是:
- 这个模式对当前 CRTC 来说是否有效;
- 如有需要,模式是否要被修正成
adjusted_mode; - 在没有 framebuffer 切换时,模式本身怎么写入硬件;
- 一次 atomic 提交里,CRTC 什么时候 enable、disable、begin、flush。
像
atomic_enable、atomic_disable、atomic_flush这种回调,通常应该从 helper 视角去理解,而不是drm_crtc_funcs。
4. Plane
Plane 的分层比 CRTC 还更直观。
Plane 本身当然也有自己的 funcs,比如 reset、state copy、destroy、property set 等。但一旦进入“真正要更新画面”的路径,驱动更常碰到的是 drm_plane_helper_funcs:
struct drm_plane_helper_funcs {
int (*prepare_fb)(struct drm_plane *plane,
struct drm_plane_state *new_state);
void (*cleanup_fb)(struct drm_plane *plane,
struct drm_plane_state *old_state);
int (*atomic_check)(struct drm_plane *plane,
struct drm_atomic_state *state);
void (*atomic_update)(struct drm_plane *plane,
struct drm_atomic_state *state);
void (*atomic_disable)(struct drm_plane *plane,
struct drm_atomic_state *state);
...
};
这几个回调放在一起看,会很清楚:
prepare_fb:提交前先把 framebuffer 相关准备做好;atomic_check:检查这个 plane 在本次提交里是否合法;atomic_update:把 plane 的新输入参数写给硬件;atomic_disable:这个 plane 这次如果不用了,怎么停掉。
这里特别值得强调的是:
plane 负责的是自己的输入配置更新,不负责全局统一生效。
所以这里没有 plane->atomic_flush() 这种入口。更常见的模式是:
- 每个 plane 先各自把参数准备好;
- CRTC 在
atomic_begin/atomic_flush阶段统一做最终提交或 GO bit 触发。
这个分工特别符合真实硬件的组织方式:多个图层先分别配置,最后由显示控制器在某个时刻一起切过去。
5. Encoder 和 Connector
encoder 和 connector 的 helper 分工,通常没有 plane / crtc 那么显眼,但它们在链路里的位置很关键。
对 encoder 来说,core 层更关心它作为一个对象怎么注册、怎么和 CRTC 建立关联;helper 层则更关心它在模式设置链路里怎么参与校验和 enable/disable。
对 connector 来说,core 层负责的是:
- connector 对象的生命周期;
- 属性与状态对象;
- 用户态能看到哪些输出端对象。
helper 层更常承担的是:
- 探测当前连接状态;
- 获取模式列表;
- 在 modeset 流程里配合 encoder / bridge / panel 完成链路收敛。
它们不一定像 plane 那样有强烈的“每帧更新感”,但在“把这条链路对用户态讲清楚”这件事上非常重要。
6. Bridge
Bridge 的函数表经常比较长,因为它既要参与链路拼接,也要参与模式协商和上下电顺序:
struct drm_bridge_funcs {
int (*attach)(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags);
void (*detach)(struct drm_bridge *bridge);
enum drm_mode_status (*mode_valid)(struct drm_bridge *bridge,
const struct drm_display_mode *mode);
bool (*mode_fixup)(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
void (*disable)(struct drm_bridge *bridge);
void (*post_disable)(struct drm_bridge *bridge);
void (*mode_set)(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode);
void (*pre_enable)(struct drm_bridge *bridge);
void (*enable)(struct drm_bridge *bridge);
...
};
这组回调大致分成三类:
- 链路构建:
attach/detach - 模式处理:
mode_valid/mode_fixup/mode_set - 链路时序:
pre_enable/enable/disable/post_disable
其中最重要的一点,不是记住名字,而是记住顺序。
Bridge 驱动的工作往往不是“算一堆参数”,而是把一段物理链路在正确时机拉起来、再在正确时机拆掉。
7. 一次 atomic 提交里,这些 helper 回调大概站在哪
把 funcs 和 helper funcs 分清之后,还是会有一个疑问:
“它们在一次真实提交里,各个 helper 谁先谁后?”
可以先用一个粗粒度视角来理解:
atomic 提交
-> 先收集并校验各对象 state
-> plane helper 做 prepare/check/update
-> crtc helper 做 mode/enable/begin/flush
-> bridge helper 按顺序 pre_enable/enable 或 disable/post_disable
-> connector/panel 配合完成末端链路状态切换
如果把 helper 回调放到显示流水线里看,感觉会更清楚:
这张图不是在抠每一个 helper 函数的精确源码调用点,而是在建立一个顺序感:
- plane 更偏输入更新;
- crtc 更偏统一提交;
- bridge / panel 更偏输出链路时序;
- connector 更偏对用户态暴露结果。
8. mode_valid 和 atomic_check 的边界
这两个概念特别容易混,因为它们看起来都像“检查”。
mode_valid
更适合做“这个模式本身合不合法”的判断,比如像素时钟太高、分辨率超上限、链路位宽不支持。
atomic_check
更适合做“这次提交出来的整组状态合不合法”的判断,比如 plane 路由冲突、带宽不够、缩放比例不支持、同一次提交里多个对象组合后不成立。
一个更具体的区分方式是:
- “1920x1080@60 这个模式能不能跑”更像
mode_valid; - “这次把 3 个 plane 同时开到这个 CRTC 上还要做缩放,整体能不能跑”更像
atomic_check。
一句话概括:
mode_valid看模式本身;atomic_check看状态组合。

浙公网安备 33010602011771号