Skip to content

Commit 48ab863

Browse files
leo-sunli1alexdeucher
authored andcommitted
drm/amd/display: check GRPH_FLIP status before sending event
[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 1159898 ("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. Fixes: 9b47278 ("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 Tested-by: Mario Limonciello (AMD) <superm1@kernel.org> Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org> Signed-off-by: Leo Li <sunpeng.li@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com> (cherry picked from commit f64a9be) Cc: stable@vger.kernel.org # 8382cd2: drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock Cc: stable@vger.kernel.org
1 parent 8382cd2 commit 48ab863

3 files changed

Lines changed: 197 additions & 40 deletions

File tree

drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c

Lines changed: 151 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -681,13 +681,30 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev,
681681
* Deliver pageflip completion events (DCN only).
682682
*
683683
* Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch
684-
* point. Deliver any pending pageflip completion event from here.
684+
* point. Deliver any pending pageflip completion event from here,
685+
* once HW has consumed the new address (the OTG no longer reports a
686+
* pending flip).
685687
*
686-
* NOTE: This can deliver an event for a flip that was armed but not yet
687-
* programmed into HW; that race is closed in a follow-up change by
688-
* checking the programmed flip status.
688+
* Also handle the case here where there aren't any active planes and
689+
* DCN HUBP may be clock-gated, so the flip-pending status may be
690+
* undefined.
689691
*/
690-
if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) {
692+
if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
693+
acrtc->event) {
694+
695+
if (!dc_get_flip_pending_on_otg(adev->dm.dc, acrtc->otg_inst)) {
696+
drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
697+
acrtc->event = NULL;
698+
drm_crtc_vblank_put(&acrtc->base);
699+
acrtc->pflip_status = AMDGPU_FLIP_NONE;
700+
}
701+
/*
702+
* If the flip is still pending, leave it armed and
703+
* retry on the next vupdate.
704+
*/
705+
} else if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
706+
acrtc->dm_irq_params.active_planes == 0) {
707+
691708
if (acrtc->event) {
692709
drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
693710
acrtc->event = NULL;
@@ -10257,6 +10274,28 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_display_manager *dm,
1025710274
}
1025810275
}
1025910276

10277+
static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc,
10278+
struct dm_crtc_state *acrtc_state,
10279+
bool pflip_update,
10280+
bool cursor_update)
10281+
{
10282+
assert_spin_locked(&acrtc->base.dev->event_lock);
10283+
10284+
if (!acrtc->base.state->event || acrtc_state->active_planes == 0)
10285+
return;
10286+
10287+
if (pflip_update) {
10288+
drm_crtc_vblank_get(&acrtc->base);
10289+
WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE);
10290+
/* Arm flip completion handling and event delivery after programming. */
10291+
prepare_flip_isr(acrtc);
10292+
} else if (cursor_update) {
10293+
drm_crtc_vblank_get(&acrtc->base);
10294+
acrtc->event = acrtc->base.state->event;
10295+
acrtc->base.state->event = NULL;
10296+
}
10297+
}
10298+
1026010299
static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
1026110300
struct drm_device *dev,
1026210301
struct amdgpu_display_manager *dm,
@@ -10280,6 +10319,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
1028010319
bool cursor_update = false;
1028110320
bool pflip_present = false;
1028210321
bool immediate_flip = false;
10322+
bool flip_latched_during_prog = false;
1028310323
bool dirty_rects_changed = false;
1028410324
bool updated_planes_and_streams = false;
1028510325
struct {
@@ -10514,39 +10554,24 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
1051410554
usleep_range(1000, 1100);
1051510555
}
1051610556

10517-
/**
10518-
* Prepare the flip event for the pageflip interrupt to handle.
10519-
*
10520-
* This only works in the case where we've already turned on the
10521-
* appropriate hardware blocks (eg. HUBP) so in the transition case
10522-
* from 0 -> n planes we have to skip a hardware generated event
10523-
* and rely on sending it from software.
10524-
*/
10525-
if (acrtc_attach->base.state->event &&
10526-
acrtc_state->active_planes > 0) {
10527-
drm_crtc_vblank_get(pcrtc);
10528-
10529-
spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
10530-
10531-
WARN_ON(acrtc_attach->pflip_status != AMDGPU_FLIP_NONE);
10532-
prepare_flip_isr(acrtc_attach);
10533-
10534-
spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags);
10535-
}
10536-
1053710557
if (acrtc_state->stream) {
1053810558
if (acrtc_state->freesync_vrr_info_changed)
1053910559
bundle->stream_update.vrr_infopacket =
1054010560
&acrtc_state->stream->vrr_infopacket;
1054110561
}
10542-
} else if (cursor_update && acrtc_state->active_planes > 0) {
10543-
spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
10544-
if (acrtc_attach->base.state->event) {
10545-
drm_crtc_vblank_get(pcrtc);
10546-
acrtc_attach->event = acrtc_attach->base.state->event;
10547-
acrtc_attach->base.state->event = NULL;
10562+
}
10563+
10564+
/*
10565+
* DCE depends on a combination of GRPH_FLIP, VLINE0, and VUPDATE for
10566+
* event delivery. Only GRPH_FLIP handler can send pflip events, and it
10567+
* only fires if HW latched to the flip. Maintain legacy behavior by
10568+
* arming event before programming.
10569+
*/
10570+
if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) {
10571+
scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) {
10572+
dm_arm_vblank_event(acrtc_attach, acrtc_state,
10573+
pflip_present, cursor_update);
1054810574
}
10549-
spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags);
1055010575
}
1055110576

1055210577
/* Update the planes if changed or disable if we don't have any. */
@@ -10639,17 +10664,103 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
1063910664
amdgpu_dm_commit_cursors(state);
1064010665

1064110666
/*
10642-
* On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK.
10643-
* However, an immediate (tearing / async) flip is latched by HW right
10644-
* away and does not wait for the next vupdate, so deliver its
10645-
* completion event here after programming.
10667+
* DCN specific vblank handling
10668+
* ============================
10669+
*
10670+
* With the event_lock held, arm the vblank event, and determine whether
10671+
* deliver it immediately, or in VUPDATE_NO_LOCK IRQ (i.e. HW latch
10672+
* point) handler. Do this *after* programming so that the IRQ handler
10673+
* will not deliver the event before HW laches onto the programmed
10674+
* values:
10675+
*
10676+
* Commit thread IRQ handler HW
10677+
* -----------------------------------------------------------------
10678+
* arm_vblank_event()
10679+
* vupdate()
10680+
* vupdate_handler()
10681+
* cook_timestamp()
10682+
* # prev flip already latched,
10683+
* # so flip_latched == true.
10684+
* if event_armed && flip_latched:
10685+
* send_vblank_event()
10686+
* # sent before latch, **BAD!**
10687+
* hw_program()
10688+
* vupdate()
10689+
* **latch**
10690+
*
10691+
* There's a consequence of arming after: it's possible for HW to latch
10692+
* between start of HW programming and acrtc->event/pflip_status arming.
10693+
* When this happens, the IRQ handler will send the event on the next
10694+
* immediate latch point, even though HW has already latched. This is
10695+
* handled by optimistically checking for HW latch after programming,
10696+
* and if latched, send the event immediately:
1064610697
*
10647-
* On DCE, GRPH_PFLIP already fires immediately for immediate flips, so
10648-
* this is DCN-only.
10698+
* Commit thread IRQ handler HW
10699+
* -----------------------------------------------------------------
10700+
* hw_program()
10701+
* vupdate()
10702+
* **latch**
10703+
* vupdate_handler()
10704+
* cook_timestamp()
10705+
* # event_armed == false
10706+
* # **no event sent!**
10707+
* arm_vblank_event()
10708+
* if flip_latched:
10709+
* **send_vblank_event()**
10710+
* disarm_vblank_event()
10711+
*
10712+
* The IRQ handler is expected to cook the timestamp, but we need to
10713+
* cook the timestamp before optimistic sending as well. That's because
10714+
* the following sequence is possible:
10715+
*
10716+
* Commit thread IRQ handler HW
10717+
* -----------------------------------------------------------------
10718+
* hw_program()
10719+
* arm_vblank_event()
10720+
* vupdate()
10721+
* **latch**
10722+
* if flip_latched:
10723+
* # Need cook before send!
10724+
* **cook_timestamp()**
10725+
* send_vblank_event()
10726+
* disarm_vblank_event()
10727+
* vupdate_handler()
10728+
* cook_timestamp()
10729+
* # event_armed == false
10730+
* # no event sent!
10731+
*
10732+
* Cooking twice is OK, since DRM scanout accurate timestamps report A)
10733+
* the previous vactive start if currently in vactive, or B) the next
10734+
* vactive start if currently in vblank (see &get_vblank_counter). 'A)'
10735+
* is what we want for the optimistic send, and for 'B)', we'll cook a
10736+
* timestamp no later than the next IRQ handler run.
10737+
*
10738+
* The more correct fix is to wrap programming and arming with the
10739+
* event_lock and thus serializing it with the IRQ handler. However,
10740+
* there are various sleep-waits within
10741+
* update_planes_and_stream_adapter() that makes spin locking illegal.
10742+
* And on full updates, it can take 1-2 frame-times to return (see
10743+
* commit_planes_for_stream).
10744+
*
10745+
* On DCE, GRPH_PFLIP IRQ is used and takes care of this.
1064910746
*/
10650-
if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) {
10747+
if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) {
1065110748
spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
10652-
if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED &&
10749+
10750+
if (updated_planes_and_streams) {
10751+
flip_latched_during_prog =
10752+
!dc_get_flip_pending_on_otg(dm->dc, acrtc_attach->otg_inst);
10753+
}
10754+
10755+
dm_arm_vblank_event(acrtc_attach, acrtc_state,
10756+
pflip_present, cursor_update);
10757+
10758+
/*
10759+
* Deliver the event immediately on immediate flip, or on a
10760+
* update that has already latched.
10761+
*/
10762+
if ((immediate_flip || flip_latched_during_prog) &&
10763+
acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED &&
1065310764
acrtc_attach->event) {
1065410765
drm_crtc_accurate_vblank_count(&acrtc_attach->base);
1065510766
drm_crtc_send_vblank_event(&acrtc_attach->base,

drivers/gpu/drm/amd/display/dc/core/dc.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6165,6 +6165,51 @@ void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src)
61656165
dal_irq_service_ack(dc->res_pool->irqs, src);
61666166
}
61676167

6168+
/*
6169+
* dc_get_flip_pending_on_otg() - Check if a GRPH_FLIP is still pending on OTG
6170+
*
6171+
* @dc: display core context @otg_inst: OTG instance to query
6172+
*
6173+
* Reads the HUBP flip-pending status for the pipe(s) bound to @otg_inst,
6174+
* returning true if any of them has not yet latched its programmed surface
6175+
* address.
6176+
*
6177+
* Unlike dc_plane_get_status(), this does not take or mutate a dc_plane_state,
6178+
* so it is safe to call from interrupt context without racing a concurrent
6179+
* commit that may be updating plane state.
6180+
*
6181+
* Return: true if a flip is still pending on the OTG, false otherwise.
6182+
*/
6183+
bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst)
6184+
{
6185+
bool flip_pending = false;
6186+
int i;
6187+
6188+
if (!dc || !dc->current_state)
6189+
return false;
6190+
6191+
dc_exit_ips_for_hw_access(dc);
6192+
6193+
for (i = 0; i < dc->res_pool->pipe_count; i++) {
6194+
struct pipe_ctx *pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i];
6195+
struct hubp *hubp = pipe_ctx->plane_res.hubp;
6196+
6197+
if (!pipe_ctx->plane_state || !pipe_ctx->stream_res.tg)
6198+
continue;
6199+
6200+
if (pipe_ctx->stream_res.tg->inst != otg_inst)
6201+
continue;
6202+
6203+
if (hubp && hubp->funcs->hubp_is_flip_pending &&
6204+
hubp->funcs->hubp_is_flip_pending(hubp)) {
6205+
flip_pending = true;
6206+
break;
6207+
}
6208+
}
6209+
6210+
return flip_pending;
6211+
}
6212+
61686213
void dc_power_down_on_boot(struct dc *dc)
61696214
{
61706215
if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW &&

drivers/gpu/drm/amd/display/dc/dc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2883,6 +2883,7 @@ enum dc_irq_source dc_interrupt_to_irq_source(
28832883
uint32_t ext_id);
28842884
bool dc_interrupt_set(struct dc *dc, enum dc_irq_source src, bool enable);
28852885
void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src);
2886+
bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst);
28862887
enum dc_irq_source dc_get_hpd_irq_source_at_index(
28872888
struct dc *dc, uint32_t link_index);
28882889

0 commit comments

Comments
 (0)