From 763b4dd1e7c1d1a5e1e330b974d20c481da51d0f Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Mon, 26 Oct 2026 22:02:48 -0300 Subject: [PATCH 1/2] drm/asahi: g17: never report a busy fragment queue on 25G83 The 25G83 firmware reads the tiling descriptor's fragment_busy word (TA+0x7a4) only in its out-of-memory lane, at the first out-of-memory stop of a pass. When it is non-zero the firmware compares the pass-seq byte of the last completed fragment kick on that queue with the lane's copy of TA+0x7b0. On a mismatch it resumes the stopped TA with the resume-after-grow register write but without adding memory, and retires the lane: the tiler then stops silently with no further out-of-memory stop and no completion, and the forward-progress watchdog fires (reason 0). We set fragment_busy whenever the previous fragment kick had not completed at publish, which is common in a pipelined stream. macOS always writes 0 there on this firmware. Add Variant::fragment_busy and clear it for V25G83, so the out-of-memory lane takes the path macOS takes. Renders that fit their tiler heap never reach that lane, so they are unaffected. V26A428 keeps its current behaviour. The firmware behaviour above is from a static disassembly of the 25G83 firmware and the macOS AGXG16X kext. In our testing on a J616s (T6040, 25G83), with the tiler heap at its tile-count size so ordinary heavy passes must grow, this alone took dEQP-VK.memory.pipeline_barrier.graphics.all_device.1048576_vertex_buffer_stride_2 from 7 firmware recoveries over three runs to 4; the next change removes the rest. Signed-off-by: Jonathan Goyvaerts Assisted-by: LLM --- drivers/gpu/drm/asahi/g17/fw/variant.rs | 7 +++++++ drivers/gpu/drm/asahi/g17/queue/render.rs | 8 ++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index d2d6d2404dfc8..60b46f628eb8a 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -57,6 +57,9 @@ pub(crate) struct Variant { pub(crate) few_primitives: bool, /// The firmware runs ray-traversal work (ray queries and intersection programs). pub(crate) ray_traversal: bool, + /// Tiling descriptors report whether the fragment queue still had earlier kicks + /// outstanding (`fragment_busy`). + pub(crate) fragment_busy: bool, /// Render item rings carry lead commands: InitBufferManager before the first tiling pass /// of a buffer manager, and FragmentBarrier before every fragment descriptor. pub(crate) lead_commands: bool, @@ -101,6 +104,7 @@ pub(crate) const V26A428: Variant = Variant { mcache: true, few_primitives: true, ray_traversal: true, + fragment_busy: true, lead_commands: false, programs: super::render::RegisterPrograms::G17P, }; @@ -126,6 +130,9 @@ pub(crate) const V25G83: Variant = Variant { few_primitives: false, // A stalled ray traversal wedges the GPU without a firmware recovery. ray_traversal: false, + // When a tiling pass runs out of memory with this set, the firmware resumes it without + // adding memory and the tiler stops silently; macOS always writes 0. + fragment_busy: false, lead_commands: true, programs: super::render::RegisterPrograms::V25G83, }; diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index 3261f2f0640b3..41a2a4bfbbbd9 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -587,8 +587,12 @@ impl Pair { let stamps = host.stamps(qids)?; fence(Ordering::Acquire); let covered = stamps.map(|stamp| retirement::stamp_covers(stamp, ordinal)); - self.memory - .completion_links(ordinal, kicks, payload, !covered[1])?; + self.memory.completion_links( + ordinal, + kicks, + payload, + self.variant.fragment_busy && !covered[1], + )?; let leads = match self.variant.lead_commands { true => { let init_manager = !self.memory.manager.initialized(); From ad14f2da457be86a30e212852f23d8e18166f6d7 Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Mon, 26 Oct 2026 22:03:56 -0300 Subject: [PATCH 2/2] drm/asahi: g17: drop the 25G83 fragment barrier Commit 0d9efa515 ("drm/asahi: g17: render lead commands for the 25G83 firmware") led every 25G83 fragment descriptor with a FragmentBarrier (tag 4) that waits for the paired tiling stamp. macOS does not send that item: its fragment submission stores item tags 1, 0xc, 0xe, 0xf and 0x10 only, and keeps fragment-after-tiling ordering in the kick entry's dependency 0, which we already set. The barrier deadlocks a pass that runs out of tiler memory. The firmware's item-ring loop stops at an item whose handler returns 0, and the tag-4 handler returns 0 until the tiling stamp is reached, so the fragment kick's AddKicks is only processed after its tiling pass completes. A partial render needs that fragment kick announced while the tiling pass is stopped (the firmware releases it in out-of-memory state 7 by clearing the kick entry's dependency 0), so the tiling pass waits for the partial render and the partial render waits for the tiling pass. Remove FragmentBarrier and write only InitBufferManager as a lead command. V26A428 has no lead commands and is unaffected. The firmware behaviour above is from a static disassembly of the 25G83 firmware and the macOS AGXG16X kext. On a J616s (T6040, 25G83), with the tiler heap left at its tile-count size so ordinary heavy passes must grow, this and the previous change take dEQP-VK.memory.pipeline_barrier.graphics.all_device.1048576_vertex_buffer_stride_2 from 7 firmware recoveries over three runs to none. A 1/2000 vk-default slice is 769 pass / 0 fail / 0 crash, and the desktop and GL clients render as before. Fixes: 0d9efa515 ("drm/asahi: g17: render lead commands for the 25G83 firmware") Signed-off-by: Jonathan Goyvaerts Assisted-by: LLM --- drivers/gpu/drm/asahi/g17/fw/queue.rs | 59 ------------------- drivers/gpu/drm/asahi/g17/fw/variant.rs | 2 +- drivers/gpu/drm/asahi/g17/queue/render.rs | 2 +- .../gpu/drm/asahi/g17/queue/render/memory.rs | 36 +++-------- 4 files changed, 10 insertions(+), 89 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/queue.rs b/drivers/gpu/drm/asahi/g17/fw/queue.rs index 5708a2251a062..66e7d07db48a5 100644 --- a/drivers/gpu/drm/asahi/g17/fw/queue.rs +++ b/drivers/gpu/drm/asahi/g17/fw/queue.rs @@ -96,8 +96,6 @@ pub(crate) enum CommandTag { QueueConfig = 0x0f, /// [`KickPredecessor`]. KickPredecessor = 0x10, - /// [`FragmentBarrier`] (25G83 firmware). - FragmentBarrier = 4, /// [`InitBufferManager`] (25G83 firmware). InitBufferManager = 5, } @@ -569,63 +567,6 @@ impl QueueConfig { } } -/// The 25G83 firmware's barrier before every fragment descriptor (tag 4): the fragment work -/// waits for the paired tiling stamp, in addition to the kick-entry dependency. -#[repr(C, packed)] -#[derive(Copy, Clone, Debug)] -pub(crate) struct FragmentBarrier { - /// [`CommandTag::FragmentBarrier`]. - pub(crate) tag: u32, - /// GPU address of the tiling queue's completion stamp word. - pub(crate) stamp_va: u64, - /// Timestamp of the tiling kick. - pub(crate) tiling_kick: u64, - /// QID of the tiling queue. - pub(crate) tiling_qid: u32, - /// Tiling stamp value waited for. - pub(crate) wait: u32, - pub(crate) unk_1c: [u8; 8], - /// Event of the wait: the tiling QID. - pub(crate) event: u32, - /// Fragment stamp value. - pub(crate) stamp_self: u32, - /// Current object ID of the fragment stage. - pub(crate) fragment_id: u64, - pub(crate) unk_34: u32, - pub(crate) unk_38: [u8; 0x10], -} - -static_assert!(size_of::() == 0x48); -static_assert!(core::mem::offset_of!(FragmentBarrier, wait) == 0x18); -static_assert!(core::mem::offset_of!(FragmentBarrier, event) == 0x24); -static_assert!(core::mem::offset_of!(FragmentBarrier, fragment_id) == 0x2c); - -impl FragmentBarrier { - /// The barrier of a pass whose tiling stage, kicked at `tiling_kick` on `tiling_qid`, - /// signals `stamp_value` at `stamp_va`. - pub(crate) fn new( - stamp_va: u64, - tiling_kick: KickTimestamp, - tiling_qid: u32, - stamp_value: u32, - fragment_id: u64, - ) -> Self { - Self { - tag: CommandTag::FragmentBarrier as u32, - stamp_va, - tiling_kick: tiling_kick.get(), - tiling_qid, - wait: stamp_value, - unk_1c: [0; 8], - event: tiling_qid, - stamp_self: stamp_value, - fragment_id, - unk_34: 1, - unk_38: [0; 0x10], - } - } -} - /// The 25G83 firmware's buffer-manager initialization (tag 5), before the first tiling /// descriptor that uses a parameter buffer. The block count is read from the manager. #[repr(C, packed)] diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 60b46f628eb8a..6b1a6af2d2cfc 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -61,7 +61,7 @@ pub(crate) struct Variant { /// outstanding (`fragment_busy`). pub(crate) fragment_busy: bool, /// Render item rings carry lead commands: InitBufferManager before the first tiling pass - /// of a buffer manager, and FragmentBarrier before every fragment descriptor. + /// of a buffer manager. pub(crate) lead_commands: bool, /// Register programs of the render descriptors. pub(crate) programs: super::render::RegisterPrograms, diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index 41a2a4bfbbbd9..1d0a584f171ae 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -596,7 +596,7 @@ impl Pair { let leads = match self.variant.lead_commands { true => { let init_manager = !self.memory.manager.initialized(); - self.memory.write_leads(ordinal, kicks[0], init_manager)? + self.memory.write_leads(ordinal, init_manager)? } false => [false; 2], }; diff --git a/drivers/gpu/drm/asahi/g17/queue/render/memory.rs b/drivers/gpu/drm/asahi/g17/queue/render/memory.rs index 884537b99b6d8..90a11ca977684 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render/memory.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render/memory.rs @@ -31,8 +31,8 @@ pub(super) const ANNOUNCE: [usize; 2] = [0x17000, 0x1b000]; pub(super) const JOB_LIST: usize = 0xa000; pub(super) const CONFIG: [usize; 2] = [0, 0x4000]; pub(super) const CONFIG_STRIDE: usize = 0x180; -/// The lead command of a stage (InitBM before tiling, the barrier before fragment), after the -/// stage's configuration record in its slot ([`crate::g17::fw::Variant::lead_commands`]). +/// The lead command of a stage (InitBM before tiling), after the stage's configuration record +/// in its slot ([`crate::g17::fw::Variant::lead_commands`]). pub(super) const LEAD: usize = 0xc0; const DEPTH: usize = queues::RENDER_DEPTH as usize; /// Offsets of the priority pair and the policy word in a queue record. @@ -534,7 +534,6 @@ const _: () = { assert!(abi::FRAGMENT_SLOT_SIZE * RECORDS <= FRAGMENT_BYTES); assert!(RECORDS * CONFIG_STRIDE <= 0x4000); assert!(size_of::() <= LEAD); - assert!(LEAD + size_of::() <= CONFIG_STRIDE); assert!(LEAD + size_of::() <= CONFIG_STRIDE); }; @@ -648,14 +647,8 @@ impl Memory { } /// Writes the lead commands of `ordinal` from its linked descriptors: InitBM before the - /// tiling descriptor when `init_manager`, and the fragment barrier. Returns which stages - /// have one. - pub(super) fn write_leads( - &mut self, - ordinal: u64, - tiling_kick: fw::kick::KickTimestamp, - init_manager: bool, - ) -> Result<[bool; 2]> { + /// tiling descriptor when `init_manager`. Returns which stages have one. + pub(super) fn write_leads(&mut self, ordinal: u64, init_manager: bool) -> Result<[bool; 2]> { if !self.owns(ordinal) { return Err(EIO); } @@ -664,23 +657,10 @@ impl Memory { .descriptors .pointer(Graph::offset(0, ordinal), size_of::())? .cast::(); - let fragment = self - .graph - .descriptors - .pointer(Graph::offset(1, ordinal), size_of::())? - .cast::(); - // SAFETY: Both views were checked during preparation and the ordinal still owns them; - // only integer fields are read. - let (ta, fragment_id) = unsafe { (&*ta, (*fragment).descriptor.current_id) }; + // SAFETY: The view was checked during preparation and the ordinal still owns it; only + // integer fields are read. + let ta = unsafe { &*ta }; let slot = ordinal as usize % RECORDS * CONFIG_STRIDE + LEAD; - let barrier = queue::FragmentBarrier::new( - ta.stamp_va, - tiling_kick, - ta.qid, - ta.stamp_value, - fragment_id, - ); - self.graph.config.write(CONFIG[1] + slot, barrier)?; if init_manager { let init = queue::InitBufferManager::new( ta.context_id, @@ -690,7 +670,7 @@ impl Memory { ); self.graph.config.write(CONFIG[0] + slot, init)?; } - Ok([init_manager, true]) + Ok([init_manager, false]) } }