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
16 changes: 12 additions & 4 deletions drivers/gpu/drm/asahi/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::{
gem,
memacct,
mmu,
module_parameters,
queue,
util::{
align,
Expand Down Expand Up @@ -408,7 +407,7 @@ impl File {
u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_INDEPENDENT_QUEUES);
}

if module_parameters::fault_control.value() == 0xb {
if gpu.supports_soft_faults() {
params.features |= uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_SOFT_FAULTS as u64;
}

Expand Down Expand Up @@ -1214,11 +1213,20 @@ impl File {
return Err(EINVAL);
}

// TODO: Allow with CAP_SYS_NICE
if data.priority >= uapi::drm_asahi_priority_DRM_ASAHI_PRIORITY_HIGH {
// REALTIME stays reserved. HIGH needs CAP_SYS_NICE or DRM master, as amdgpu and
// panthor require for raised queue priority; EACCES lets userspace fall back to MEDIUM.
if data.priority == uapi::drm_asahi_priority_DRM_ASAHI_PRIORITY_REALTIME {
cls_pr_debug!(Errors, "queue_create: Invalid priority\n");
return Err(EINVAL);
}
// SAFETY: capable() only reads the calling task's credentials (ioctl process context).
if data.priority == uapi::drm_asahi_priority_DRM_ASAHI_PRIORITY_HIGH
&& !unsafe { bindings::capable(bindings::CAP_SYS_NICE as _) }
&& !file.is_current_master()
{
cls_pr_debug!(Errors, "queue_create: HIGH priority needs CAP_SYS_NICE or DRM master\n");
return Err(EACCES);
}

let queues_xa = file.inner().queues();
let resv = queues_xa.lock().reserve_limit(1..=u32::MAX, GFP_KERNEL)?;
Expand Down
14 changes: 12 additions & 2 deletions drivers/gpu/drm/asahi/g17/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,15 @@ impl gpu::Gpu for Gpu {
true
}

/// The G17 runtime never programs fault_control into the firmware, so unmapped reads are not
/// guaranteed to return zero. Advertising soft faults made Mesa point out-of-bounds vec3/vec4
/// vertex fetches at address 0 and skip bounds checks on bounded loads; on T6040 those reads
/// returned non-zero data (2026-10-19: every robustness2 rgba32 vertex_attribute_fetch case
/// failed; all pass with soft faults off).
fn supports_soft_faults(&self) -> bool {
false
}

fn queue_limits(&self) -> Option<uapi::drm_asahi_queue_limits> {
Some(uapi::drm_asahi_queue_limits {
max_queues: mmu::MAX_EXECUTION_CONTEXTS,
Expand Down Expand Up @@ -1227,10 +1236,11 @@ impl gpu::Gpu for Gpu {
}

/// Scheduling profile of a new logical queue. `file.rs` passes
/// `DRM_ASAHI_PRIORITY_REALTIME - priority` and refuses HIGH and REALTIME, so
/// MEDIUM arrives as 2 and LOW as 3.
/// `DRM_ASAHI_PRIORITY_REALTIME - priority` and refuses REALTIME, so HIGH
/// arrives as 1, MEDIUM as 2 and LOW as 3.
fn scheduling_policy(priority: u32) -> Result<fw::queue::Policy> {
match priority {
1 => Ok(hw::t8140::scheduling::HIGH),
2 => Ok(hw::t8140::scheduling::MEDIUM),
3 => Ok(hw::t8140::scheduling::LOW),
_ => Err(EINVAL),
Expand Down
7 changes: 7 additions & 0 deletions drivers/gpu/drm/asahi/g17/queue/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,13 @@ impl<B: Backend> Queue<B> {
// either order and must each consume an input's error status before running.
for sync in inputs {
if let Some(fence) = &sync.fence {
// An input that already signalled without error orders nothing and has no status
// left to consume (like a completed frontier producer above). Keeping it would
// send the render through the scheduler thread instead of inline publication:
// most compositor and client renders carry such already-signalled buffer fences.
if job::fence_status(fence) > 0 {
continue;
}
dependencies.push(fence.clone(), GFP_KERNEL)?;
checked_inputs.push(fence.clone(), GFP_KERNEL)?;
}
Expand Down
8 changes: 6 additions & 2 deletions drivers/gpu/drm/asahi/g17/queue/render/retirement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,12 @@ pub(crate) fn poll_observed(
memory: &mut impl Memory,
snapshot: Snapshot,
) -> Result<Poll> {
let observation = observe(ticket, snapshot)?;
complete_poll(ticket, memory, snapshot, observation)
match observe(ticket, snapshot) {
Ok(observation) => complete_poll(ticket, memory, snapshot, observation),
// The borrowed snapshot can straddle a firmware cursor update like any other sample:
// take the ordinary poll's fresh snapshot and reread instead of failing the client.
Err(_) => poll(ticket, memory),
}
}

fn complete_poll(
Expand Down
24 changes: 18 additions & 6 deletions drivers/gpu/drm/asahi/g17/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,17 @@ pub(super) fn service<H: Host>(
}
host.recovery_mut().pending = None;

let cancelled = classify(host, entries, request.blamed, &mut sources);
let (cancelled, blame) = classify(host, entries, request.blamed, &mut sources);
if let Some(qid) = request.blamed {
host.settle_blamed_render(qid, EIO);
pr_warn!(
"G17: GPU recovery (reason {:?}): firmware blamed queue {}, {}\n",
sources.reason,
qid,
if blame { "failing its oldest render" } else { "attribution cleared it; spared" }
);
if blame {
host.settle_blamed_render(qid, EIO);
}
}
if cancelled != 0 {
host.settle_cancelled_renders(cancelled);
Expand All @@ -242,7 +250,10 @@ pub(super) fn service<H: Host>(
/// Resolves guilty QIDs before changing any completion's classification. Unknown ownership or
/// too many distinct guilty VMs leaves all prior classifications intact. The oldest spared work
/// of a QID makes later blame collateral to its previous recovery, rather than new VM guilt.
fn classify<Q: Queues>(queues: &mut Q, entries: Entries, blamed: Option<u8>, sources: &mut Sources) -> u128 {
/// Also returns whether the firmware's blamed QID is still guilty after attribution: a blamed
/// render that had not started while others had (or with no culprit at all) is innocent, and its
/// work is spared like that of every other innocent VM instead of failing with EIO.
fn classify<Q: Queues>(queues: &mut Q, entries: Entries, blamed: Option<u8>, sources: &mut Sources) -> (u128, bool) {
let mut guilty_qids = entries.guilty | blamed.map_or(0, |qid| 1u128 << qid);
let cancelled = entries.cancelled;
let attribution = sources.attribute(blamed, |qid, stamp| queues.render_pass_started(qid, stamp));
Expand All @@ -252,8 +263,9 @@ fn classify<Q: Queues>(queues: &mut Q, entries: Entries, blamed: Option<u8>, sou
Attribution::NoCulprit => guilty_qids = 0,
}
let no_culprit = attribution == Attribution::NoCulprit;
let blame = blamed.is_some_and(|qid| guilty_qids & (1u128 << qid) != 0);
if guilty_qids == 0 && !no_culprit {
return 0;
return (0, blame);
}
let mut guilty: [Option<Arc<VmStatus>>; MAX_GUILTY_VMS] = Default::default();
let mut count = 0;
Expand Down Expand Up @@ -289,7 +301,7 @@ fn classify<Q: Queues>(queues: &mut Q, entries: Entries, blamed: Option<u8>, sou
}
}
if unresolved || (count == 0 && collateral == 0 && !no_culprit) {
return 0;
return (0, blame);
}
let spared = queues.classify_work(&|status| {
guilty[..count]
Expand All @@ -302,5 +314,5 @@ fn classify<Q: Queues>(queues: &mut Q, entries: Entries, blamed: Option<u8>, sou
})
.unwrap_or(Class::Spared)
});
cancelled & !(guilty_qids & !collateral) & spared
(cancelled & !(guilty_qids & !collateral) & spared, blame)
}
4 changes: 4 additions & 0 deletions drivers/gpu/drm/asahi/gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,10 @@ pub(crate) trait Gpu: Send + Sync {
fn is_crashed(&self) -> bool;
/// Fill in the hardware description reported to userspace.
fn get_params(&self, params: &mut uapi::drm_asahi_params_global) -> Result;
/// Whether unmapped GPU reads return zero (fault_control programmed into the firmware).
fn supports_soft_faults(&self) -> bool {
crate::module_parameters::fault_control.value() == 0xb
}
/// Whether user VMs retain a sticky error and support a userspace mirror.
fn supports_vm_status(&self) -> bool {
false
Expand Down
3 changes: 3 additions & 0 deletions drivers/gpu/drm/asahi/hw/t8140.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ pub(crate) mod qos {
pub(crate) mod scheduling {
use crate::g17::fw::queue::Policy;

/// `DRM_ASAHI_PRIORITY_HIGH`: class 1, as the G13 tables map HIGH; the
/// policy word of the user classes is kept (no macOS trace of class 1 yet).
pub(crate) const HIGH: Policy = Policy::new(1, 2, super::qos::CLASS, super::qos::SHARE);
/// `DRM_ASAHI_PRIORITY_MEDIUM`.
pub(crate) const MEDIUM: Policy = Policy::new(2, 2, super::qos::CLASS, super::qos::SHARE);
/// `DRM_ASAHI_PRIORITY_LOW`.
Expand Down
3 changes: 2 additions & 1 deletion include/uapi/drm/asahi_drm.h
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,8 @@ enum drm_asahi_priority {
/**
* @DRM_ASAHI_PRIORITY_HIGH: High priority queue.
*
* Reserved for future extension.
* Requires CAP_SYS_NICE or DRM master; otherwise queue creation fails
* with -EACCES.
*/
DRM_ASAHI_PRIORITY_HIGH = 2,

Expand Down
1 change: 1 addition & 0 deletions rust/bindings/bindings_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include <linux/acpi.h>
#include <linux/gpu_buddy.h>
#include <drm/drm_auth.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
Expand Down
6 changes: 6 additions & 0 deletions rust/kernel/drm/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ impl<T: DriverFile> File<T> {
self.0.get()
}

/// Whether this file holds the device's DRM master (the compositor or display server).
pub fn is_current_master(&self) -> bool {
// SAFETY: By the type invariants of `Self`, `self.as_raw()` is a valid open `drm_file`.
unsafe { bindings::drm_is_current_master(self.as_raw()) }
}

/// Tag recent feeder work for temporary capacity hints during syncobj waits.
/// User scheduling attributes and CPU affinity remain unchanged.
pub fn set_syncobj_wait_hint(&self, util_min: u32, duration_ms: u32) {
Expand Down