GPU 是独立于 CPU 运行的协处理器。一旦命令发射到硬件 ring,CPU 只能等待完成信号。如果 GPU 停止响应,硬件 fence 永远不会信号,调度链路永久阻塞。TDR(Timeout Detection and Recovery)是 scheduler 对此的系统性解决方案:检测超时 → 判定责任 → 恢复调度。
1. 全景
三阶段全景图如下:
┌──────────────────────────────┐
│ delayed_work: work_tdr │ ← drm_sched_init() 初始化
│ timeout: sched->timeout │
└──────────┬───────────────────┘
│ 超时到期
▼
drm_sched_job_timedout()
│
▼
sched->ops->timedout_job(job) ← 驱动回调
│
├── RESET: GPU reset + 恢复调度
├── NO_HANG: job 重新插入(误报)
└── ENODEV: 设备不可用,停止调度
| 检测 | delayed_work 定时器,盯住 pending_list 队首 | drm_sched_start_timeout() |
| 判定 | 驱动回调 + karma 累计 + guilty 标记 | timedout_job(), drm_sched_increase_karma() |
| 恢复 | stop → reset → resubmit → start | drm_sched_stop(), drm_sched_start() |
2. 检测:定时器如何盯住 GPU
2.1 启动与触发
static void drm_sched_start_timeout(struct drm_gpu_scheduler *sched)
{
if (sched->timeout != MAX_SCHEDULE_TIMEOUT &&
!list_empty(&sched->pending_list))
mod_delayed_work(sched->timeout_wq, &sched->work_tdr, sched->timeout);
}
调用时机:job 加入 pending_list 时、队首 job 完成后为下一个重启、reset 恢复后。
2.2 监控对象:永远是队首
TDR 监控 pending_list 队首 job(最老的已发射未完成 job)。队首完成则为下一个重启定时器。
2.3 公平计时
当队首 job 完成时,下一个 job 的计时起点被重置为"前一个完成的时刻":
next->s_fence->scheduled.timestamp = dma_fence_timestamp(&job->s_fence->finished);
drm_sched_start_timeout(sched);
每个 job 都有完整的执行时间窗口,不会因排在慢 job 后面而被误判。
3. 判定:超时后谁是罪犯
3.1 drm_sched_job_timedout()
static void drm_sched_job_timedout(struct work_struct *work)
{
sched = container_of(work, struct drm_gpu_scheduler, work_tdr.work);
spin_lock(&sched->job_list_lock);
job = list_first_entry_or_null(&sched->pending_list, ...);
if (job) {
list_del_init(&job->list); // ① 摘除(防 stop 重复插入)
spin_unlock(&sched->job_list_lock);
status = job->sched->ops->timedout_job(job); // ② 驱动判决
if (sched->free_guilty) { // ③ 释放边缘完成的 bad job
job->sched->ops->free_job(job);
sched->free_guilty = false;
}
if (status == DRM_GPU_SCHED_STAT_NO_HANG)
drm_sched_job_reinsert_on_false_timeout(sched, job); // ④ 误报重插
} else {
spin_unlock(&sched->job_list_lock);
}
if (status != DRM_GPU_SCHED_STAT_ENODEV)
drm_sched_start_timeout_unlocked(sched); // ⑤ 重启定时器
}
3.2 三种判决
| RESET | GPU 挂了,驱动已 reset | 重启定时器,继续调度 |
| ENODEV | 设备不可用 | 不重启定时器,调度器停止 |
| NO_HANG | 假警报 | job 重新插回 pending_list |
3.3 Karma 机制:识别惯犯
每次 timeout → job.karma++
karma > hang_limit → entity.guilty = true → 后续 job 全部 -ECANCELED
void drm_sched_increase_karma(struct drm_sched_job *bad)
{
if (bad->s_priority != DRM_SCHED_PRIORITY_KERNEL) { // KERNEL 优先级豁免
atomic_inc(&bad->karma);
// 通过 fence_context 匹配找到对应 entity,标记 guilty
}
}
Guilty 效果:在 drm_sched_entity_pop_job() 中,guilty entity 的所有后续 job 的 finished fence 被设为 -ECANCELED。job 仍发射到硬件,但用户态通过错误码感知惩罚。
KERNEL 豁免:VM 页表更新、SDMA 迁移等系统必要 job 不计 karma,因为 hang 可能间接损坏它们。
4. 恢复:从废墟中重建调度
4.1 两种恢复模式
| Firmware Scheduler | 每 ring 一个 scheduler/entity | 杀死故障 entity + scheduler,不影响其他进程 |
| Hardware Scheduler | 一个 ring 服务多个 entity | stop all → 隔离 guilty → GPU reset → resubmit → start all |
Hardware 模式的关键:不能简单杀死 scheduler,无辜进程的 entity 还在上面,需精确隔离。
4.2 drm_sched_stop():冻结现场
void drm_sched_stop(struct drm_gpu_scheduler *sched, struct drm_sched_job *bad)
{
drm_sched_wqueue_stop(sched); // ① 停 workqueue
if (bad && bad->sched == sched)
list_add(&bad->list, &sched->pending_list); // ② bad job 插回队首
// ③ 反向遍历 pending_list
list_for_each_entry_safe_reverse(s_job, tmp, &sched->pending_list, list) {
if (s_job->s_fence->parent &&
dma_fence_remove_callback(s_job->s_fence->parent, &s_job->cb)) {
// 未完成:归还 credit,清空 parent
dma_fence_put(s_job->s_fence->parent);
s_job->s_fence->parent = NULL;
atomic_sub(s_job->credits, &sched->credit_count);
} else {
// 已完成:释放(bad job 除外)
if (bad != s_job) sched->ops->free_job(s_job);
else sched->free_guilty = true;
}
}
cancel_delayed_work(&sched->work_tdr); // ④ 取消定时器
}
为什么反向遍历? 先处理最新 job,最后处理 bad job,确保恢复时 bad job 在队首供驱动决策。
4.3 drm_sched_start():重启引擎
void drm_sched_start(struct drm_gpu_scheduler *sched, int errno)
{
list_for_each_entry_safe(s_job, tmp, &sched->pending_list, list) {
atomic_add(s_job->credits, &sched->credit_count);
if (!s_job->s_fence->parent)
drm_sched_job_done(s_job, errno ?: –ECANCELED); // 未 resubmit
else if (dma_fence_add_callback(fence, &s_job->cb, ...))
drm_sched_job_done(s_job, fence->error ?: errno); // 已完成
}
drm_sched_start_timeout_unlocked(sched);
drm_sched_wqueue_start(sched);
}
4.4 完整恢复时序
超时发生 → drm_sched_job_timedout()
├── 摘除 job_A
└── timedout_job(job_A)
├── drm_sched_increase_karma(job_A)
├── drm_sched_stop(sched, job_A)
│ ├── 停 workqueue,job_A 插回队首
│ ├── 遍历:未完成的清 parent,已完成的释放
│ └── 取消 TDR
├── GPU Reset (驱动特定)
├── drm_sched_resubmit_jobs(sched)
│ └── 对每个 pending job: run_job() → 新 hw_fence
└── drm_sched_start(sched, 0)
└── 重注册 callbacks,恢复 workqueue
5. 特殊场景
5.1 多 Ring 全局 Reset 同步
很多 GPU 的 reset 是全局的。解决方案:所有 scheduler 共享一个 ordered workqueue 作为 timeout_wq,保证 timeout handler 串行执行,避免并发 reset 竞争。
timeout_wq = alloc_ordered_workqueue("gpu_sched_timeout", 0);
Ring B 超时时,若已被 Ring A 的 reset 解决,驱动检查后返回 NO_HANG 即可。
5.2 False Timeout
驱动返回 NO_HANG 时,job 重新插回 pending_list 队首,触发 free_queue 检查(如已完成则立即释放),并重启 TDR 定时器重新计时。
5.3 暂停/恢复与主动触发
// 暂停:长时间调试/compute 场景
remaining = drm_sched_suspend_timeout(sched);
// … 操作 …
drm_sched_resume_timeout(sched, remaining);
// 主动触发:硬件中断报告故障时立即触发 TDR
drm_sched_fault(sched); // mod_delayed_work(…, 0)
6. 小结
| 检测 | delayed_work 定时器监控 pending_list 队首 |
| 判定 | timedout_job() 三种返回值;karma 累计标记 guilty |
| 恢复 | stop(冻结 + 清理)→ reset → resubmit → start(恢复) |
| 多 ring | 共享 ordered timeout_wq 串行化 |
| 边缘情况 | false timeout 重插、free_guilty 延迟释放、suspend/resume |


