4. modeset 流程详解:用户态 ioctl 调用链、atomic_check 与 atomic_commit

Modeset(模式设置)是显示子系统中最核心的操作,涉及分辨率、刷新率、平面布局、CRTC 连接等状态的切换。本章深入剖析从用户态 DRM ioctl 发起请求,到内核 atomic_check 校验、再到 atomic_commit 硬件生效的完整调用链,帮助调试人员精准定位故障环节。

4.1 用户态 ioctl 调用链

用户态程序(如 Weston、Xorg、libdrm 测试工具)通过 drmModeAtomicCommit 或 drmModeSetCrtc 等接口触发 modeset。底层均通过 ioctl(fd, DRM_IOCTL_MODE_ATOMIC, &req) 进入内核。

用户态接口 对应 ioctl 典型用途
drmModeAtomicCommit DRM_IOCTL_MODE_ATOMIC 原子提交多个属性变更
drmModeSetCrtc DRM_IOCTL_MODE_SETCRTC 传统非原子模式设置
drmModePageFlip DRM_IOCTL_MODE_PAGE_FLIP 仅翻转 FB,不改变 timing
drmModeSetPlane DRM_IOCTL_MODE_SETPLANE 独立设置 overlay 平面

内核侧入口函数为 drm_atomic_ioctl,其核心处理流程如下:

  1. 解析属性:从用户态拷贝 drm_mode_atomic 结构,提取 object id、property id、value。
  2. 构建 state:调用 drm_atomic_state_alloc 分配状态对象,将属性填入对应 plane_state、crtc_state、connector_state。
  3. 执行 atomic_check:调用驱动注册的 .atomic_check 回调,验证状态可行性。
  4. 执行 atomic_commit:根据 flags 选择同步或异步提交,调用 .atomic_commit 回调。
调试要点:在 drm_atomic_ioctl 入口处添加 tracepoint 或 ftrace,可捕获用户态传入的完整属性集合,快速判断是否因属性值非法导致校验失败。
用户态 ioctl 调用链 用户态应用 libdrm / drmModeAtomicCommit drm_atomic_ioctl drm_atomic_state_alloc atomic_check atomic_commit .atomic_check 回调 .atomic_commit 回调 ioctl(fd, DRM_IOCTL_MODE_ATOMIC)

4.2 atomic_check:状态校验阶段

atomic_check 是 modeset 流程中的“守门员”,负责验证新状态是否合法。该回调在持有 drm_modeset_lock 的上下文中执行,不允许睡眠或等待硬件。

4.2.1 校验内容清单

校验项 典型检查逻辑 失败常见原因
CRTC 时钟范围 dotclock 是否在允许范围内 分辨率超出硬件限制
平面格式 FB 像素格式是否被 plane 支持 NV12 提交到不支持 YUV 的 plane
缩放因子 src 与 dst 尺寸比例是否合法 非整数缩放或超出最大倍数
连接器状态 connector 是否已连接且有效 热插拔后未更新状态
带宽预算 所有 plane 总带宽是否超过链路限制 4K@60 + 多个 overlay 同时启用
色彩空间 color_encoding / color_range 是否匹配 HDMI 与 DP 的色彩标准冲突

4.2.2 调试方法

  • 添加 WARN_ON:在 .atomic_check 中插入 drm_WARN_ON,当校验失败时打印调用栈。
  • 使用 drm_dbg_atomic:输出每个 state 的关键字段,如 crtc->state->mode、plane->state->src。
  • 检查返回值:atomic_check 返回 -EINVAL 时,用户态会收到 errno,但具体原因需从 dmesg 获取。
常见陷阱:某些驱动在 atomic_check 中修改了 state 内容(如自动调整时钟),这会导致用户态感知到的状态与最终生效状态不一致。调试时应对比 check 前后的 state 快照。
atomic_check 校验决策树 atomic_check 入口 CRTC 时钟校验 平面格式校验 连接器状态校验 带宽预算校验 全部通过 → 返回 0 任意失败 → 返回 -EINVAL

4.3 atomic_commit:硬件生效阶段

atomic_commit 负责将校验通过的状态真正应用到硬件。根据 flags 不同,提交方式分为同步(阻塞)和异步(非阻塞)两种。

4.3.1 提交模式对比

模式 Flags 行为 适用场景
同步提交 DRM_MODE_ATOMIC_ALLOW_MODESET 等待 vblank 后切换,阻塞直到完成 初始 modeset、分辨率切换
异步提交 DRM_MODE_PAGE_FLIP_EVENT 立即返回,通过 event 通知完成 游戏渲染、视频播放
测试提交 DRM_MODE_ATOMIC_TEST_ONLY 只执行 check,不提交硬件 用户态预检状态可行性

4.3.2 驱动实现要点

驱动在 .atomic_commit 中通常执行以下步骤:

  1. 更新寄存器:将 state 中的参数写入硬件寄存器(如 timing generator、plane 地址)。
  2. 触发更新:向硬件发送更新触发信号(如写 UPDATE_REG 寄存器)。
  3. 等待 vblank:同步模式下等待垂直消隐期,避免撕裂。
  4. 发送事件:异步模式下通过 drm_crtc_send_vblank_event 通知用户态。
调试技巧:在 .atomic_commit 中增加 ktime_get() 时间戳,对比 commit 开始与 vblank 事件到达的时间差,可量化 modeset 延迟。若延迟超过 2 个 vblank 周期,说明硬件响应异常。
atomic_commit 同步 vs 异步流程 同步提交 (ALLOW_MODESET) 写寄存器 → 触发更新 等待 vblank (阻塞) 返回 0 异步提交 (PAGE_FLIP_EVENT) 写寄存器 → 触发更新 立即返回 0 vblank 后发送 drm_event

4.4 完整 modeset 调用链示例

以下是一个典型 4K@60 分辨率切换的完整调用链,包含关键函数与返回值:

// 用户态调用
drmModeAtomicCommit(fd, &req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);

// 内核调用链(ftrace 输出)
drm_atomic_ioctl()                          // 入口
  drm_atomic_state_alloc()                  // 分配 state
  drm_atomic_check_only()                   // 校验入口
    i915_atomic_check()                     // i915 驱动 check
      intel_crtc_atomic_check()             // CRTC 时钟校验
      intel_plane_atomic_check()            // 平面格式校验
      intel_connector_atomic_check()        // 连接器状态校验
    drm_atomic_check_planes()               // 全局平面校验
  drm_atomic_commit()                       // 提交入口
    drm_atomic_helper_commit()              // 辅助函数
      drm_atomic_helper_swap_state()        // 交换新旧 state
      intel_atomic_commit_tail()            // 驱动提交尾部
        intel_update_crtc()                 // 写 CRTC 寄存器
        intel_update_plane()                // 写平面寄存器
        intel_wait_for_vblank()             // 等待 vblank
        drm_crtc_send_vblank_event()        // 发送事件(异步模式)
调试命令:使用 ftrace 跟踪上述函数调用:
echo 'drm_atomic_*' > /sys/kernel/debug/tracing/set_ftrace_filter
echo function > /sys/kernel/debug/tracing/current_tracer
cat /sys/kernel/debug/tracing/trace_pipe

4.5 故障排查清单

故障现象 可能原因 排查手段
ioctl 返回 -EINVAL atomic_check 校验失败 查看 dmesg 中 drm_dbg_atomic 输出
屏幕无显示但 ioctl 成功 atomic_commit 未正确写寄存器 对比寄存器 dump 与预期值
画面撕裂 异步提交未等待 vblank 检查 .atomic_commit 中 vblank 等待逻辑
分辨率切换卡顿 同步提交阻塞时间过长 测量 commit 到 vblank 的延迟
热插拔后 modeset 失败 connector state 未更新 检查 .detect 回调是否及时更新连接状态