DRM显示子系统:funcs 与 helper funcs 的分工

1. 为什么 DRM 要分成两层函数表

在 DRM 子系统中,对象旁边总挂着好几套函数表:funcshelper_funcshelper_private,有时还会有 atomic_* 回调分散在不同层里。

其中:

  • funcs 更偏对象本身的基础能力和生命周期;
  • helper funcs 更偏 DRM/KMS 这套显示流程里的通用阶段划分;
  • 很多 atomic、modeset、bridge 链路相关的回调,真正落点是在 helper 层,而不是都塞进 funcs

这套分层把两类事情拆开:

  1. 这个对象作为一个 DRM object,本身需要具备哪些基本能力;
  2. 这个对象在一次显示模式设置或 atomic 提交里,要在什么阶段参与。

如果没有 helper 层,驱动作者就得自己重复处理大量共性的显示流程:模式校验、状态遍历、对象 enable/disable 顺序、bridge 链调用、原子提交阶段切分。

有了 helper 层之后,DRM core 和 helper 库就能把“流程骨架”先搭好,驱动只负责把真正和硬件相关的那部分实现进去。

可以把它理解成这样:

funcs
  -> 这个对象最基本该怎么活着:创建、释放、状态复制、事件、vblank、属性入口

helper funcs
  -> 这个对象在显示流水线里该怎么工作:校验、设模式、开关链路、更新图层、统一 flush

所以 helper 不是“可有可无的工具函数集合”,它本质上是在给 DRM 提供一套统一的显示流程约定。

2. 一个对象为什么既有 funcs,又有 helper_private

以 CRTC、plane、encoder、connector 这些对象为例,驱动在初始化时通常会做两件事:

  1. drm_*_init() 之类的接口把对象注册到 DRM core;
  2. 再用 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_configpage_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_enableatomic_disableatomic_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() 这种入口。更常见的模式是:

  1. 每个 plane 先各自把参数准备好;
  2. 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 回调大概站在哪

funcshelper 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 回调放到显示流水线里看,感觉会更清楚:

flowchart TD A[atomic state 构建] --> B[各对象 atomic_check] B --> C[plane prepare_fb / atomic_update] C --> D[crtc atomic_begin] D --> E[bridge pre_enable / enable 或 disable / post_disable] E --> F[crtc atomic_flush] F --> G[vblank/event 同步]

这张图不是在抠每一个 helper 函数的精确源码调用点,而是在建立一个顺序感:

  • plane 更偏输入更新;
  • crtc 更偏统一提交;
  • bridge / panel 更偏输出链路时序;
  • connector 更偏对用户态暴露结果。

8. mode_validatomic_check 的边界

这两个概念特别容易混,因为它们看起来都像“检查”。

mode_valid
更适合做“这个模式本身合不合法”的判断,比如像素时钟太高、分辨率超上限、链路位宽不支持。

atomic_check
更适合做“这次提交出来的整组状态合不合法”的判断,比如 plane 路由冲突、带宽不够、缩放比例不支持、同一次提交里多个对象组合后不成立。

一个更具体的区分方式是:

  • “1920x1080@60 这个模式能不能跑”更像 mode_valid
  • “这次把 3 个 plane 同时开到这个 CRTC 上还要做缩放,整体能不能跑”更像 atomic_check

一句话概括:

  • mode_valid 看模式本身;
  • atomic_check 看状态组合。
posted @ 2026-07-20 18:49  G777  阅读(2)  评论(0)    收藏  举报