Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions drivers/gpu/drm/asahi/g17/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ pub(crate) trait Backend: Send + Sync {
fn cancel(&self, packet: Arc<Packet>);
/// Transfer a timeout to an outstanding off-lock graph transaction, if any.
fn defer_timeout(&self, packet: &Arc<Packet>, renewals: u8) -> bool;
/// A long job of another queue is running beside `packet`.
fn long_job_beside(&self, _packet: &Arc<Packet>) -> bool { false }
/// Return true only after taking publication ownership and recording its result.
fn try_early(&self, packet: &Arc<Packet>) -> bool;
fn count_backlog(&self, packet: &Packet);
Expand Down Expand Up @@ -346,11 +348,11 @@ impl<B: Backend> sched::JobImpl for Job<B> {
) {
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);
Expand Down
10 changes: 10 additions & 0 deletions drivers/gpu/drm/asahi/g17/queue/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<crate::g17::timeout::Head> {
if self.quarantined && !self.awaiting_witness {
Expand Down
10 changes: 10 additions & 0 deletions drivers/gpu/drm/asahi/g17/runtime/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,16 @@ impl job::Backend for Backend {
}
}

fn long_job_beside(&self, _packet: &Arc<Packet>) -> 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<Packet>, renewals: u8) -> bool {
if packet.engine() == Engine::Compute {
return self.defer_compute_timeout(packet, renewals);
Expand Down
42 changes: 30 additions & 12 deletions drivers/gpu/drm/asahi/g17/timeout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>, 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<u64>) -> 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;
Expand Down Expand Up @@ -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;
Expand Down