On 2025. december 18., csütörtök 9:58:45 középső államokbeli zónaidő Alex 
Deucher wrote:
> On Thu, Dec 18, 2025 at 12:21 AM Timur Kristóf <[email protected]> 
wrote:
> > On 2025. december 15., hétfő 10:07:11 középső államokbeli zónaidő Alex
> > Deucher> 
> > wrote:
> > > GFX ring resets work differently on pre-GFX10 hardware since
> > > there is no MQD managed by the scheduler.
> > > For ring reset, you need issue the reset via CP_VMID_RESET
> > > via KIQ or MMIO and submit the following to the gfx ring to
> > > complete the reset:
> > > 1. EOP packet with EXEC bit set
> > > 2. WAIT_REG_MEM to wait for the fence
> > > 3. Clear CP_VMID_RESET to 0
> > > 4. EVENT_WRITE ENABLE_LEGACY_PIPELINE
> > > 5. EOP packet with EXEC bit set
> > > 6. WAIT_REG_MEM to wait for the fence
> > > Once those commands have completed the reset should
> > > be complete and the ring can accept new packets.
> > > 
> > > However, because we have a pipeline sync between jobs,
> > > the PFP is waiting on the fence from the bad job to signal so
> > > it can't process any of the packets in the reset sequence
> > > until that pipeline sync clears.  To unblock the PFP, we
> > > use the KIQ to signal the fence after we reset the queue.
> > > 
> > > Signed-off-by: Alex Deucher <[email protected]>
> > > ---
> > > 
> > >  drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 104 +++++++++++++++++++++++++-
> > >  1 file changed, 101 insertions(+), 3 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c
> > > b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index
> > > bb1465a98c7ca..9b7073650315e
> > > 100644
> > > --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c
> > > +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c
> > > @@ -2410,8 +2410,10 @@ static int gfx_v9_0_sw_init(struct
> > > amdgpu_ip_block
> > > *ip_block) amdgpu_get_soft_full_reset_mask(&adev->gfx.gfx_ring[0]);
> > > 
> > >       adev->gfx.compute_supported_reset =
> > >       
> > >               amdgpu_get_soft_full_reset_mask(&adev-
> > >
> > >gfx.compute_ring[0]);
> > >
> > > -     if (!amdgpu_sriov_vf(adev) && !adev->debug_disable_gpu_ring_reset)
> > > +     if (!amdgpu_sriov_vf(adev) && !adev->debug_disable_gpu_ring_reset)
> > 
> > {
> > 
> > >               adev->gfx.compute_supported_reset |=
> > 
> > AMDGPU_RESET_TYPE_PER_QUEUE;
> > 
> > > +             adev->gfx.gfx_supported_reset |=
> > 
> > AMDGPU_RESET_TYPE_PER_QUEUE;
> > 
> > > +     }
> > > 
> > >       r = amdgpu_gfx_kiq_init(adev, GFX9_MEC_HPD_SIZE, 0);
> > >       if (r) {
> > > 
> > > @@ -7163,6 +7165,103 @@ static void gfx_v9_ring_insert_nop(struct
> > > amdgpu_ring *ring, uint32_t num_nop) amdgpu_ring_insert_nop(ring,
> > > num_nop -
> > > 1);
> > > 
> > >  }
> > > 
> > > +static void gfx_v9_0_ring_emit_wreg_me(struct amdgpu_ring *ring,
> > > +                                    uint32_t reg,
> > > +                                    uint32_t val)
> > > +{
> > > +     uint32_t cmd = 0;
> > > +
> > > +     switch (ring->funcs->type) {
> > > +     case AMDGPU_RING_TYPE_KIQ:
> > > +             cmd = (1 << 16); /* no inc addr */
> > 
> > What do you mean by "inc addr" in this context?
> 
> It's part of the packet.  bit 16 controls whether the address is
> incremented or not.  This function is basically the same as
> gfx_v9_0_ring_emit_wreg(), but uses the ME to do the wait rather than
> the PFP.  I could have alternatively added a new parameter to
> gfx_v9_0_ring_emit_wreg() to select between PFP and ME.

Thanks. I was just not familiar with this field of the packet.

> 
> > > +             break;
> > > +     default:
> > > +             cmd = WR_CONFIRM;
> > > +             break;
> > > +     }
> > > +     amdgpu_ring_write(ring, PACKET3(PACKET3_WRITE_DATA, 3));
> > > +     amdgpu_ring_write(ring, cmd);
> > > +     amdgpu_ring_write(ring, reg);
> > > +     amdgpu_ring_write(ring, 0);
> > > +     amdgpu_ring_write(ring, val);
> > > +}
> > > +
> > > +static void gfx_v9_0_ring_emit_event_write(struct amdgpu_ring *ring,
> > > +                                        uint32_t event_type,
> > > +                                        uint32_t
> > 
> > event_index)
> > 
> > > +{
> > > +     amdgpu_ring_write(ring, PACKET3(PACKET3_EVENT_WRITE, 0));
> > > +     amdgpu_ring_write(ring, EVENT_TYPE(event_type) |
> > > +                       EVENT_INDEX(event_index));
> > > +}
> > > +
> > > +static int gfx_v9_0_reset_kgq(struct amdgpu_ring *ring,
> > > +                           unsigned int vmid,
> > > +                           struct amdgpu_fence *timedout_fence)
> > > +{
> > > +     struct amdgpu_device *adev = ring->adev;
> > > +     struct amdgpu_kiq *kiq = &adev->gfx.kiq[0];
> > > +     struct amdgpu_ring *kiq_ring = &kiq->ring;
> > > +     unsigned long flags;
> > > +     u32 tmp;
> > > +     int r;
> > > +
> > > +     amdgpu_ring_reset_helper_begin(ring, timedout_fence);
> > > +
> > > +     spin_lock_irqsave(&kiq->ring_lock, flags);
> > > +
> > > +     if (amdgpu_ring_alloc(kiq_ring, 5 + 5)) {
> > > +             spin_unlock_irqrestore(&kiq->ring_lock, flags);
> > > +             return -ENOMEM;
> > > +     }
> > > +
> > > +     /* send the reset - 5 */
> > > +     tmp = REG_SET_FIELD(0, CP_VMID_RESET, RESET_REQUEST, 1 << vmid);
> > > +     gfx_v9_0_ring_emit_wreg(kiq_ring,
> > > +                             SOC15_REG_OFFSET(GC, 0,
> > 
> > mmCP_VMID_RESET), tmp);
> > 
> > > +     /* emit the fence to clear the pipeline sync - 5 */
> > > +     gfx_v9_0_ring_emit_fence_kiq(kiq_ring, ring->fence_drv.gpu_addr,
> > > +                                  timedout_fence->base.seqno,
> > 
> > 0);
> > 
> > As far as I see, this isn't going to work when sched_hw_submission > 2 and
> > there are more than two jobs (from various different userspace processes)
> > emitted in the ring.
> > 
> > I can think of two possible solutons:
> > - Emit each fence individually, with a short delay in between to give a
> > chance to the GFX ring to catch up with the KIQ.
> > - Change the wait_reg_mem command used for the pipeline sync to allow
> > greater than equal instead of just equal. Then it's enough to signal just
> > the last fence on the KIQ ring.
> 
> That won't work.  The signalling patch is asynchronous so if we signal
> additional fences other than the bad one, those jobs will end up being
> seen as successfully completed.

Yeah, I already realized that after I sent the email, and sent a follow-up 
email with a different suggestion, that is to change the pipeline sync to not 
rely on the fence of the previous submission. eg. the pipeline sync could emit 
a separate fence, or even simpler, an ACQUIRE_MEM (gfx9+) / SURFACE_SYNC 
(gfx6-8). Then this becomes a non-issue.

What do you think about that?

> That said, there is a change coming
> for the firmware to fix this. 

That's very nice to hear that AMD is still willing to fix firmware for old 
chips 
like Vega. However,

- Would be nice to solve the problem for users who are still running old 
firmware, as it doesn't seem too difficult to do so.
- AFAIK gfx7-8 also use the same queue reset mechanism so I think they may be 
susceptible to the same issue (and I don't think anyone's gonna release new FW 
for those).

> I'd suggest we just limit the queue
> depth to 2 until the new firmware is available.

Last I heard of it, the SteamOS kernel set it to 4 due to "CPU bubbles" that 
they observed with the default setting of 2 on the Steam Deck. I think on the 
long term we should seriously consider increasing the default in upstream as 
well.

> 
> > > +     amdgpu_ring_commit(kiq_ring);
> > > +     r = amdgpu_ring_test_ring(kiq_ring);
> > > +     spin_unlock_irqrestore(&kiq->ring_lock, flags);
> > > +     if (r)
> > > +             return r;
> > > +
> > > +     if (amdgpu_ring_alloc(ring, 8 + 7 + 5 + 2 + 8 + 7))
> > > +             return -ENOMEM;
> > > +     /* emit the fence to finish the reset - 8 */
> > > +     ring->trail_seq++;
> > > +     gfx_v9_0_ring_emit_fence(ring, ring->trail_fence_gpu_addr,
> > > +                              ring->trail_seq,
> > 
> > AMDGPU_FENCE_FLAG_EXEC);
> > 
> > > +     /* wait for the fence - 7 */
> > > +     gfx_v9_0_wait_reg_mem(ring, 0, 1, 0,
> > > +                           lower_32_bits(ring-
> > >
> > >trail_fence_gpu_addr),
> > >
> > > +                           upper_32_bits(ring-
> > >
> > >trail_fence_gpu_addr),
> > >
> > > +                           ring->trail_seq, 0xffffffff, 4);
> > > +     /* clear mmCP_VMID_RESET - 5 */
> > > +     gfx_v9_0_ring_emit_wreg_me(ring,
> > > +                                SOC15_REG_OFFSET(GC, 0,
> > 
> > mmCP_VMID_RESET), 0);
> > 
> > > +     /* event write ENABLE_LEGACY_PIPELINE - 2 */
> > > +     gfx_v9_0_ring_emit_event_write(ring, ENABLE_LEGACY_PIPELINE, 0);
> > > +     /* emit a regular fence - 8 */
> > > +     ring->trail_seq++;
> > > +     gfx_v9_0_ring_emit_fence(ring, ring->trail_fence_gpu_addr,
> > > +                              ring->trail_seq,
> > 
> > AMDGPU_FENCE_FLAG_EXEC);
> > 
> > > +     /* wait for the fence - 7 */
> > > +     gfx_v9_0_wait_reg_mem(ring, 1, 1, 0,
> > > +                           lower_32_bits(ring-
> > >
> > >trail_fence_gpu_addr),
> > >
> > > +                           upper_32_bits(ring-
> > >
> > >trail_fence_gpu_addr),
> > >
> > > +                           ring->trail_seq, 0xffffffff, 4);
> > 
> > Why is it necessary to emit (and wait for) a regular fence here?
> > I'm not against it, just curious why it's needed.
> 
> It's part of the recovery sequence to make sure the
> ENABLE_LEGACY_PIPELINE event has completed successfully.

Ah, okay. I didn't realize that we needed fences after EVENT_WRITE.

> 
> > > +     amdgpu_ring_commit(ring);
> > > +     /* wait for the commands to complete */
> > > +     r = amdgpu_ring_test_ring(ring);
> > > +     if (r)
> > > +             return r;
> > > +
> > > +     return amdgpu_ring_reset_helper_end(ring, timedout_fence);
> > > +}
> > > +
> > > 
> > >  static int gfx_v9_0_reset_kcq(struct amdgpu_ring *ring,
> > >  
> > >                             unsigned int vmid,
> > >                             struct amdgpu_fence *timedout_fence)
> > > 
> > > @@ -7441,9 +7540,9 @@ static const struct amdgpu_ring_funcs
> > > gfx_v9_0_ring_funcs_gfx = { .emit_wreg = gfx_v9_0_ring_emit_wreg,
> > > 
> > >       .emit_reg_wait = gfx_v9_0_ring_emit_reg_wait,
> > >       .emit_reg_write_reg_wait = gfx_v9_0_ring_emit_reg_write_reg_wait,
> > > 
> > > -     .soft_recovery = gfx_v9_0_ring_soft_recovery,
> > 
> > Can you please split removing the soft recovery into a separate patch?
> > 
> > Can we talk about removing the soft recovery? For the other chips where it
> > has already been removed, it is percieved by users as a regression.
> 
> Queue reset is superset of soft recovery.  There's no need for soft
> recovery when queue reset is available.
> 

I'm not arguing for or against either approach, just relaying how the change 
is percieved by users who tell their experience.

From a user's perspective:
- queue reset just kills the entire job and the offending process, ie. the user 
sees the game crash
- soft recovery sometimes allows the guilty process to move on intact, ie. the 
user would see a "hitch" in the game but then it would keep working


> 
> > >       .emit_mem_sync = gfx_v9_0_emit_mem_sync,
> > >       .emit_cleaner_shader = gfx_v9_0_ring_emit_cleaner_shader,
> > > 
> > > +     .reset = gfx_v9_0_reset_kgq,
> > > 
> > >       .begin_use = amdgpu_gfx_enforce_isolation_ring_begin_use,
> > >       .end_use = amdgpu_gfx_enforce_isolation_ring_end_use,
> > >  
> > >  };
> > > 
> > > @@ -7542,7 +7641,6 @@ static const struct amdgpu_ring_funcs
> > > gfx_v9_0_ring_funcs_compute = { .emit_wreg = gfx_v9_0_ring_emit_wreg,
> > > 
> > >       .emit_reg_wait = gfx_v9_0_ring_emit_reg_wait,
> > >       .emit_reg_write_reg_wait = gfx_v9_0_ring_emit_reg_write_reg_wait,
> > > 
> > > -     .soft_recovery = gfx_v9_0_ring_soft_recovery,
> > > 
> > >       .emit_mem_sync = gfx_v9_0_emit_mem_sync,
> > >       .emit_wave_limit = gfx_v9_0_emit_wave_limit,
> > >       .reset = gfx_v9_0_reset_kcq,




Reply via email to