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
4 changes: 4 additions & 0 deletions drivers/gpu/drm/asahi/asahi_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ module! {
default: 512,
description: "G13/G14: GPU event-drain utilization minimum (0..1024)",
},
compute_timeout_max_ms: u32 {
default: 30000,
description: "G17: longest compute watchdog allowance a queue may set, ms (floor 2000)",
},
g15_deadline_ms: u32 {
default: 5000,
description: "G15: host deadline of a published prefix without progress, ms (min 100)",
Expand Down
11 changes: 11 additions & 0 deletions drivers/gpu/drm/asahi/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ pub(crate) const DRM_ASAHI_FEATURE_COMPUTE_VISIBLE_COMPLETION: u64 =
/// command may wait for it in firmware.
pub(crate) const DRM_ASAHI_COMPUTE_VISIBLE_COMPLETION: u32 =
uapi::drm_asahi_compute_flags_DRM_ASAHI_COMPUTE_VISIBLE_COMPLETION as u32;
/// Queues take a watchdog allowance for their compute commands
/// (`drm_asahi_queue_create::compute_timeout_ms`).
pub(crate) const DRM_ASAHI_FEATURE_COMPUTE_TIMEOUT: u64 =
uapi::DRM_ASAHI_FEATURE_COMPUTE_TIMEOUT as u64;
/// The tiling half waits only on the render and compute prefixes of the command's stage-prefix
/// fields, its fragment half on the whole ones.
#[allow(dead_code)]
Expand Down Expand Up @@ -1615,6 +1619,10 @@ impl File {
data.flags,
);

if data.pad != 0 {
cls_pr_debug!(Errors, "queue_create: Invalid arguments\n");
return Err(EINVAL);
}
if data.flags != 0 || data.priority > uapi::drm_asahi_priority_DRM_ASAHI_PRIORITY_REALTIME {
cls_pr_debug!(Errors, "queue_create: Invalid arguments\n");
return Err(EINVAL);
Expand Down Expand Up @@ -1686,6 +1694,9 @@ impl File {
)?;

queue.reserve_render_scs(data.render_scs_layout)?;
if data.compute_timeout_ms != 0 {
data.compute_timeout_ms = queue.set_compute_timeout(data.compute_timeout_ms)?;
}

data.queue_id = resv.index().try_into()?;
resv.fill(Arc::pin_init(
Expand Down
67 changes: 59 additions & 8 deletions drivers/gpu/drm/asahi/g17/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use super::{command::Validated, completion::Completion, context::Context};
use crate::driver::AsahiDrmDevice;
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, Ordering};
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, AtomicU8, Ordering};
use kernel::{
bindings, c_str,
cpu::CpuId,
Expand Down Expand Up @@ -128,6 +128,9 @@ pub(crate) struct Packet {
cancelled: AtomicBool,
early: AtomicI32,
backlog: AtomicBool,
/// When the job's watchdog allowance ends (host ns, [`super::timeout::Allowance::end`]):
/// zero under the default allowance, `u64::MAX` for a longer one not yet placed.
allowance_end: AtomicU64,
}

impl Packet {
Expand Down Expand Up @@ -160,11 +163,18 @@ impl Packet {
cancelled: AtomicBool::new(false),
early: AtomicI32::new(0),
backlog: AtomicBool::new(false),
allowance_end: AtomicU64::new(0),
},
GFP_KERNEL,
)?)
}

/// Its own watchdog still renews the job for the allowance its queue asked for at `now`
/// ([`super::timeout::Head::excused`]).
pub(crate) fn within_allowance(&self, now: u64) -> bool {
now < self.allowance_end.load(Ordering::Relaxed)
}

pub(crate) fn engine(&self) -> Engine {
match &self.command {
Validated::Render { .. } => Engine::Render,
Expand Down Expand Up @@ -350,8 +360,12 @@ pub(crate) trait Backend: Send + Sync {
fn publish(&self, packet: Arc<Packet>) -> Result<Fence>;
fn timed_out(&self, packet: Arc<Packet>) -> sched::Status;
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;
/// Transfer a timeout to an outstanding off-lock graph transaction, if any, or decide it
/// against other VMs' older work ([`super::timeout::decide`]). `renewals` and `excused`
/// count the job's earlier [`super::timeout::Decision::Defer`] and
/// [`super::timeout::Decision::Excuse`] results; the job is renewed on either.
fn defer_timeout(&self, packet: &Arc<Packet>, renewals: u8, excused: u32)
-> super::timeout::Decision;
/// A long job of another queue is running beside `packet`.
fn long_job_beside(&self, _packet: &Arc<Packet>) -> bool { false }
/// A published job's deadline may be renewed while other work retires
Expand All @@ -365,13 +379,19 @@ pub(crate) trait Backend: Send + Sync {
struct Job<B: Backend> {
backend: Arc<B>,
packet: Arc<Packet>,
/// The watchdog allowance of the job ([`super::timeout::Allowance`]), in ms.
allowance_ms: u32,
}

struct Timeout<B: Backend> {
backend: Arc<B>,
victim_renewals: u8,
/// Polls excused behind other VMs' live work inside its allowance
/// ([`super::timeout::Decision::Excuse`]).
excused: u32,
execution_renewals: u8,
shared_renewals: u8,
allowance: super::timeout::Allowance,
packet: Arc<Packet>,
}

Expand All @@ -395,8 +415,10 @@ impl<B: Backend> sched::JobImpl for Job<B> {
Timeout {
backend: self.backend.clone(),
victim_renewals: 0,
excused: 0,
execution_renewals: 0,
shared_renewals: 0,
allowance: super::timeout::Allowance::new(self.allowance_ms),
packet: self.packet.clone(),
}
}
Expand All @@ -405,6 +427,19 @@ impl<B: Backend> sched::JobImpl for Job<B> {
if finished || job.packet.completion.take_replay_timeout() {
return true;
}
// Within the allowance its queue asked for. Past it, the deferrals below still apply;
// the shared renewals run alongside an allowance rather than after it.
let placing = job.allowance.placing_after_wait(job.packet.is_published());
let within = job.allowance.renew(job.packet.is_published(), super::now_ns());
// Other VMs' watchdogs read the end to excuse work waiting behind this one.
if let Some(end) = job.allowance.end() {
job.packet.allowance_end.store(end, Ordering::Relaxed);
}
if within {
super::timeout::count_allowance_poll(job.allowance.asked() && !placing,
&mut job.shared_renewals);
return true;
}
if super::timeout::renew_execution(
job.packet.is_published(), job.packet.command.long_job(), &mut job.execution_renewals,
) {
Expand All @@ -418,11 +453,17 @@ impl<B: Backend> sched::JobImpl for Job<B> {
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);
if deferred {
job.victim_renewals = job.victim_renewals.saturating_add(1);
match job.backend.defer_timeout(&job.packet, job.victim_renewals, job.excused) {
super::timeout::Decision::Defer => {
job.victim_renewals = job.victim_renewals.saturating_add(1);
true
}
super::timeout::Decision::Excuse => {
job.excused = job.excused.saturating_add(1);
true
}
super::timeout::Decision::Own | super::timeout::Decision::Spare => false,
}
deferred
}

fn prepare(job: &mut Self) -> Option<Fence> {
Expand Down Expand Up @@ -457,6 +498,7 @@ pub(crate) struct Scheduler<B: Backend> {
entity: sched::Entity<Job<B>>,
_scheduler: sched::Scheduler<Job<B>>,
backend: Arc<B>,
allowance_ms: u32,
}

#[derive(Clone)]
Expand Down Expand Up @@ -486,7 +528,11 @@ impl Fences {
}

impl<B: Backend> Scheduler<B> {
pub(crate) fn new(dev: &AsahiDrmDevice, backend: Arc<B>, engine: Engine) -> Result<Self> {
/// `allowance_ms`: the watchdog allowance of each job ([`super::timeout::allowance_ms`]);
/// the watchdog still polls every [`TIMEOUT_MS`].
pub(crate) fn new(dev: &AsahiDrmDevice, backend: Arc<B>, engine: Engine, allowance_ms: u32)
-> Result<Self>
{
let (credits, name) = match engine {
Engine::Render => (
RENDER_CREDITS.checked_mul(backend.render_pairs()).ok_or(EOVERFLOW)?,
Expand All @@ -513,6 +559,7 @@ impl<B: Backend> Scheduler<B> {
entity,
_scheduler: scheduler,
backend,
allowance_ms,
})
}

Expand All @@ -535,11 +582,15 @@ impl<B: Backend> Scheduler<B> {
} else {
None
};
if self.allowance_ms > super::timeout::DEFAULT_ALLOWANCE_MS {
packet.allowance_end.store(u64::MAX, Ordering::Relaxed);
}
let mut job = self.entity.new_job(
1,
Job {
backend: self.backend.clone(),
packet: packet.clone(),
allowance_ms: self.allowance_ms,
},
)?;
if dependencies.len() > MERGED_DEPENDENCIES {
Expand Down
3 changes: 2 additions & 1 deletion drivers/gpu/drm/asahi/g17/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2458,7 +2458,8 @@ impl gpu::Gpu for Gpu {
u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_PER_COMMAND_INPUT_SYNCS)
| u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_EXACT_PRIOR_BARRIERS)
| u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_FRAGMENT_BARRIERS)
| sync_output::FEATURE_CUMULATIVE_SIGNALS;
| sync_output::FEATURE_CUMULATIVE_SIGNALS
| crate::file::DRM_ASAHI_FEATURE_COMPUTE_TIMEOUT;
let variant = self.shared.variant;
// The M5 command set has no AS-build flag: its validation refuses one.
if !variant.m5_commands {
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/asahi/g17/queue/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,7 @@ impl Queue {
active.observed.is_some()
},
failed: self.quarantined,
excused: active.packet.within_allowance(crate::g17::now_ns()),
})
}

Expand Down
22 changes: 21 additions & 1 deletion drivers/gpu/drm/asahi/g17/queue/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ pub(crate) struct Queue<B: Backend> {
backend: Arc<B>,
render: Option<Scheduler<B>>,
compute: Option<Scheduler<B>>,
/// The watchdog allowance of each compute command, set before the first one
/// ([`crate::queue::Queue::set_compute_timeout`]).
compute_allowance_ms: u32,
fences: FenceContexts,
allocation_failure: Fence,
range: Range<u64>,
Expand Down Expand Up @@ -127,14 +130,20 @@ impl<B: Backend> Queue<B> {
window,
variant,
} = frontend;
let render = Some(Scheduler::new(dev, backend.clone(), Engine::Render)?);
let render = Some(Scheduler::new(
dev,
backend.clone(),
Engine::Render,
super::timeout::DEFAULT_ALLOWANCE_MS,
)?);
Ok(Self {
dev: dev.into(),
context,
usage,
backend,
render,
compute: None,
compute_allowance_ms: super::timeout::DEFAULT_ALLOWANCE_MS,
fences,
allocation_failure,
range,
Expand All @@ -158,6 +167,7 @@ impl<B: Backend> Queue<B> {
&self.dev,
self.backend.clone(),
Engine::Compute,
self.compute_allowance_ms,
)?);
}
Ok(())
Expand Down Expand Up @@ -513,6 +523,16 @@ impl<B: Backend> Queue<B> {
}

impl<B: Backend> crate::queue::Queue for Queue<B> {
fn set_compute_timeout(&mut self, requested_ms: u32) -> Result<u32> {
// The compute scheduler takes the allowance when the first compute command creates it.
if self.compute.is_some() {
return Err(EBUSY);
}
let maximum = crate::module_parameters::compute_timeout_max_ms.value();
self.compute_allowance_ms = super::timeout::allowance_ms(requested_ms, maximum);
Ok(self.compute_allowance_ms)
}

fn reserve_render_scs(&mut self, layouts: [u64; 2]) -> Result {
let pages = [
super::fw::compute::ScratchRequest::new(layouts[0])?.pages(),
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/asahi/g17/queue/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,7 @@ impl Pair {
published: active.published,
finished: false,
failed: self.quarantined,
excused: active.packet.within_allowance(crate::g17::now_ns()),
})
}

Expand Down
30 changes: 17 additions & 13 deletions drivers/gpu/drm/asahi/g17/runtime/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,19 +386,20 @@ impl job::Backend for Backend {
.any(|other| other.owner() != Some(self.owner) && other.long_job_running())
}

fn defer_timeout(&self, packet: &Arc<Packet>, renewals: u8) -> bool {
fn defer_timeout(&self, packet: &Arc<Packet>, renewals: u8, excused: u32)
-> crate::g17::timeout::Decision {
if packet.engine() == Engine::Compute {
return self.defer_compute_timeout(packet, renewals);
return self.defer_compute_timeout(packet, renewals, excused);
}
use crate::g17::timeout::{self, Decision};
// As for compute, a final retirement pass precedes the attribution, and all completion
// callbacks run after releasing the device mutex. Without a collector the attribution
// goes ahead without that pass, as it did before it existed.
let mut deferred = DeferredBatch::worker().ok();
let result = (|| -> Result<bool> {
let result = (|| -> Result<Decision> {
let mut state = self.shared.state.lock();
let Some(firmware) = (*state).as_deref_mut() else {
return Ok(false);
return Ok(Decision::Own);
};
if let Some(deferred) = deferred.as_mut() {
firmware.scan_renders(deferred)?;
Expand All @@ -409,15 +410,17 @@ impl job::Backend for Backend {
"Render watchdog owner={} behind its context's older node {:#x} (own {:#x}); the firmware progresses: renewed\n",
self.owner, older, own);
}
return Ok(true);
return Ok(Decision::Defer);
}
// Borrowed-graph and queued-retirement deferrals share the renewal bound.
if timeout::transaction_grace(renewals)
&& firmware.queues.defer_render_timeout(packet, ETIMEDOUT)
{
return Ok(true);
return Ok(Decision::Defer);
}
let verdict = firmware.queues.render_timeout_decision(packet, renewals);
let excuse = timeout::Excuse { used: excused, limit: timeout::excuse_limit(
crate::module_parameters::compute_timeout_max_ms.value()) };
let verdict = firmware.queues.render_timeout_decision(packet, renewals, excuse);
let decision = verdict.map_or(Decision::Own, |verdict| verdict.decision);
// As compute does: work still blocked behind another VM's older unfinished work
// after the whole grace is innocent, so its failure must stay non-sticky. The pair's
Expand All @@ -426,19 +429,20 @@ impl job::Backend for Backend {
packet.completion.classify(crate::g17::recovery::Class::Spared);
}
if let Some(verdict) = verdict {
if let Some(blocker) = verdict.reported_blocker(renewals) {
if let Some(blocker) = verdict.reported_blocker(renewals, excuse) {
dev_warn!(self.shared.dev.as_ref(),
"Render watchdog owner={} vm={} publication={} renewals={} decision={:?} behind earlier work of another VM: blocker_vm={} blocker_publication={} blocker_failed={}\n",
self.owner, verdict.own.vm, verdict.own.published, renewals, decision,
blocker.vm, blocker.published, u8::from(blocker.failed));
"Render watchdog owner={} vm={} publication={} renewals={} excused={} decision={:?} behind earlier work of another VM: blocker_vm={} blocker_publication={} blocker_failed={} blocker_in_allowance={}\n",
self.owner, verdict.own.vm, verdict.own.published, renewals, excused,
decision, blocker.vm, blocker.published, u8::from(blocker.failed),
u8::from(blocker.excused));
}
}
Ok(decision == Decision::Defer)
Ok(decision)
})();
if let Some(deferred) = deferred.as_mut() {
deferred.finish();
}
matches!(result, Ok(true))
result.unwrap_or(Decision::Own)
}

fn try_early(&self, packet: &Arc<Packet>) -> bool {
Expand Down
Loading
Loading