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
61 changes: 58 additions & 3 deletions drivers/gpu/drm/asahi/g17/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ impl Proof {
.all(|&request| self.requests.covers(request))
}

/// The requested ranges this proof does not already cover.
fn uncovered(&self, requests: &Requests) -> Requests {
let mut missing = Requests::new();
for &request in requests.as_slice() {
if !self.requests.covers(request) {
missing.insert(request);
}
}
missing
}

fn record(&mut self, epoch: Option<u64>, requests: Requests) {
// Alternating in-flight buffers must not evict each other's proofs.
if epoch.is_some() && self.epoch == epoch && !requests.overflow {
Expand Down Expand Up @@ -153,12 +164,22 @@ impl Proof {
let optimistic = payload.validate(window, variant, &Space::Record(&range, &requests));
let requests = requests.into_inner();
if optimistic.is_ok() && !requests.overflow {
if self.covers(vm.mapping_validation_epoch(), &requests) {
let epoch = vm.mapping_validation_epoch();
if self.covers(epoch, &requests) {
return optimistic;
}
let (covered, epoch) = vm.covers_ranges_batch(requests.as_slice());
// Proofs from an unchanged epoch still hold: walk only the ranges they do not
// cover. A command buffer at a fresh address then no longer re-walks every
// render target it names.
let partial = self.epoch.is_some() && self.epoch == epoch;
let walk = if partial { self.uncovered(&requests) } else { requests };
let (mut covered, mut walked) = vm.covers_ranges_batch(walk.as_slice());
if covered && partial && walked != self.epoch {
// A mapping changed since the cached proofs were taken: walk everything.
(covered, walked) = vm.covers_ranges_batch(requests.as_slice());
}
if covered {
self.record(epoch, requests);
self.record(walked, requests);
return optimistic;
}
self.epoch = None;
Expand Down Expand Up @@ -280,6 +301,40 @@ mod tests {
&requests((MAX_CACHED_RANGES as u64 * 2 * PAGE, 8, 1))));
}

#[test]
fn only_uncovered_ranges_are_walked_again() {
let mut proof = Proof::new();
let mut proven = requests((PAGE, 4 * PAGE, 1));
proven.insert((8 * PAGE, PAGE, 3));
proof.record(Some(5), proven);

let mut next = requests((PAGE + 16, 8, 1));
next.insert((8 * PAGE, 8, 2));
next.insert((20 * PAGE, PAGE, 1));
next.insert((4 * PAGE, 2 * PAGE, 1));
let missing = proof.uncovered(&next);
assert_eq!(missing.as_slice(), &[(20 * PAGE, PAGE, 1), (4 * PAGE, 2 * PAGE, 1)]);

// Stronger access than proven is walked again.
assert_eq!(proof.uncovered(&requests((PAGE, 8, 2))).count, 1);
assert_eq!(proof.uncovered(&requests((8 * PAGE, 8, 3))).count, 0);
}

#[test]
fn a_partial_walk_extends_the_proof() {
let mut proof = Proof::new();
proof.record(Some(5), requests((PAGE, PAGE, 1)));
let mut next = requests((PAGE, 8, 1));
next.insert((10 * PAGE, 8, 1));
assert!(!proof.covers(Some(5), &next));
// validate() walks proof.uncovered(&next), then records the whole request set.
assert_eq!(proof.uncovered(&next).as_slice(), &[(10 * PAGE, PAGE, 1)]);
proof.record(Some(5), next);
assert!(proof.covers(Some(5), &next));
assert!(proof.covers(Some(5), &requests((PAGE + 8, 8, 1))));
assert!(!proof.covers(Some(6), &next));
}

#[test]
fn invalid_rounding_and_full_cache_disable_reuse() {
for range in [(0, 0, 1), (u64::MAX - 1, 1, 1), (0, 1, 0), (0, 1, 4)] {
Expand Down
15 changes: 14 additions & 1 deletion drivers/gpu/drm/asahi/mmu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ impl gpuvm::DriverGpuVm for VmInner {
Ordering::Acquire,
|value| value.checked_add(1),
).map_err(|_| EOVERFLOW)? + 1;
let _mutation = self.mapping_mutation();
let _mutation = self.mapping_addition();
let (map_addr, map_length) = (op.addr(), op.length() as usize);
let mut iova = op.addr();
let mut left = op.length() as usize;
Expand Down Expand Up @@ -827,6 +827,19 @@ impl VmInner {
self.tlbi_contexts_naming_root(addr, size);
}

/// A mutation that only maps addresses nothing maps yet: GPUVM runs the unmap and remap
/// steps for any overlap first, and those are full mutations. Every range a cached proof
/// covers stays mapped, so the epoch keeps its value and the proofs stay valid; cached
/// validation still pauses until the leaf writes finish.
fn mapping_addition(&mut self) -> MappingMutation {
let state = self.mapping_epoch.clone();
if let Some(state) = state.as_ref() {
state.writers.fetch_add(1, Ordering::Relaxed);
state.visible.swap(u64::MAX, Ordering::AcqRel);
}
MappingMutation(state)
}

fn mapping_mutation(&mut self) -> MappingMutation {
let state = self.mapping_epoch.clone();
if let Some(state) = state.as_ref() {
Expand Down