* [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption
@ 2026-06-30 18:02 sunpeng.li
2026-06-30 18:02 ` [PATCH v3 1/3] drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock sunpeng.li
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: sunpeng.li @ 2026-06-30 18:02 UTC (permalink / raw)
To: amd-gfx
Cc: Harry.Wentland, mario.limonciello, wiagn233, sysdadmin,
timur.kristof, xaver.hugl, mario.kleiner.de, michel.daenzer,
matthew.schwartz, chris, Leo Li
From: Leo Li <sunpeng.li@amd.com>
v1 here:
https://lore.kernel.org/amd-gfx/20260616201828.389985-1-sunpeng.li@amd.com/
v2 here:
https://lore.kernel.org/amd-gfx/20260622171752.73374-1-sunpeng.li@amd.com/
Summary of changes since v2:
* Fixed a regression in kms_vrr@flip-basic, which exposed a scenario in
optimistic event delivery where wrong vblank timestamps were attached.
See new docstrings in patch 2/3 for details.
* Readability cleanups in patch 2/3, thanks Michel!
v7.0.y based branch here (drops the revert and resolves merge conflicts
due to recent refactor of IRQ handlers from amdgpu_dm.c into
amdgpu_dm_irq.c):
https://gitlab.freedesktop.org/leoli/linux-asdn/-/commits/lileo/flip_done_timeout_corruption_fixes?ref_type=heads
A Note on LLM use:
Claude helped with code plumbing and IGT testing. The effort of
reporting, debugging, spinning up fixes, and testing them, were from
humans. Kudos to individuals who collaborated on the amd-gfx mailing
list, gitlab, 2026 Display Hackfest, and AMD internal chats, to finally
nail down a fix.
Leo Li (3):
drm/amd/display: consolidate DCN vblank/flip handling onto
vupdate_no_lock
drm/amd/display: check GRPH_FLIP status before sending event
Revert "drm/amd/display: Restore 5s vbl offdelay for NV3x+ DGPUs"
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 234 +++++++++++---
.../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 70 ++--
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 299 +++++++++---------
drivers/gpu/drm/amd/display/dc/core/dc.c | 45 +++
drivers/gpu/drm/amd/display/dc/dc.h | 1 +
5 files changed, 417 insertions(+), 232 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/3] drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock
2026-06-30 18:02 [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption sunpeng.li
@ 2026-06-30 18:02 ` sunpeng.li
2026-06-30 18:03 ` [PATCH v3 2/3] drm/amd/display: check GRPH_FLIP status before sending event sunpeng.li
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: sunpeng.li @ 2026-06-30 18:02 UTC (permalink / raw)
To: amd-gfx
Cc: Harry.Wentland, mario.limonciello, wiagn233, sysdadmin,
timur.kristof, xaver.hugl, mario.kleiner.de, michel.daenzer,
matthew.schwartz, chris, Leo Li, stable
From: Leo Li <sunpeng.li@amd.com>
[Why]
On DCN, vblank events were delivered from VSTARTUP/VUPDATE
(dm_crtc_high_irq/dm_vupdate_high_irq) and pageflip completion from
GRPH_PFLIP (dm_pflip_high_irq). These signals can be masked by hardware
by a few things:
* DPG - DCN can Dynamically Power Gate parts of the display pipe when a
self-refresh capable eDP is connected. DPG is engaged when there's
enough static frames (detected through drm_vblank_off). Once gated,
even though the OTG (output timing generator) is still enabled,
VSTARTUP and GRPH_FLIP are masked.
* GSL - Driver can use the Global Sync Lock to block HW from latching
onto double-buffered registers during programming, to prevent HW from
latching onto a partially programmed state. This will mask VSTARTUP,
GRPH_FLIP, and VUPDATE. See dcn20_pipe_control_lock().
* MALL - A DCN accessible cache introduced in DCN32+ DGPUs that can
store fb data to allow for longer DRAM sleep. When scanning out from
MALL, VSTARTUP is masked.
When masked, events are never delivered, which can show up as flip_done
timeouts in the wild.
However, there is an interrupt source on DCN that is never masked:
VUPDATE_NO_LOCK. It's simply an unmasked variant of VUPDATE, which fires
while the OTG is active, at the exact point hardware latches
double-buffered registers. It is therefore the natural single signal for
delivering both vblank and flip-completion events on DCN, and the
correct point to timestamp both VRR and non-VRR vblanks.
DCE's interrupt sources are different, it does not have an unmaskable
VUPDATE_NO_LOCK. The only unmaskable DCE interrupt is VLINE0, but it can
only be programmed as a vline offset from vsync_start, making it
unsuitable for VRR. Thus, we keep DCE untouched and use the existing mix
of interrupt sources.
[How]
For DCN1 and newer only:
* Factor the body of dm_crtc_high_irq() into dm_crtc_high_irq_handler()
and drive it from dm_vupdate_high_irq() (VUPDATE_NO_LOCK). DCE keeps
using dm_crtc_high_irq() (VSTARTUP) and dm_pflip_high_irq()
(GRPH_PFLIP) unchanged.
* Stop registering VSTARTUP (crtc_irq) and GRPH_PFLIP (pageflip_irq) on
DCN, and stop enabling them in amdgpu_dm_crtc_set_vblank() /
manage_dm_interrupts(). Enable VUPDATE whenever vblank is enabled on
DCN (previously only in VRR mode). The secure-display vline0 interrupt
is left untouched.
* VUPDATE_NO_LOCK does not early-fire on an immediate (tearing / async)
flip, since HW latches the new address right away. Deliver the flip
completion event immediately after programming such flips in
amdgpu_dm_commit_planes(), and clear pflip_status so the next vupdate
handler does not double-send.
v2: Do not gate VUPDATE_NO_LOCK on DCN in dm_handle_vrr_transition()
Also toggle VUPDATE_NO_LOCK on DCN in dm_gpureset_toggle_interrupts()
Re-cook vblank event count and timestamp for immediate flips
Cc: stable@vger.kernel.org
Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations")
Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787
Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141
Assisted-by: Copilot:claude-opus-4.8
Co-developed-by: Matthew Schwartz <matthew.schwartz@linux.dev>
Signed-off-by: Matthew Schwartz <matthew.schwartz@linux.dev>
Signed-off-by: Leo Li <sunpeng.li@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 68 ++++-
.../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 70 +++--
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 282 ++++++++----------
3 files changed, 230 insertions(+), 190 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 c72c417903fbd..2a64012295521 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -1541,7 +1541,8 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev,
acrtc = amdgpu_dm_get_crtc_by_otg_inst(
adev, state->stream_status[i].primary_otg_inst);
- if (acrtc && state->stream_status[i].plane_count != 0) {
+ if (acrtc && state->stream_status[i].plane_count != 0 &&
+ amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) {
irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst;
rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY;
if (rc)
@@ -1569,6 +1570,13 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev,
*/
if (!dc_interrupt_set(adev->dm.dc, irq_source, enable))
drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis");
+
+ } else if (acrtc && state->stream_status[i].plane_count != 0) {
+ /* DCN only needs to toggle VUPDATE_NO_LOCK */
+ rc = amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, enable);
+ if (rc)
+ drm_warn(adev_to_drm(adev), "Failed to %sable vupdate interrupt\n",
+ enable ? "en" : "dis");
}
}
@@ -3551,14 +3559,22 @@ static void manage_dm_interrupts(struct amdgpu_device *adev,
drm_crtc_vblank_on_config(&acrtc->base,
&config);
- /* Allow RX6xxx, RX7700, RX7800 GPUs to call amdgpu_irq_get.*/
+ /*
+ * Since pflip_high_irq is no longer registered for DCN, grab an
+ * extra reference to vupdate irq instead to workaround this
+ * issue:
+ * https://gitlab.freedesktop.org/drm/amd/-/work_items/3936
+ *
+ * The callbacks to drm_vblank_on/off should really take care of
+ * this though.
+ */
switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
case IP_VERSION(3, 0, 0):
case IP_VERSION(3, 0, 2):
case IP_VERSION(3, 0, 3):
case IP_VERSION(3, 2, 0):
- if (amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type))
- drm_err(dev, "DM_IRQ: Cannot get pageflip irq!\n");
+ if (amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type))
+ drm_err(dev, "DM_IRQ: Cannot get vupdate irq!\n");
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
if (amdgpu_irq_get(adev, &adev->vline0_irq, irq_type))
drm_err(dev, "DM_IRQ: Cannot get vline0 irq!\n");
@@ -3576,8 +3592,8 @@ static void manage_dm_interrupts(struct amdgpu_device *adev,
if (amdgpu_irq_put(adev, &adev->vline0_irq, irq_type))
drm_err(dev, "DM_IRQ: Cannot put vline0 irq!\n");
#endif
- if (amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type))
- drm_err(dev, "DM_IRQ: Cannot put pageflip irq!\n");
+ if (amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type))
+ drm_err(dev, "DM_IRQ: Cannot put vupdate irq!\n");
}
drm_crtc_vblank_off(&acrtc->base);
@@ -3590,6 +3606,10 @@ static void dm_update_pflip_irq_state(struct amdgpu_device *adev,
int irq_type =
amdgpu_display_crtc_idx_to_irq_type(adev, acrtc->crtc_id);
+ /* GRPH_PFLIP is not used on DCN; nothing to reapply. */
+ if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0)
+ return;
+
/**
* This reads the current state for the IRQ and force reapplies
* the setting to hardware.
@@ -3923,9 +3943,13 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm,
struct dm_crtc_state *old_state,
struct dm_crtc_state *new_state)
{
+ struct amdgpu_device *adev = dm->adev;
bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state);
bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state);
+ /* Only DCE gates vupdate on VRR, keep it enabled for DCN */
+ bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0;
+
if (!old_vrr_active && new_vrr_active) {
/* Transition VRR inactive -> active:
* While VRR is active, we must not disable vblank irq, as a
@@ -3935,7 +3959,8 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm,
* We also need vupdate irq for the actual core vblank handling
* at end of vblank.
*/
- WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0);
+ if (vrr_gates_vupdate)
+ WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0);
WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0);
drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n",
__func__, new_state->base.crtc->base.id);
@@ -3951,7 +3976,8 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm,
/* Transition VRR active -> inactive:
* Allow vblank irq disable again for fixed refresh rate.
*/
- WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0);
+ if (vrr_gates_vupdate)
+ WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0);
drm_crtc_vblank_put(new_state->base.crtc);
drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n",
__func__, new_state->base.crtc->base.id);
@@ -4122,6 +4148,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
bool vrr_active = amdgpu_dm_crtc_vrr_active(acrtc_state);
bool cursor_update = false;
bool pflip_present = false;
+ bool immediate_flip = false;
bool dirty_rects_changed = false;
bool updated_planes_and_streams = false;
struct {
@@ -4284,6 +4311,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
acrtc_state->update_type == UPDATE_TYPE_FAST &&
get_mem_type(old_plane_state->fb) == get_mem_type(fb);
+ immediate_flip |= bundle->flip_addrs[planes_count].flip_immediate;
+
timestamp_ns = ktime_get_ns();
bundle->flip_addrs[planes_count].flip_timestamp_in_us = div_u64(timestamp_ns, 1000);
bundle->surface_updates[planes_count].flip_addr = &bundle->flip_addrs[planes_count];
@@ -4476,6 +4505,29 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
acrtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE)
amdgpu_dm_commit_cursors(state);
+ /*
+ * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK.
+ * However, an immediate (tearing / async) flip is latched by HW right
+ * away and does not wait for the next vupdate, so deliver its
+ * completion event here after programming.
+ *
+ * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so
+ * this is DCN-only.
+ */
+ if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) {
+ spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
+ if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED &&
+ acrtc_attach->event) {
+ drm_crtc_accurate_vblank_count(&acrtc_attach->base);
+ drm_crtc_send_vblank_event(&acrtc_attach->base,
+ acrtc_attach->event);
+ acrtc_attach->event = NULL;
+ drm_crtc_vblank_put(&acrtc_attach->base);
+ acrtc_attach->pflip_status = AMDGPU_FLIP_NONE;
+ }
+ spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags);
+ }
+
cleanup:
kfree(bundle);
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
index c9916ed0ddc14..8a6b732cf80c8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
@@ -281,7 +281,14 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable)
drm_crtc_vblank_restore(crtc);
}
- if (dc_supports_vrr(dm->dc->ctx->dce_version)) {
+ /*
+ * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver
+ * vblank and pageflip completion events, so enable it whenever vblank
+ * is enabled. On DCE, vupdate is only needed in VRR mode.
+ */
+ if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) {
+ rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, enable);
+ } else if (dc_supports_vrr(dm->dc->ctx->dce_version)) {
if (enable) {
/* vblank irq on -> Only need vupdate irq in vrr mode */
if (amdgpu_dm_crtc_vrr_active(acrtc_state))
@@ -292,39 +299,46 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable)
}
}
- if (rc)
- return rc;
-
- /* crtc vblank or vstartup interrupt */
- if (enable) {
- rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type);
- drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc);
- } else {
- rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type);
- drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc);
- }
-
if (rc)
return rc;
/*
- * hubp surface flip interrupt
- *
- * We have no guarantee that the frontend index maps to the same
- * backend index - some even map to more than one.
- *
- * TODO: Use a different interrupt or check DC itself for the mapping.
+ * VLINE0 (crtc_irq) and GRPH_PFLIP (pageflip_irq) are only used on
+ * DCE. On DCN, vblank and pageflip completion are delivered from
+ * VUPDATE_NO_LOCK (enabled above), so don't touch them here.
*/
- if (enable) {
- rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type);
- drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc);
- } else {
- rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type);
- drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc);
- }
+ if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) {
+ /* crtc vblank or vstartup interrupt */
+ if (enable) {
+ rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type);
+ drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc);
+ } else {
+ rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type);
+ drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc);
+ }
- if (rc)
- return rc;
+ if (rc)
+ return rc;
+
+ /*
+ * hubp surface flip interrupt
+ *
+ * We have no guarantee that the frontend index maps to the same
+ * backend index - some even map to more than one.
+ *
+ * TODO: Use a different interrupt or check DC itself for the mapping.
+ */
+ if (enable) {
+ rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type);
+ drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc);
+ } else {
+ rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type);
+ drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc);
+ }
+
+ if (rc)
+ return rc;
+ }
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
/* crtc vline0 interrupt, only available on DCN+ */
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 c5467f34c51f1..5c0ff345150d3 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
@@ -1890,88 +1890,24 @@ static void schedule_dc_vmin_vmax(struct amdgpu_device *adev,
queue_work(system_percpu_wq, &offload_work->work);
}
-static void dm_vupdate_high_irq(void *interrupt_params)
-{
- struct common_irq_params *irq_params = interrupt_params;
- struct amdgpu_device *adev = irq_params->adev;
- struct amdgpu_crtc *acrtc;
- struct drm_device *drm_dev;
- struct drm_vblank_crtc *vblank;
- ktime_t frame_duration_ns, previous_timestamp;
- unsigned long flags;
- int vrr_active;
-
- acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE);
-
- if (acrtc) {
- vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
- drm_dev = acrtc->base.dev;
- vblank = drm_crtc_vblank_crtc(&acrtc->base);
- previous_timestamp = atomic64_read(&irq_params->previous_timestamp);
- frame_duration_ns = vblank->time - previous_timestamp;
-
- if (frame_duration_ns > 0) {
- trace_amdgpu_refresh_rate_track(acrtc->base.index,
- frame_duration_ns,
- ktime_divns(NSEC_PER_SEC, frame_duration_ns));
- atomic64_set(&irq_params->previous_timestamp, vblank->time);
- }
-
- drm_dbg_vbl(drm_dev,
- "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id,
- vrr_active);
-
- /* Core vblank handling is done here after end of front-porch in
- * vrr mode, as vblank timestamping will give valid results
- * while now done after front-porch. This will also deliver
- * page-flip completion events that have been queued to us
- * if a pageflip happened inside front-porch.
- */
- if (vrr_active && acrtc->dm_irq_params.stream) {
- bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled;
- bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled;
- bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state
- == VRR_STATE_ACTIVE_VARIABLE;
-
- amdgpu_dm_crtc_handle_vblank(acrtc);
-
- /* BTR processing for pre-DCE12 ASICs */
- if (adev->family < AMDGPU_FAMILY_AI) {
- spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
- mod_freesync_handle_v_update(
- adev->dm.freesync_module,
- acrtc->dm_irq_params.stream,
- &acrtc->dm_irq_params.vrr_params);
-
- if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) {
- schedule_dc_vmin_vmax(adev,
- acrtc->dm_irq_params.stream,
- &acrtc->dm_irq_params.vrr_params.adjust);
- }
- spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
- }
- }
- }
-}
-
/**
- * dm_crtc_high_irq() - Handles CRTC interrupt
- * @interrupt_params: used for determining the CRTC instance
+ * dm_crtc_high_irq_handler() - Common OTG vblank/flip event handling
+ * @adev: amdgpu device
+ * @acrtc: the CRTC to service
*
- * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK
- * event handler.
+ * Performs writeback completion, vblank event handling, CRC processing, VRR BTR
+ * updates and pageflip completion delivery.
+ *
+ * On DCN this is driven by VUPDATE_NO_LOCK (the register latch point) from
+ * dm_vupdate_high_irq(); on DCE it is driven by VLINE0 at the start of vblank
+ * from dm_crtc_high_irq().
*/
-static void dm_crtc_high_irq(void *interrupt_params)
+static void dm_crtc_high_irq_handler(struct amdgpu_device *adev,
+ struct amdgpu_crtc *acrtc)
{
- struct common_irq_params *irq_params = interrupt_params;
- struct amdgpu_device *adev = irq_params->adev;
- struct amdgpu_crtc *acrtc;
unsigned long flags;
int vrr_active;
-
- acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK);
- if (!acrtc)
- return;
+ bool is_dcn = amdgpu_ip_version(adev, DCE_HWIP, 0) != 0;
if (acrtc->wb_conn && acrtc->wb_pending) {
struct dc_stream_state *stream = acrtc->dm_irq_params.stream;
@@ -2000,12 +1936,17 @@ static void dm_crtc_high_irq(void *interrupt_params)
vrr_active, acrtc->dm_irq_params.active_planes);
/**
- * Core vblank handling at start of front-porch is only possible
- * in non-vrr mode, as only there vblank timestamping will give
- * valid results while done in front-porch. Otherwise defer it
- * to dm_vupdate_high_irq after end of front-porch.
+ * Core vblank handling.
+ *
+ * On DCN this handler runs at VUPDATE_NO_LOCK, the register latch
+ * point, which is the correct place to timestamp both VRR and non-VRR
+ * vblanks.
+ *
+ * On DCE this handler runs at the start of front-porch, where only
+ * non-VRR timestamping is valid; VRR vblank is deferred to
+ * dm_vupdate_high_irq() after end of front-porch.
*/
- if (!vrr_active)
+ if (is_dcn || !vrr_active)
amdgpu_dm_crtc_handle_vblank(acrtc);
/**
@@ -2038,18 +1979,16 @@ static void dm_crtc_high_irq(void *interrupt_params)
}
/*
- * If there aren't any active_planes then DCH HUBP may be clock-gated.
- * In that case, pageflip completion interrupts won't fire and pageflip
- * completion events won't get delivered. Prevent this by sending
- * pending pageflip events from here if a flip is still pending.
+ * Deliver pageflip completion events (DCN only).
*
- * If any planes are enabled, use dm_pflip_high_irq() instead, to
- * avoid race conditions between flip programming and completion,
- * which could cause too early flip completion events.
+ * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch
+ * point. Deliver any pending pageflip completion event from here.
+ *
+ * NOTE: This can deliver an event for a flip that was armed but not yet
+ * programmed into HW; that race is closed in a follow-up change by
+ * checking the programmed flip status.
*/
- if (adev->family >= AMDGPU_FAMILY_RV &&
- acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
- acrtc->dm_irq_params.active_planes == 0) {
+ if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) {
if (acrtc->event) {
drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
acrtc->event = NULL;
@@ -2061,6 +2000,104 @@ static void dm_crtc_high_irq(void *interrupt_params)
spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
}
+static void dm_vupdate_high_irq(void *interrupt_params)
+{
+ struct common_irq_params *irq_params = interrupt_params;
+ struct amdgpu_device *adev = irq_params->adev;
+ struct amdgpu_crtc *acrtc;
+ struct drm_device *drm_dev;
+ struct drm_vblank_crtc *vblank;
+ ktime_t frame_duration_ns, previous_timestamp;
+ unsigned long flags;
+ int vrr_active;
+
+ acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE);
+ if (!acrtc)
+ return;
+
+ vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
+ drm_dev = acrtc->base.dev;
+ vblank = drm_crtc_vblank_crtc(&acrtc->base);
+ previous_timestamp = atomic64_read(&irq_params->previous_timestamp);
+ frame_duration_ns = vblank->time - previous_timestamp;
+
+ if (frame_duration_ns > 0) {
+ trace_amdgpu_refresh_rate_track(acrtc->base.index,
+ frame_duration_ns,
+ ktime_divns(NSEC_PER_SEC, frame_duration_ns));
+ atomic64_set(&irq_params->previous_timestamp, vblank->time);
+ }
+
+ drm_dbg_vbl(drm_dev,
+ "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id,
+ vrr_active);
+
+ /*
+ * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver
+ * vblank and pageflip completion events; VSTARTUP and GRPH_PFLIP are
+ * not used. Run the full handler here.
+ */
+ if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) {
+ dm_crtc_high_irq_handler(adev, acrtc);
+ return;
+ }
+
+ /* DCE only below. */
+
+ /* Core vblank handling is done here after end of front-porch in
+ * vrr mode, as vblank timestamping will give valid results
+ * while now done after front-porch. This will also deliver
+ * page-flip completion events that have been queued to us
+ * if a pageflip happened inside front-porch.
+ */
+ if (vrr_active && acrtc->dm_irq_params.stream) {
+ bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled;
+ bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled;
+ bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state
+ == VRR_STATE_ACTIVE_VARIABLE;
+
+ amdgpu_dm_crtc_handle_vblank(acrtc);
+
+ /* BTR processing for pre-DCE12 ASICs */
+ if (adev->family < AMDGPU_FAMILY_AI) {
+ spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
+ mod_freesync_handle_v_update(
+ adev->dm.freesync_module,
+ acrtc->dm_irq_params.stream,
+ &acrtc->dm_irq_params.vrr_params);
+
+ if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) {
+ schedule_dc_vmin_vmax(adev,
+ acrtc->dm_irq_params.stream,
+ &acrtc->dm_irq_params.vrr_params.adjust);
+ }
+ spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
+ }
+ }
+}
+
+/**
+ * dm_crtc_high_irq() - Handles CRTC interrupt
+ * @interrupt_params: used for determining the CRTC instance
+ *
+ * Handles the CRTC/VSYNC interrupt by notifying DRM's VBLANK event handler.
+ *
+ * Used on DCE (VLINE0, set to vblank start). On DCN the equivalent handling is
+ * driven by VUPDATE_NO_LOCK in dm_vupdate_high_irq().
+ */
+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 amdgpu_crtc *acrtc;
+
+ acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK);
+ if (!acrtc)
+ return;
+
+ dm_crtc_high_irq_handler(adev, acrtc);
+}
+
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
/**
* dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for
@@ -2374,38 +2411,6 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev)
* for acknowledging and handling.
*/
- /* Use VSTARTUP interrupt */
- for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP;
- i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1;
- i++) {
- r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq);
-
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, i, 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 ||
- int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) {
- drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_crtc_high_irq, c_irq_params))
- return -ENOMEM;
- }
-
/* Use otg vertical line interrupt */
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) {
@@ -2477,37 +2482,6 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev)
return -ENOMEM;
}
- /* Use GRPH_PFLIP interrupt */
- for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT;
- i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1;
- i++) {
- r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq);
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, i, 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST ||
- int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) {
- drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_pflip_high_irq, c_irq_params))
- return -ENOMEM;
- }
-
/* HPD */
r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT,
&adev->hpd_irq);
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 2/3] drm/amd/display: check GRPH_FLIP status before sending event
2026-06-30 18:02 [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption sunpeng.li
2026-06-30 18:02 ` [PATCH v3 1/3] drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock sunpeng.li
@ 2026-06-30 18:03 ` sunpeng.li
2026-06-30 18:03 ` [PATCH v3 3/3] Revert "drm/amd/display: Restore 5s vbl offdelay for NV3x+ DGPUs" sunpeng.li
2026-07-01 21:09 ` [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption Mario Limonciello
3 siblings, 0 replies; 5+ messages in thread
From: sunpeng.li @ 2026-06-30 18:03 UTC (permalink / raw)
To: amd-gfx
Cc: Harry.Wentland, mario.limonciello, wiagn233, sysdadmin,
timur.kristof, xaver.hugl, mario.kleiner.de, michel.daenzer,
matthew.schwartz, chris, Leo Li, stable
From: Leo Li <sunpeng.li@amd.com>
[Why]
After unifying DCN interrupt sources under VUPDATE_NO_LOCK, we have two
remaining issues to clean up:
1. On DCN, flip completion is now delivered from VUPDATE_NO_LOCK
(dm_crtc_high_irq_handler) instead of GRPH_PFLIP. But VUPDATE_NO_LOCK
fires every frame, regardless of whether a flip has latched.
2. There is a window during commit where a flip is armed (pflip_status =
SUBMITTED) but not yet programmed into HW. If the VUPDATE_NO_LOCK
fires in that window, its handler would deliver a flip event to
userspace before HW has latched to it. If userspace then renders to
what it believes is now the back buffer (but HW is still latched to
it!), it will cause display corruption. This issue seemed to have
been introduced by:
commit 1159898a88db ("drm/amd/display: Handle commit plane with no FB.")
Enabling replay or psr extended the duration of this window, and
hence made corruption more likely to be observed.
[How]
* Move acrtc->event/pflip_status arming to after
update_planes_and_stream_adapter() has programmed the flip into HW.
This closes the window where pflip_status is SUBMITTED but the flip is
not yet programmed.
* Add dc_get_flip_pending_on_otg(), which reads the HUBP flip-pending
status straight from HW for the pipe(s) bound to an OTG instance. It
is keyed only by otg_inst and does not take or mutate a
dc_plane_state, so it is safe to call from the OTG interrupt handler
without racing a concurrent commit that may be modifying plane state.
* Optimistically query for flip-pending after programming, in the event
that HW latched to the new fb between programming start and arming
event. If it latched, send the vblank event immediately, rather than
wait for the next vblank IRQ.
* In the VUPDATE_NO_LOCK handler, only deliver flip completion once
dc_get_flip_pending_on_otg() reports the flip is no longer pending.
Otherwise leave the flip armed and retry on the next vupdate.
* For DCE, maintain the existing behavior of arming flips before
programming, and relying on GRPH_FLIP to fire at HW latch.
v2:
* Drop flip_programmed completion object, instead move
event/pflip_status arming after programming.
* For DCN, optimistically query for flip pending immediately after
programming, and if it latched, send event right away.
v3:
* Fix event timestamps on optimistic flip latch detection, where it's
possible for it to run *before* the vupdate IRQ updates the timestamp.
* Add more docstrings for DCN vblank handling.
* Clean up if conditions in dm_arm_vblank_event().
* Code style cleanup on braces surrounding multi-line statements.
Cc: stable@vger.kernel.org
Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations")
Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787
Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141
Assisted-by: Copilot:claude-opus-4.8
Signed-off-by: Leo Li <sunpeng.li@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 164 ++++++++++++++----
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 27 ++-
drivers/gpu/drm/amd/display/dc/core/dc.c | 45 +++++
drivers/gpu/drm/amd/display/dc/dc.h | 1 +
4 files changed, 197 insertions(+), 40 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 2a64012295521..754fedbdd7460 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -4126,6 +4126,28 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_display_manager *dm,
}
}
+static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc,
+ struct dm_crtc_state *acrtc_state,
+ bool pflip_update,
+ bool cursor_update)
+{
+ assert_spin_locked(&acrtc->base.dev->event_lock);
+
+ if (!acrtc->base.state->event || acrtc_state->active_planes == 0)
+ return;
+
+ if (pflip_update) {
+ drm_crtc_vblank_get(&acrtc->base);
+ WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE);
+ /* Arm flip completion handling and event delivery after programming. */
+ prepare_flip_isr(acrtc);
+ } else if (cursor_update) {
+ drm_crtc_vblank_get(&acrtc->base);
+ acrtc->event = acrtc->base.state->event;
+ acrtc->base.state->event = NULL;
+ }
+}
+
static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
struct drm_device *dev,
struct amdgpu_display_manager *dm,
@@ -4149,6 +4171,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
bool cursor_update = false;
bool pflip_present = false;
bool immediate_flip = false;
+ bool flip_latched_during_prog = false;
bool dirty_rects_changed = false;
bool updated_planes_and_streams = false;
struct {
@@ -4381,39 +4404,24 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
usleep_range(1000, 1100);
}
- /**
- * Prepare the flip event for the pageflip interrupt to handle.
- *
- * This only works in the case where we've already turned on the
- * appropriate hardware blocks (eg. HUBP) so in the transition case
- * from 0 -> n planes we have to skip a hardware generated event
- * and rely on sending it from software.
- */
- if (acrtc_attach->base.state->event &&
- acrtc_state->active_planes > 0) {
- drm_crtc_vblank_get(pcrtc);
-
- spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
-
- WARN_ON(acrtc_attach->pflip_status != AMDGPU_FLIP_NONE);
- prepare_flip_isr(acrtc_attach);
-
- spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags);
- }
-
if (acrtc_state->stream) {
if (acrtc_state->freesync_vrr_info_changed)
bundle->stream_update.vrr_infopacket =
&acrtc_state->stream->vrr_infopacket;
}
- } else if (cursor_update && acrtc_state->active_planes > 0) {
- spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
- if (acrtc_attach->base.state->event) {
- drm_crtc_vblank_get(pcrtc);
- acrtc_attach->event = acrtc_attach->base.state->event;
- acrtc_attach->base.state->event = NULL;
+ }
+
+ /*
+ * DCE depends on a combination of GRPH_FLIP, VLINE0, and VUPDATE for
+ * event delivery. Only GRPH_FLIP handler can send pflip events, and it
+ * only fires if HW latched to the flip. Maintain legacy behavior by
+ * arming event before programming.
+ */
+ if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) {
+ scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) {
+ dm_arm_vblank_event(acrtc_attach, acrtc_state,
+ pflip_present, cursor_update);
}
- spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags);
}
/* Update the planes if changed or disable if we don't have any. */
@@ -4506,17 +4514,103 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
amdgpu_dm_commit_cursors(state);
/*
- * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK.
- * However, an immediate (tearing / async) flip is latched by HW right
- * away and does not wait for the next vupdate, so deliver its
- * completion event here after programming.
+ * DCN specific vblank handling
+ * ============================
+ *
+ * With the event_lock held, arm the vblank event, and determine whether
+ * deliver it immediately, or in VUPDATE_NO_LOCK IRQ (i.e. HW latch
+ * point) handler. Do this *after* programming so that the IRQ handler
+ * will not deliver the event before HW laches onto the programmed
+ * values:
+ *
+ * Commit thread IRQ handler HW
+ * -----------------------------------------------------------------
+ * arm_vblank_event()
+ * vupdate()
+ * vupdate_handler()
+ * cook_timestamp()
+ * # prev flip already latched,
+ * # so flip_latched == true.
+ * if event_armed && flip_latched:
+ * send_vblank_event()
+ * # sent before latch, **BAD!**
+ * hw_program()
+ * vupdate()
+ * **latch**
+ *
+ * There's a consequence of arming after: it's possible for HW to latch
+ * between start of HW programming and acrtc->event/pflip_status arming.
+ * When this happens, the IRQ handler will send the event on the next
+ * immediate latch point, even though HW has already latched. This is
+ * handled by optimistically checking for HW latch after programming,
+ * and if latched, send the event immediately:
+ *
+ * Commit thread IRQ handler HW
+ * -----------------------------------------------------------------
+ * hw_program()
+ * vupdate()
+ * **latch**
+ * vupdate_handler()
+ * cook_timestamp()
+ * # event_armed == false
+ * # **no event sent!**
+ * arm_vblank_event()
+ * if flip_latched:
+ * **send_vblank_event()**
+ * disarm_vblank_event()
+ *
+ * The IRQ handler is expected to cook the timestamp, but we need to
+ * cook the timestamp before optimistic sending as well. That's because
+ * the following sequence is possible:
*
- * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so
- * this is DCN-only.
+ * Commit thread IRQ handler HW
+ * -----------------------------------------------------------------
+ * hw_program()
+ * arm_vblank_event()
+ * vupdate()
+ * **latch**
+ * if flip_latched:
+ * # Need cook before send!
+ * **cook_timestamp()**
+ * send_vblank_event()
+ * disarm_vblank_event()
+ * vupdate_handler()
+ * cook_timestamp()
+ * # event_armed == false
+ * # no event sent!
+ *
+ * Cooking twice is OK, since DRM scanout accurate timestamps report A)
+ * the previous vactive start if currently in vactive, or B) the next
+ * vactive start if currently in vblank (see &get_vblank_counter). 'A)'
+ * is what we want for the optimistic send, and for 'B)', we'll cook a
+ * timestamp no later than the next IRQ handler run.
+ *
+ * The more correct fix is to wrap programming and arming with the
+ * event_lock and thus serializing it with the IRQ handler. However,
+ * there are various sleep-waits within
+ * update_planes_and_stream_adapter() that makes spin locking illegal.
+ * And on full updates, it can take 1-2 frame-times to return (see
+ * commit_planes_for_stream).
+ *
+ * On DCE, GRPH_PFLIP IRQ is used and takes care of this.
*/
- if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) {
+ if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) {
spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
- if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED &&
+
+ if (updated_planes_and_streams) {
+ flip_latched_during_prog =
+ !dc_get_flip_pending_on_otg(dm->dc, acrtc_attach->otg_inst);
+ }
+
+ dm_arm_vblank_event(acrtc_attach, acrtc_state,
+ pflip_present, cursor_update);
+
+ /*
+ * Deliver the event immediately on immediate flip, or on a
+ * update that has already latched.
+ */
+ if ((immediate_flip || flip_latched_during_prog) &&
+ acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED &&
acrtc_attach->event) {
drm_crtc_accurate_vblank_count(&acrtc_attach->base);
drm_crtc_send_vblank_event(&acrtc_attach->base,
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 5c0ff345150d3..06708051d051c 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
@@ -1982,13 +1982,30 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev,
* Deliver pageflip completion events (DCN only).
*
* Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch
- * point. Deliver any pending pageflip completion event from here.
+ * point. Deliver any pending pageflip completion event from here,
+ * once HW has consumed the new address (the OTG no longer reports a
+ * pending flip).
*
- * NOTE: This can deliver an event for a flip that was armed but not yet
- * programmed into HW; that race is closed in a follow-up change by
- * checking the programmed flip status.
+ * Also handle the case here where there aren't any active planes and
+ * DCN HUBP may be clock-gated, so the flip-pending status may be
+ * undefined.
*/
- if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) {
+ if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
+ acrtc->event) {
+
+ if (!dc_get_flip_pending_on_otg(adev->dm.dc, acrtc->otg_inst)) {
+ drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
+ acrtc->event = NULL;
+ drm_crtc_vblank_put(&acrtc->base);
+ acrtc->pflip_status = AMDGPU_FLIP_NONE;
+ }
+ /*
+ * If the flip is still pending, leave it armed and
+ * retry on the next vupdate.
+ */
+ } else if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
+ acrtc->dm_irq_params.active_planes == 0) {
+
if (acrtc->event) {
drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
acrtc->event = NULL;
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c
index 7a635f906f037..a77e56fc2fb2d 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
@@ -6220,6 +6220,51 @@ void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src)
dal_irq_service_ack(dc->res_pool->irqs, src);
}
+/*
+ * dc_get_flip_pending_on_otg() - Check if a GRPH_FLIP is still pending on OTG
+ *
+ * @dc: display core context @otg_inst: OTG instance to query
+ *
+ * Reads the HUBP flip-pending status for the pipe(s) bound to @otg_inst,
+ * returning true if any of them has not yet latched its programmed surface
+ * address.
+ *
+ * Unlike dc_plane_get_status(), this does not take or mutate a dc_plane_state,
+ * so it is safe to call from interrupt context without racing a concurrent
+ * commit that may be updating plane state.
+ *
+ * Return: true if a flip is still pending on the OTG, false otherwise.
+ */
+bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst)
+{
+ bool flip_pending = false;
+ int i;
+
+ if (!dc || !dc->current_state)
+ return false;
+
+ dc_exit_ips_for_hw_access(dc);
+
+ for (i = 0; i < dc->res_pool->pipe_count; i++) {
+ struct pipe_ctx *pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i];
+ struct hubp *hubp = pipe_ctx->plane_res.hubp;
+
+ if (!pipe_ctx->plane_state || !pipe_ctx->stream_res.tg)
+ continue;
+
+ if (pipe_ctx->stream_res.tg->inst != otg_inst)
+ continue;
+
+ if (hubp && hubp->funcs->hubp_is_flip_pending &&
+ hubp->funcs->hubp_is_flip_pending(hubp)) {
+ flip_pending = true;
+ break;
+ }
+ }
+
+ return flip_pending;
+}
+
void dc_power_down_on_boot(struct dc *dc)
{
if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW &&
diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
index 13c1f7cd9d7d3..94028f18dbdfe 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -2993,6 +2993,7 @@ enum dc_irq_source dc_interrupt_to_irq_source(
uint32_t ext_id);
bool dc_interrupt_set(struct dc *dc, enum dc_irq_source src, bool enable);
void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src);
+bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst);
enum dc_irq_source dc_get_hpd_irq_source_at_index(
struct dc *dc, uint32_t link_index);
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 3/3] Revert "drm/amd/display: Restore 5s vbl offdelay for NV3x+ DGPUs"
2026-06-30 18:02 [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption sunpeng.li
2026-06-30 18:02 ` [PATCH v3 1/3] drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock sunpeng.li
2026-06-30 18:03 ` [PATCH v3 2/3] drm/amd/display: check GRPH_FLIP status before sending event sunpeng.li
@ 2026-06-30 18:03 ` sunpeng.li
2026-07-01 21:09 ` [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption Mario Limonciello
3 siblings, 0 replies; 5+ messages in thread
From: sunpeng.li @ 2026-06-30 18:03 UTC (permalink / raw)
To: amd-gfx
Cc: Harry.Wentland, mario.limonciello, wiagn233, sysdadmin,
timur.kristof, xaver.hugl, mario.kleiner.de, michel.daenzer,
matthew.schwartz, chris, Leo Li, stable
From: Leo Li <sunpeng.li@amd.com>
Now that proper fixes have been found, let's revert this workaround.
This reverts commit 751414c12388ff2b475e15c15d3c817dcf563635.
Cc: stable@vger.kernel.org
Signed-off-by: Leo Li <sunpeng.li@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 18 +++---------------
1 file changed, 3 insertions(+), 15 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 754fedbdd7460..43a786f08516e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -3526,21 +3526,9 @@ static void manage_dm_interrupts(struct amdgpu_device *adev,
if (acrtc_state) {
timing = &acrtc_state->stream->timing;
- if (amdgpu_ip_version(adev, DCE_HWIP, 0) >=
- IP_VERSION(3, 2, 0) &&
- !(adev->flags & AMD_IS_APU)) {
- /*
- * DGPUs NV3x and newer that support idle optimizations
- * experience intermittent flip-done timeouts on cursor
- * updates. Restore 5s offdelay behavior for now.
- *
- * Discussion on the issue:
- * https://lore.kernel.org/amd-gfx/20260217191632.1243826-1-sysdadmin@m1k.cloud/
- */
- config.offdelay_ms = 5000;
- config.disable_immediate = false;
- } else if (amdgpu_ip_version(adev, DCE_HWIP, 0) <
- IP_VERSION(3, 5, 0)) {
+ if (amdgpu_ip_version(adev, DCE_HWIP, 0) <
+ IP_VERSION(3, 5, 0) ||
+ !(adev->flags & AMD_IS_APU)) {
/*
* Older HW and DGPU have issues with instant off;
* use a 2 frame offdelay.
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption
2026-06-30 18:02 [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption sunpeng.li
` (2 preceding siblings ...)
2026-06-30 18:03 ` [PATCH v3 3/3] Revert "drm/amd/display: Restore 5s vbl offdelay for NV3x+ DGPUs" sunpeng.li
@ 2026-07-01 21:09 ` Mario Limonciello
3 siblings, 0 replies; 5+ messages in thread
From: Mario Limonciello @ 2026-07-01 21:09 UTC (permalink / raw)
To: sunpeng.li, amd-gfx
Cc: Harry.Wentland, wiagn233, sysdadmin, timur.kristof, xaver.hugl,
mario.kleiner.de, michel.daenzer, matthew.schwartz, chris
On 6/30/26 13:02, sunpeng.li@amd.com wrote:
> From: Leo Li <sunpeng.li@amd.com>
>
> v1 here:
> https://lore.kernel.org/amd-gfx/20260616201828.389985-1-sunpeng.li@amd.com/
> v2 here:
> https://lore.kernel.org/amd-gfx/20260622171752.73374-1-sunpeng.li@amd.com/
>
> Summary of changes since v2:
>
> * Fixed a regression in kms_vrr@flip-basic, which exposed a scenario in
> optimistic event delivery where wrong vblank timestamps were attached.
> See new docstrings in patch 2/3 for details.
> * Readability cleanups in patch 2/3, thanks Michel!
>
> v7.0.y based branch here (drops the revert and resolves merge conflicts
> due to recent refactor of IRQ handlers from amdgpu_dm.c into
> amdgpu_dm_irq.c):
> https://gitlab.freedesktop.org/leoli/linux-asdn/-/commits/lileo/flip_done_timeout_corruption_fixes?ref_type=heads
>
> A Note on LLM use:
>
> Claude helped with code plumbing and IGT testing. The effort of
> reporting, debugging, spinning up fixes, and testing them, were from
> humans. Kudos to individuals who collaborated on the amd-gfx mailing
> list, gitlab, 2026 Display Hackfest, and AMD internal chats, to finally
> nail down a fix.
>
Having been one of those being able to readily reproduce issues at the
hackfest with PSR I'm happy to report that I tested a backport of this
series on a clean 7.2-rc1 where I don't observe any issues. Due to
changes in the codebase it's not purely the same as your content headed
to drm-next.
The branch of what I tested is here though if anyone else would like to
test on 7.2 as well.
https://git.kernel.org/pub/scm/linux/kernel/git/superm1/linux.git/log/?h=lileo/flip_done_timeout_corruption_fixes-7.2
So my tag isn't purely this series, but I guess at least close enough
with the backport if you want to carry it forward.
Tested-by: Mario Limonciello (AMD) <superm1@kernel.org>
Also as I reviewed the series nothing stands out to me, you obviously
know this part of the code base better than anyone though.
Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
Since I had a hard time applying this even to 7.2, I suspect after we
land this we'll need to do some custom/manual backports for each of the
applicable stable series at the time it actually lands in mainline.
>
> Leo Li (3):
> drm/amd/display: consolidate DCN vblank/flip handling onto
> vupdate_no_lock
> drm/amd/display: check GRPH_FLIP status before sending event
> Revert "drm/amd/display: Restore 5s vbl offdelay for NV3x+ DGPUs"
>
> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 234 +++++++++++---
> .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 70 ++--
> .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 299 +++++++++---------
> drivers/gpu/drm/amd/display/dc/core/dc.c | 45 +++
> drivers/gpu/drm/amd/display/dc/dc.h | 1 +
> 5 files changed, 417 insertions(+), 232 deletions(-)
>
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-01 21:09 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-30 18:02 [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption sunpeng.li
2026-06-30 18:02 ` [PATCH v3 1/3] drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock sunpeng.li
2026-06-30 18:03 ` [PATCH v3 2/3] drm/amd/display: check GRPH_FLIP status before sending event sunpeng.li
2026-06-30 18:03 ` [PATCH v3 3/3] Revert "drm/amd/display: Restore 5s vbl offdelay for NV3x+ DGPUs" sunpeng.li
2026-07-01 21:09 ` [PATCH v3 0/3] Fixes for flip_done timed outs and PSR/Replay corruption Mario Limonciello
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox