From 3db24da5c830344e1c1449d38ec9ae0de1396a3d Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 26 Oct 2026 09:28:38 -0400 Subject: [PATCH] drm/asahi: keep G17 long jobs alive beside other long jobs A published job's deadline is renewed, up to thirty seconds, while some command retired within the last five seconds: the GPU is then shared, not hung. Two ray traversal dispatches side by side retire nothing until they end, though both progress. Alone each took about 6.3 s; side by side they took 10 to 17 s, past the ten seconds a long job gets without that renewal, so the watchdog failed most of them with ETIMEDOUT and their clients lost the device. Also count the GPU as shared while a long job of another queue runs beside a long job. The thirty-second bound still ends a pair that never finishes. --- drivers/gpu/drm/asahi/g17/job.rs | 12 +++--- drivers/gpu/drm/asahi/g17/queue/compute.rs | 10 +++++ drivers/gpu/drm/asahi/g17/runtime/backend.rs | 10 +++++ drivers/gpu/drm/asahi/g17/timeout.rs | 42 ++++++++++++++------ 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/job.rs b/drivers/gpu/drm/asahi/g17/job.rs index 8175c992b8a62..61033aa9e52b4 100644 --- a/drivers/gpu/drm/asahi/g17/job.rs +++ b/drivers/gpu/drm/asahi/g17/job.rs @@ -293,6 +293,8 @@ pub(crate) trait Backend: Send + Sync { fn cancel(&self, packet: Arc); /// Transfer a timeout to an outstanding off-lock graph transaction, if any. fn defer_timeout(&self, packet: &Arc, renewals: u8) -> bool; + /// A long job of another queue is running beside `packet`. + fn long_job_beside(&self, _packet: &Arc) -> bool { false } /// Return true only after taking publication ownership and recording its result. fn try_early(&self, packet: &Arc) -> bool; fn count_backlog(&self, packet: &Packet); @@ -346,11 +348,11 @@ impl sched::JobImpl for Job { ) { return true; } - // Other work retiring means the GPU is shared, not hung. - if super::timeout::renew_shared( - job.packet.is_published(), super::recovery::since_retired(super::now_ns()), - &mut job.shared_renewals, - ) { + // Other work retiring means the GPU is shared, not hung. So does another long job + // running beside a long job, though neither retires until it ends. + let shared = super::timeout::retiring(super::recovery::since_retired(super::now_ns())) + || (job.packet.command.long_job() && job.backend.long_job_beside(&job.packet)); + if super::timeout::renew_shared(job.packet.is_published(), shared, &mut job.shared_renewals) { return true; } let deferred = job.backend.defer_timeout(&job.packet, job.victim_renewals); diff --git a/drivers/gpu/drm/asahi/g17/queue/compute.rs b/drivers/gpu/drm/asahi/g17/queue/compute.rs index 9c4c5c92d049d..23dc1200f3545 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute.rs @@ -747,6 +747,16 @@ impl Queue { pub(crate) fn in_flight(&self) -> bool { self.active.len != 0 && !self.quarantined } + /// The oldest command is a published long job (ray traversal or AS build) whose + /// completion record the driver has not seen yet. + pub(crate) fn long_job_running(&self) -> bool { + !self.quarantined + && self.active.front().is_some_and(|active| { + active.observed.is_none() + && active.packet.is_published() + && active.packet.command.long_job() + }) + } /// Quarantines without a pending witness no longer describe live pipe order. pub(crate) fn timeout_head(&self) -> Option { if self.quarantined && !self.awaiting_witness { diff --git a/drivers/gpu/drm/asahi/g17/runtime/backend.rs b/drivers/gpu/drm/asahi/g17/runtime/backend.rs index d19f51c08dd29..bf0f989f042a5 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/backend.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/backend.rs @@ -297,6 +297,16 @@ impl job::Backend for Backend { } } + fn long_job_beside(&self, _packet: &Arc) -> bool { + let state = self.shared.state.lock(); + let Some(firmware) = (*state).as_deref() else { + return false; + }; + firmware.queues.compute.iter().flatten() + .filter_map(|entry| entry.queue.as_deref()) + .any(|other| other.owner() != Some(self.owner) && other.long_job_running()) + } + fn defer_timeout(&self, packet: &Arc, renewals: u8) -> bool { if packet.engine() == Engine::Compute { return self.defer_compute_timeout(packet, renewals); diff --git a/drivers/gpu/drm/asahi/g17/timeout.rs b/drivers/gpu/drm/asahi/g17/timeout.rs index 7b4ef4137b795..b83ea5d07f406 100644 --- a/drivers/gpu/drm/asahi/g17/timeout.rs +++ b/drivers/gpu/drm/asahi/g17/timeout.rs @@ -66,13 +66,18 @@ const SHARED_RENEWALS: u8 = 14; /// nothing for this long has stopped. pub(crate) const LIVENESS_NS: u64 = 5_000_000_000; -/// Renews a published job's deadline while some command retired within -/// [`LIVENESS_NS`] (`since_retired`), at most [`SHARED_RENEWALS`] times. -pub(crate) fn renew_shared(published: bool, since_retired: Option, renewals: &mut u8) -> bool { - if !published - || !since_retired.is_some_and(|since| since < LIVENESS_NS) - || *renewals >= SHARED_RENEWALS - { +/// Whether some command retired within [`LIVENESS_NS`] (`since_retired`). +pub(crate) fn retiring(since_retired: Option) -> bool { + since_retired.is_some_and(|since| since < LIVENESS_NS) +} + +/// Renews a published job's deadline while the GPU is `shared`, at most +/// [`SHARED_RENEWALS`] times. It is shared while some command retires +/// ([`retiring`]), or while a long job of another queue runs beside a long job: +/// two ray traversal dispatches of about 6 s each took 10 to 17 s side by side, and +/// neither retires anything until it ends. +pub(crate) fn renew_shared(published: bool, shared: bool, renewals: &mut u8) -> bool { + if !published || !shared || *renewals >= SHARED_RENEWALS { return false; } *renewals += 1; @@ -244,21 +249,34 @@ mod tests { fn a_live_gpu_renews_a_published_job_for_thirty_seconds() { let mut renewals = 0; for poll in 1..=14u8 { - assert!(renew_shared(true, Some(4_900_000_000), &mut renewals)); + assert!(renew_shared(true, retiring(Some(4_900_000_000)), &mut renewals)); assert_eq!(renewals, poll); } - assert!(!renew_shared(true, Some(0), &mut renewals)); + assert!(!renew_shared(true, true, &mut renewals)); assert_eq!(POLL_MS * (u32::from(renewals) + 1), 30_000); } #[test] fn a_stopped_gpu_or_unpublished_job_keeps_the_deadline() { let mut renewals = 0; - assert!(!renew_shared(true, Some(LIVENESS_NS), &mut renewals)); - assert!(!renew_shared(true, None, &mut renewals)); - assert!(!renew_shared(false, Some(0), &mut renewals)); + assert!(!renew_shared(true, retiring(Some(LIVENESS_NS)), &mut renewals)); + assert!(!renew_shared(true, retiring(None), &mut renewals)); + assert!(!renew_shared(false, true, &mut renewals)); assert_eq!(renewals, 0); } #[test] + fn a_long_job_beside_another_shares_the_gpu_without_retirements() { + // Two traversal dispatches side by side: nothing retires, yet both progress. + let mut renewals = 0; + let long_job_beside = true; + let shared = retiring(Some(LIVENESS_NS + 1)) || long_job_beside; + for poll in 1..=14u8 { + assert!(renew_shared(true, shared, &mut renewals)); + assert_eq!(renewals, poll); + } + // The thirty-second bound still ends a pair that never finishes. + assert!(!renew_shared(true, true, &mut renewals)); + } + #[test] fn exhausted_or_invalid_credit_never_wraps() { for value in 4..=u8::MAX { let mut renewals = value;