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,其核心处理流程如下:
- 解析属性:从用户态拷贝
drm_mode_atomic结构,提取 object id、property id、value。 - 构建 state:调用
drm_atomic_state_alloc分配状态对象,将属性填入对应plane_state、crtc_state、connector_state。 - 执行 atomic_check:调用驱动注册的
.atomic_check回调,验证状态可行性。 - 执行 atomic_commit:根据 flags 选择同步或异步提交,调用
.atomic_commit回调。
调试要点:在
drm_atomic_ioctl 入口处添加 tracepoint 或 ftrace,可捕获用户态传入的完整属性集合,快速判断是否因属性值非法导致校验失败。
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 快照。
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 中通常执行以下步骤:
- 更新寄存器:将 state 中的参数写入硬件寄存器(如 timing generator、plane 地址)。
- 触发更新:向硬件发送更新触发信号(如写
UPDATE_REG寄存器)。 - 等待 vblank:同步模式下等待垂直消隐期,避免撕裂。
- 发送事件:异步模式下通过
drm_crtc_send_vblank_event通知用户态。
调试技巧:在
.atomic_commit 中增加 ktime_get() 时间戳,对比 commit 开始与 vblank 事件到达的时间差,可量化 modeset 延迟。若延迟超过 2 个 vblank 周期,说明硬件响应异常。
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_filterecho function > /sys/kernel/debug/tracing/current_tracercat /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 回调是否及时更新连接状态 |