Skip to content
Closed
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
8 changes: 6 additions & 2 deletions drivers/gpu/drm/asahi/g17/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,9 @@ impl Completion {
return;
}
self.status.record_if_device_loss(error);
if self.accepted.load(Ordering::Acquire) {
// As in complete(): a cancellation is not this client's device loss. A guilty VM
// already carries its own sticky error.
if self.accepted.load(Ordering::Acquire) && error != ECANCELED {
self.status.report_failure(error);
}
self.release_healthy();
Expand All @@ -589,7 +591,9 @@ impl Completion {
guard.quarantine();
}
if self.spared() {
self.status.report_failure(error);
if error != ECANCELED {
self.status.report_failure(error);
}
} else if error != ECANCELED {
self.status.record(error);
}
Expand Down
4 changes: 3 additions & 1 deletion drivers/gpu/drm/asahi/g17/completion/publication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ impl Publication {
if let Err(error) = result {
// Inherited failure is visible to the client without inventing a
// permanent admission failure in this command's VM.
self.status.report_failure(error);
if error != ECANCELED {
self.status.report_failure(error);
}
}
self.output.publish(result);
let result = result.map(|_| ());
Expand Down
16 changes: 16 additions & 0 deletions drivers/gpu/drm/asahi/g17/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,19 @@ impl Frontier {
self.insert(sequence, dependency)
}
}

#[kunit_tests(asahi_g17_dependency)]
mod tests {
use super::*;

#[test]
fn failed_predecessor_cannot_be_bypassed() {
let mut frontier = Frontier::new();
for error in [ECANCELED, EIO, ETIMEDOUT] {
assert_eq!(frontier.include(Some(5), 5, error.to_errno(), 0), Err(ECANCELED));
}
assert_eq!(frontier.include(Some(5), 5, 1, 0), Ok(()));
assert_eq!(frontier.include(Some(5), 6, EIO.to_errno(), 0), Ok(()));
assert_eq!(frontier.count, 0);
}
}
35 changes: 31 additions & 4 deletions drivers/gpu/drm/asahi/g17/preparation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! the device mutex, then drops that mutex before waiting for builders. A builder's lease is
//! stored after its tentative mappings so those mappings are destroyed before the join completes.

use core::cell::Cell;
use core::sync::atomic::{AtomicU64, Ordering};
use kernel::{
new_condvar, new_mutex,
Expand All @@ -24,25 +25,37 @@ const EPOCH: u64 = CLOSED << 3;
/// packet cancellation, logical close, VM status and device loss even when no
/// capacity/recovery event arrives. Wakes and nested constructors never renew it.
pub(super) struct Admission<'a> {
deadline: u64,
deadline: Cell<u64>,
started: u64,
check: &'a dyn Fn() -> Result,
}

impl<'a> Admission<'a> {
pub(super) fn new(deadline: u64, check: &'a dyn Fn() -> Result) -> Self {
Self { deadline, check }
Self { deadline: Cell::new(deadline), started: super::now_ns(), check }
}

/// A recovery is pending or this owner's spared work drains: wait as long as the
/// watchdog may take to settle it (RECOVERY_WAIT_MS from the start) instead of failing.
pub(super) fn extend_for_recovery(&self) {
let limit = self
.started
.saturating_add(u64::from(super::timeout::RECOVERY_WAIT_MS) * 1_000_000);
if self.deadline.get() < limit {
self.deadline.set(limit);
}
}

pub(super) fn check(&self) -> Result {
(self.check)()?;
if super::now_ns() >= self.deadline { return Err(EBUSY); }
if super::now_ns() >= self.deadline.get() { return Err(EBUSY); }
Ok(())
}

/// Ignore condvar remaining-time reports: only the monotonic deadline counts.
pub(super) fn slice_ms(&self) -> Result<u32> {
self.check()?;
let remaining = self.deadline.saturating_sub(super::now_ns());
let remaining = self.deadline.get().saturating_sub(super::now_ns());
if remaining == 0 { return Err(EBUSY); }
Ok(remaining.div_ceil(1_000_000).min(10) as u32)
}
Expand Down Expand Up @@ -268,6 +281,20 @@ mod growth_gate_tests {
mod compute_deadline_tests {
use super::*;

#[test]
fn a_recovery_extends_the_deadline_once_from_the_start() {
let check = || Ok(());
let admission = Admission::new(super::super::now_ns(), &check);
assert_eq!(admission.check(), Err(EBUSY));
admission.extend_for_recovery();
assert_eq!(admission.check(), Ok(()));
let limit = admission.started
+ u64::from(super::super::timeout::RECOVERY_WAIT_MS) * 1_000_000;
assert_eq!(admission.deadline.get(), limit);
admission.extend_for_recovery();
assert_eq!(admission.deadline.get(), limit);
}

#[test]
fn spent_budget_refuses_even_an_open_gate() {
let gate = Gate::new().unwrap();
Expand Down
38 changes: 28 additions & 10 deletions drivers/gpu/drm/asahi/g17/queue/admission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,16 @@ impl Pool {
}
}
}
/// The owner's pair is quarantined behind another VM's recovery (spared) and drains.
pub(crate) fn spared_quarantined(&self, owner: u64) -> bool {
let state = self.state.lock();
state
.owners
.iter()
.position(|id| *id == owner)
.is_some_and(|slot| state.quarantined & state.spared & (1u64 << slot) != 0)
}

pub(crate) fn acquire_render(
self: &Arc<Self>,
owner: u64,
Expand Down Expand Up @@ -434,17 +444,22 @@ impl Pool {
.position(|id| *id == owner)
.is_some_and(|slot| state.quarantined & (1u64 << slot) != 0)
{
if now >= quarantine_deadline {
// Behind an innocent pair's quarantine (another VM wedged the GPU) only this
// publication fails, not the VM: it recovers once the pair drains.
let spared = state
.owners
.iter()
.position(|id| *id == owner)
.is_some_and(|slot| state.spared & (1u64 << slot) != 0);
return Err(if spared { EBUSY } else { EIO });
// Behind an innocent pair's quarantine (another VM wedged the GPU) wait for the
// pair to drain until the caller's deadline, which the caller extends while it
// drains (RECOVERY_WAIT_MS); a guilty pair's publication fails after a second.
let spared = state
.owners
.iter()
.position(|id| *id == owner)
.is_some_and(|slot| state.spared & (1u64 << slot) != 0);
if now >= quarantine_deadline && !spared {
return Err(EIO);
}
let slice = remaining_ms.min(wait_slice_ms(quarantine_deadline, now)?);
let slice = if spared {
remaining_ms
} else {
remaining_ms.min(wait_slice_ms(quarantine_deadline, now)?)
};
if matches!(self.released.wait_interruptible_timeout(
&mut state, kernel::time::msecs_to_jiffies(slice)
), CondVarTimeoutResult::Signal { .. }) {
Expand Down Expand Up @@ -785,10 +800,13 @@ mod fault_capacity_tests {
let queue = pool.reserve_queue().unwrap();
let lease = pool.acquire_render(1, None, u64::MAX, || Ok(())).unwrap();
let bit = 1u64 << lease.slot();
assert!(!pool.spared_quarantined(1));
lease.quarantine(true);
assert_ne!(pool.state.lock().spared & bit, 0);
assert!(pool.spared_quarantined(1));
lease.quarantine(false);
assert_eq!(pool.state.lock().spared & bit, 0);
assert!(!pool.spared_quarantined(1));
// Once guilty, a later spared failure does not make it spared again.
lease.quarantine(true);
assert_eq!(pool.state.lock().spared & bit, 0);
Expand Down
5 changes: 3 additions & 2 deletions drivers/gpu/drm/asahi/g17/queue/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,9 @@ impl<B: Backend> Queue<B> {
})
});
let failure_count = failures.iter().flatten().count();
let mut dependencies =
KVec::with_capacity(inputs.len() + count + timestamp_count + failure_count, GFP_KERNEL)?;
let mut dependencies = KVec::with_capacity(
inputs.len() + count + timestamp_count + failure_count, GFP_KERNEL,
)?;
let mut checked_inputs =
KVec::with_capacity(inputs.len() + timestamp_count + failure_count, GFP_KERNEL)?;
let mut firmware = KVec::with_capacity(count, GFP_KERNEL)?;
Expand Down
35 changes: 14 additions & 21 deletions drivers/gpu/drm/asahi/g17/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ use kernel::{prelude::*, sync::Arc};

const QIDS: usize = 128;
const FIRST_COMPUTE_QID: u8 = 4;
const RECOVERY_SETTLE_POLLS: usize = 200;
const WORK_RING_WAIT_NS: u64 = super::job::TIMEOUT_MS as u64 * 1_000_000;

/// A reservation remains identifiable while its backing is built without the
Expand Down Expand Up @@ -1287,9 +1286,8 @@ impl Backend {
admission.check()?;
let required = super::fw::compute_pool::required_blocks(self.shared.active_cores, scs_pages)?;
use kernel::time::{delay::fsleep, Delta};
const SPARED_WAIT_MS: usize = 200;
let mut deferred = DeferredBatch::publication()?;
for _ in 0..SPARED_WAIT_MS {
loop {
admission.check()?;
let pending = (|| {
let mut state = self.shared.state.lock();
Expand All @@ -1303,18 +1301,20 @@ impl Backend {
let pending = pending?;
if !pending {
match self.build_compute(required, admission) {
Err(error) if error == EAGAIN => continue,
Err(error) if error == EAGAIN => {
fsleep(Delta::from_millis(1));
continue;
}
result => return result,
}
}
// Other owners continue submitting while this owner gives its
// queue-local drain witness time to arrive.
// Other owners continue submitting while this owner gives its queue-local drain
// witness time to arrive. No new command has been accepted; if the admission
// deadline passes, the spared queue and its retained work stay intact until a later
// admission observes its witness.
admission.extend_for_recovery();
fsleep(Delta::from_millis(1));
}
// No new command has been accepted. Keep the spared queue and its
// retained work intact until a later admission observes its witness.
admission.check()?;
Err(EBUSY)
}

/// Called after taking this packet's publication claim. All waits and work
Expand All @@ -1326,7 +1326,6 @@ impl Backend {
) -> Result {
packet.check_dependencies()?;
let super::command::Validated::Compute { scratch, .. } = &packet.command else { return Err(EINVAL); };
let mut recovery_polls = 0;
let mut state_delay = 20i64;
let deadline = now_ns().saturating_add(WORK_RING_WAIT_NS);
let check = || {
Expand Down Expand Up @@ -1376,10 +1375,7 @@ impl Backend {
}
let recovery_state = super::recovery::Memory::recovery_state(&firmware.init)?;
if firmware.recovery.pending() || recovery_state != 0 {
if recovery_polls == RECOVERY_SETTLE_POLLS {
return Err(EBUSY);
}
recovery_polls += 1;
admission.extend_for_recovery();
drop(state);
drop(lease);
self.shared.queue_events();
Expand All @@ -1405,10 +1401,7 @@ impl Backend {
&& (firmware.recovery.pending()
|| super::recovery::Memory::recovery_state(&firmware.init)? != 0)
{
if recovery_polls == RECOVERY_SETTLE_POLLS {
return Err(EBUSY);
}
recovery_polls += 1;
admission.extend_for_recovery();
drop(state);
drop(lease);
self.shared.queue_events();
Expand Down Expand Up @@ -1609,9 +1602,9 @@ impl Registry {
}

impl Backend {
pub(super) fn wait_capacity(&self, started: u64) -> Result {
pub(super) fn wait_capacity(&self, deadline_ns: u64) -> Result {
let check = || self.admission_open();
let admission = Admission::new(started.saturating_add(WORK_RING_WAIT_NS), &check);
let admission = Admission::new(deadline_ns, &check);
self.wait_compute_capacity(&admission)
}

Expand Down
Loading