From: Harry Wentland <[email protected]> Writeback completion is detected in dm_crtc_high_irq(), the CRTC vblank IRQ handler. The arm path (dm_set_writeback) never took a vblank reference, so the interrupt was only enabled incidentally (by a pageflip on the same commit, fbcon, or a previous vblank's off-delay window).
A writeback-only commit right after a fresh drm_crtc_vblank_on() (e.g. a writeback connector detached and re-attached) therefore has no vblank reference: the IRQ never fires, wb_pending is never cleared and the out fence times out. This is reproducible with IGT kms_writeback and was seen via kms_colorop on writeback-capable hardware. The relevant IGT branch is at https://gitlab.freedesktop.org/hwentland/igt-gpu-tools/-/tree/yuv-fm-colorop Take a vblank reference when arming the writeback and release it once completion is signalled. The get is done before arming wb_pending so the completion IRQ cannot drop the reference before it is taken. Factor the shared completion bookkeeping into amdgpu_dm_crtc_complete_writeback() and also call it from the teardown path, so a writeback torn down while still pending signals its out fence and releases the reference instead of leaking both. Fixes: c81e13b929df ("drm/amd/display: Hande writeback request from userspace") Cc: [email protected] Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung <[email protected]> Signed-off-by: Harry Wentland <[email protected]> Signed-off-by: George Zhang <[email protected]> --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 64 ++++++++++++++++++- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 + .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 45 ++++++------- 3 files changed, 82 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index ca62304fba2b..c72c417903fb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4494,10 +4494,55 @@ static void amdgpu_dm_crtc_copy_transient_flags(struct drm_crtc_state *crtc_stat stream_state->mode_changed = drm_atomic_crtc_needs_modeset(crtc_state); } +/** + * amdgpu_dm_crtc_complete_writeback - finish a pending writeback job + * @acrtc: the CRTC whose pending writeback should be completed + * + * Clears the pending state, signals the writeback out fence and releases the + * vblank reference taken in dm_set_writeback() while the writeback was armed. + * The pending flag is tested and cleared under the writeback job lock, so this + * is safe to call concurrently from the completion vblank IRQ + * (dm_crtc_high_irq()) and from the writeback teardown path + * (dm_clear_writeback()); only the caller that observes the pending job + * performs the completion. + * + * Return: true if a pending writeback job was completed by this call. + */ +bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc) +{ + unsigned long flags; + bool pending; + + if (!acrtc->wb_conn) + return false; + + spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); + pending = acrtc->wb_pending; + acrtc->wb_pending = false; + spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + + if (!pending) + return false; + + drm_writeback_signal_completion(acrtc->wb_conn, 0); + drm_crtc_vblank_put(&acrtc->base); + + return true; +} + static void dm_clear_writeback(struct amdgpu_display_manager *dm, + struct amdgpu_crtc *acrtc, struct dm_crtc_state *crtc_state) { dc_stream_remove_writeback(dm->dc, crtc_state->stream, 0); + + /* + * If the writeback is still pending when it is torn down (its + * completion vblank IRQ never fired), signal the out fence so a + * waiting client does not stall and release the vblank reference + * taken in dm_set_writeback(). + */ + amdgpu_dm_crtc_complete_writeback(acrtc); } /** @@ -4650,7 +4695,7 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_state *state, dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); - dm_clear_writeback(dm, dm_old_crtc_state); + dm_clear_writeback(dm, acrtc, dm_old_crtc_state); acrtc->wb_enabled = false; } @@ -4924,9 +4969,24 @@ static void dm_set_writeback(struct amdgpu_display_manager *dm, dc_stream_add_writeback(dm->dc, crtc_state->stream, wb_info); - acrtc->wb_pending = true; acrtc->wb_conn = wb_conn; drm_writeback_queue_job(wb_conn, new_con_state); + + /* + * Writeback completion is detected in the CRTC vblank IRQ + * (dm_crtc_high_irq()). Take a vblank reference so the vblank interrupt + * stays enabled while the writeback is pending; otherwise a + * writeback-only commit right after drm_crtc_vblank_on() (e.g. + * re-enabling a CRTC that was disabled) has no other vblank reference, + * the IRQ never fires and the out fence times out. The matching put + * happens once completion is signalled in dm_crtc_high_irq(), or when + * the writeback is torn down in dm_clear_writeback(). + * + * Arm wb_pending only after the reference is held so the completion IRQ + * cannot run its matching vblank_put before this get. + */ + WARN_ON(drm_crtc_vblank_get(&acrtc->base)); + acrtc->wb_pending = true; } static void amdgpu_dm_update_hdcp(struct drm_atomic_state *state) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 2f4a567412f1..909ee71d6d59 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1120,6 +1120,8 @@ void dm_free_gpu_mem(struct amdgpu_device *adev, bool amdgpu_dm_is_headless(struct amdgpu_device *adev); +bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc); + void retrieve_dmi_info(struct amdgpu_display_manager *dm); void amdgpu_dm_emulated_link_detect(struct dc_link *link); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index a821183c076b..c5467f34c51f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1965,7 +1965,6 @@ static void dm_crtc_high_irq(void *interrupt_params) { struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; - struct drm_writeback_job *job; struct amdgpu_crtc *acrtc; unsigned long flags; int vrr_active; @@ -1974,32 +1973,24 @@ static void dm_crtc_high_irq(void *interrupt_params) if (!acrtc) return; - if (acrtc->wb_conn) { - spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); - - if (acrtc->wb_pending) { - job = list_first_entry_or_null(&acrtc->wb_conn->job_queue, - struct drm_writeback_job, - list_entry); - acrtc->wb_pending = false; - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); - - if (job) { - unsigned int v_total, refresh_hz; - struct dc_stream_state *stream = acrtc->dm_irq_params.stream; - - v_total = stream->adjust.v_total_max ? - stream->adjust.v_total_max : stream->timing.v_total; - refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz * - 100LL, (v_total * stream->timing.h_total)); - mdelay(1000 / refresh_hz); - - drm_writeback_signal_completion(acrtc->wb_conn, 0); - dc_stream_fc_disable_writeback(adev->dm.dc, - acrtc->dm_irq_params.stream, 0); - } - } else - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + if (acrtc->wb_conn && acrtc->wb_pending) { + struct dc_stream_state *stream = acrtc->dm_irq_params.stream; + unsigned int v_total, refresh_hz; + + v_total = stream->adjust.v_total_max ? + stream->adjust.v_total_max : stream->timing.v_total; + refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz * + 100LL, (v_total * stream->timing.h_total)); + mdelay(1000 / refresh_hz); + + /* + * Completion (signalling the out fence and releasing the vblank + * reference taken in dm_set_writeback()) is handled by the shared + * helper, which is also used by the teardown path. + */ + if (amdgpu_dm_crtc_complete_writeback(acrtc)) + dc_stream_fc_disable_writeback(adev->dm.dc, + acrtc->dm_irq_params.stream, 0); } vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); -- 2.53.0
