一、简介
代码分析
drm_vblank_work.c 的分析
概述:这个文件实现了 DRM 子系统的 vblank work 机制,用于在 vblank(垂直空白)中断后调度时间敏感的任务。这些任务以实时优先级(realtime priority)执行,以减少被抢占的风险,适用于 GPU 驱动中需要精确时序的硬件编程(如寄存器更新)。它基于 kthread_work,提供延迟执行、重新调度和取消功能。工作项在 vblank 计数达到指定值时触发,支持自重置(self-rearming)。文件强调非实时内核的局限性,但通过实时优先级最小化延迟。内部使用链表管理挂起工作,并与 vblank 中断集成。关键数据结构:
drm_vblank_work:工作项结构,包含 kthread_work、目标 vblank 计数和链表节点。drm_vblank_crtc:扩展 vblank 结构,包含挂起工作链表和工作队列。
特性:
支持在 vblank 后立即执行或推迟到下一个 vblank。取消/刷新操作确保同步,避免自重置工作无限循环。与 IRQ 上下文集成:工作在 IRQ 后调度到 kthread。调试:使用 drm_dbg_core 记录延迟或已过 vblank。
潜在问题:依赖实时调度器,如果系统负载高,仍可能错过截止时间;取消操作需小心处理自重置工作。
drm_vblank.c 的分析
概述:这个文件是 DRM vblank 处理的核心,管理 vblank 中断、计数器和事件。vblank 是图形渲染的关键,用于同步翻页(page flips)和渲染,避免撕裂(tearing)。它处理中断过滤、计数器包装、重置和引用计数,确保无竞争。驱动需实现 enable/disable_vblank 钩子,并在 IRQ 中调用 drm_crtc_handle_vblank。支持高精度时间戳、事件队列和 IOCTL(如等待 vblank 或获取序列)。对于无硬件 vblank 支持的驱动,使用假事件。文件详细解释 vblank 的历史和硬件背景。关键数据结构:
drm_vblank_crtc:每个 CRTC 的 vblank 状态,包括计数器、时间戳、引用计数和事件队列。drm_pending_vblank_event:挂起事件结构,用于用户空间通知。
特性:
中断处理:过滤冗余 IRQ,更新计数器,发送事件。引用计数:drm_vblank_get/put 确保中断启用。时间戳:高精度计算,支持重试以满足精度要求。IOCTL 支持:等待 vblank、获取/队列序列。模式设置集成:处理模式切换时的 vblank 禁用。
潜在问题:硬件依赖性强,无 vblank IRQ 的驱动需模拟;延迟禁用(offdelay)可能导致额外 IRQ;计数器溢出需小心处理。
两个代码之间的联系
依赖关系:drm_vblank_work.c 扩展了 drm_vblank.c,将其作为基础。drm_vblank_work 使用 drm_vblank_crtc 结构,并调用 drm_vblank_get/put、drm_vblank_passed 等函数。drm_vblank.c 中的 drm_handle_vblank 调用 drm_handle_vblank_works(从 drm_vblank_work.c),将 vblank 事件与工作调度集成。功能互补:drm_vblank.c 处理 vblank 核心(中断、计数器、事件),drm_vblank_work.c 在其上添加工作调度,支持时间敏感任务。vblank 中断触发工作执行,确保在 vblank 窗口内编程硬件。使用场景:在 DRM 驱动中,vblank IRQ 调用 drm_crtc_handle_vblank(从 drm_vblank.c),它内部调用 drm_handle_vblank_works(从 drm_vblank_work.c)来调度工作。驱动可使用 drm_vblank_work_schedule 安排任务,如翻页或寄存器更新。联系点总结:drm_vblank_work.c 是 drm_vblank.c 的扩展模块,提供高级调度;它们共享数据结构和锁(如 event_lock),确保线程安全。无 drm_vblank.c,drm_vblank_work.c 无法工作。
函数调用关系图(基于文本)
以下是基于两个代码内容的函数调用关系图,以树状结构表示。图中重点突出主要函数及其内部调用(包括辅助函数、宏和共享调用)。调用图按代码文件分开,最后在整合中显示联系。使用箭头表示调用方向,括号中注明条件或内部细节。
drm_vblank_work.c 的函数调用关系图
drm_handle_vblank_works
├── atomic64_read (读取 vblank->count)
├── list_for_each_entry_safe (迭代 pending_work 链表)
├── drm_vblank_passed (检查是否达到目标 count)
├── list_del_init (移除节点)
├── drm_vblank_put (释放 vblank 引用)
├── kthread_queue_work (调度工作到 worker)
└── wake_up_all (唤醒 work_wait_queue)
drm_vblank_cancel_pending_works
├── list_for_each_entry_safe (迭代 pending_work 链表)
├── list_del_init (移除节点)
├── drm_vblank_put (释放 vblank 引用)
└── wake_up_all (唤醒 work_wait_queue)
drm_vblank_work_schedule
├── spin_lock_irqsave (加 event_lock)
├── drm_vblank_get (如果未调度,获取 vblank 引用)
├── drm_vblank_count (获取当前 vblank count)
├── drm_vblank_passed (检查是否已过目标 count)
├── kthread_queue_work (如果已过且 !nextonmiss,直接调度)
├── list_add_tail (添加到 pending_work 链表,如果需延迟)
├── list_del_init (如果重新调度,移除旧节点)
└── spin_unlock_irqrestore (解 event_lock)
└── wake_up_all (如果移除节点,唤醒队列)
drm_vblank_work_cancel_sync
├── spin_lock_irq (加 event_lock)
├── list_del_init (如果在链表中,移除节点)
├── drm_vblank_put (释放 vblank 引用)
├── wake_up_all (唤醒 work_wait_queue)
├── kthread_cancel_work_sync (取消并等待工作完成)
└── spin_unlock_irq (解 event_lock)
drm_vblank_work_flush
├── spin_lock_irq (加 event_lock)
├── wait_event_lock_irq (等待节点为空,使用 list_empty 检查)
└── spin_unlock_irq (解 event_lock)
└── kthread_flush_work (刷新工作)
drm_vblank_work_init
├── kthread_init_work (初始化 kthread_work)
├── INIT_LIST_HEAD (初始化节点链表)
└── (设置 vblank 指针)
drm_vblank_worker_init
├── INIT_LIST_HEAD (初始化 pending_work 链表)
├── init_waitqueue_head (初始化 work_wait_queue)
├── kthread_create_worker (创建工作者线程)
└── sched_set_fifo (设置实时优先级)
辅助函数/宏:包括 spin_lock_irqsave、assert_spin_locked 等锁操作;drm_dbg_core 用于调试日志。
drm_vblank.c 的函数调用关系图
drm_handle_vblank
├── drm_update_vblank_count (更新 vblank 计数器)
├── wake_up (唤醒 vblank->queue)
├── drm_handle_vblank_events (处理事件)
├── drm_handle_vblank_works (调用 drm_vblank_work.c 处理工作)
└── vblank_disable_fn (如果需立即禁用,调度定时器)
drm_crtc_handle_vblank
└── drm_handle_vblank (调用旧版处理函数)
drm_handle_vblank_events
├── drm_vblank_count_and_time (获取 count 和时间戳)
├── list_for_each_entry_safe (迭代 vblank_event_list)
├── drm_vblank_passed (检查事件序列)
├── list_del (移除事件)
├── drm_vblank_put (释放引用)
└── send_vblank_event (发送事件到用户空间)
drm_vblank_get
├── drm_vblank_enable (启用 vblank,如果引用计数为0)
└── atomic_inc_return (增加 refcount)
drm_vblank_put
├── atomic_dec_return (减少 refcount)
└── vblank_disable_fn (如果 refcount 为0,调度禁用)
drm_crtc_vblank_on
├── drm_calc_vbltimestamp_from_scanoutpos (计算时间戳)
├── drm_update_vblank_count (更新计数器)
├── drm_vblank_enable (启用 vblank)
└── drm_vblank_get (获取引用)
drm_crtc_vblank_off
├── drm_vblank_disable_and_save (保存并禁用 vblank)
├── drm_update_vblank_count (更新计数器)
└── drm_vblank_put (释放引用)
drm_wait_one_vblank
├── drm_vblank_get (获取引用)
├── drm_vblank_count (获取当前 count)
├── wait_event_timeout (等待下一个 vblank)
└── drm_vblank_put (释放引用)
drm_crtc_get_sequence_ioctl
├── drm_crtc_find (查找 CRTC)
├── drm_crtc_vblank_get (临时获取引用,如果未启用)
├── drm_modeset_lock / drm_modeset_unlock (锁定模式设置)
├── drm_vblank_count_and_time (获取序列和时间戳)
└── drm_crtc_vblank_put (释放临时引用)
drm_crtc_queue_sequence_ioctl
├── drm_crtc_find (查找 CRTC)
├── drm_crtc_vblank_get (获取引用)
├── drm_vblank_count_and_time (获取当前序列和时间戳)
├── drm_event_reserve_init_locked (保留事件)
├── drm_vblank_passed (检查是否已过)
├── send_vblank_event (如果已过,立即发送)
└── list_add_tail (添加到 vblank_event_list,如果需延迟)
辅助函数/宏:包括 drm_update_vblank_count(内部调用 store_vblank、drm_calc_vbltimestamp_from_scanoutpos);drm_vblank_passed 用于比较序列;锁如 spin_lock_irqsave、seqlock。
整体函数调用关系图(整合联系)
drm_handle_vblank (drm_vblank.c)
├── drm_handle_vblank_events (drm_vblank.c)
└── drm_handle_vblank_works (drm_vblank_work.c) // 核心联系:vblank 中断触发工作调度
drm_crtc_handle_vblank (drm_vblank.c)
└── drm_handle_vblank (drm_vblank.c) // 桥接 KMS 到旧版
drm_vblank_get (drm_vblank.c)
└── (被 drm_vblank_work_schedule / drm_vblank_work_cancel_sync 等调用,管理引用)
drm_vblank_put (drm_vblank.c)
└── (被 drm_handle_vblank_works / drm_vblank_work_schedule 等调用,释放引用)
drm_vblank_work_schedule (drm_vblank_work.c)
├── drm_vblank_get (drm_vblank.c)
├── drm_vblank_count (drm_vblank.c)
├── drm_vblank_passed (drm_vblank.c)
└── drm_vblank_put (drm_vblank.c)
drm_vblank_work_cancel_sync (drm_vblank_work.c)
└── drm_vblank_put (drm_vblank.c)
drm_vblank_work_init (drm_vblank_work.c)
└── (使用 drm_crtc_index,从 drm_vblank.c 获取 vblank 指针)
说明:整合图突出 drm_vblank.c 如何调用 drm_vblank_work.c(如在 handle_vblank 中),以及共享函数(如 drm_vblank_get/put、drm_vblank_passed)用于同步。vblank 中断(IRQ)是入口点,触发事件和工作处理。
二、总结
1. drm_vblank.c 和 drm_vblank_work.c 的关系
drm_vblank.c
是 vblank 机制的核心实现文件。
提供了 vblank 的注册、启停、计数、事件通知等主要功能。
这里的接口比如:
/
drm_vblank_get()
:控制 vblank reference。
drm_vblank_put()
:用户态通过 IOCTL 等待 vblank。
drm_wait_vblank_ioctl()
:驱动在中断里调用,用来通知 vblank 发生。
drm_handle_vblank()
本质上负责 vblank 计数 + 中断处理 + 上层事件通知。
drm_vblank_work.c
是在 atomic 模式设置 和 commit 提交流程 中用到的异步 vblank 事件处理。
它实现了 延迟/异步的 vblank 事件处理工作队列 (workqueue),保证用户态提交的 page flip、atomic commit 能在合适的 vblank 时机被通知。
特点:
不直接处理中断,而是借助
来延迟执行。
work_struct
避免在硬件中断或原子提交路径中做太复杂的事。
👉 可以简单理解:
→ 核心 vblank 管理 (计数/事件/中断)
drm_vblank.c
→ 辅助模块,用 workqueue 处理延迟的 vblank 事件
drm_vblank_work.c
2. vblank 在 DRM 中的使用位置
vblank 的存在主要是为了解决 显示同步 问题,涉及多个模块:
DRM Core
是核心层的抽象,所有驱动都会复用。
drm_vblank.c
IOCTL:用户空间的
最终走这里。
DRM_IOCTL_WAIT_VBLANK
Atomic Commit
在 drm_atomic_helper.c 里,提交新的 framebuffer 或 mode 时,可以选择等到 vblank 再切换(避免撕裂)。
这里会用到
/
drm_crtc_send_vblank_event()
。
drm_crtc_arm_vblank_event()
Page Flip
经典的
会挂起用户请求,等 vblank 中断来了再完成。
drm_mode_page_flip_ioctl()
驱动平台层
平台必须实现 vblank 中断回调(通常是在 CRTC 驱动中)。
例如
会在中断里调用
mtk_drm_crtc.c
,上报给核心层。
drm_handle_vblank(dev, crtc_id)
3. 总结
:核心 vblank 管理,实现了计数、事件和 IOCTL 支持。
drm_vblank.c
:辅助模块,用 workqueue 处理 延迟的 vblank 事件,主要在 atomic/page flip 场景下使用。
drm_vblank_work.c
vblank 被用在 CRTC/atomic commit/page flip 路径中,用来保证画面更新和用户空间通知与显示同步。