* [PATCH RESEND 2/5] drm/vblank: Stop drm_crtc_wait_one_vblank() when vblank is turned off
2026-11-11 8:55 [PATCH RESEND 1/5] drm/client: Make the FBIO_WAITFORVSYNC vblank wait interruptible Prike Liang
@ 2026-11-11 8:55 ` Prike Liang
2026-11-11 8:55 ` [PATCH RESEND 3/5] drm/amd/display: send the flip event right away if vblank is off Prike Liang
` (2 subsequent siblings)
3 siblings, 0 replies; 8+ messages in thread
From: Prike Liang @ 2026-11-11 8:55 UTC (permalink / raw)
To: amd-gfx, dri-devel
Cc: Alexander.Deucher, Christian.Koenig, Harry.Wentland, Sunpeng.Li,
tzimmermann, Prike Liang
drm_crtc_vblank_off() turns off the vblank interrupt even while someone
holds a vblank reference and waits for a vblank. It then wakes up the
vblank wait queue. DRM_IOCTL_WAIT_VBLANK stops waiting at this point,
because its wait condition also checks whether vblank is still enabled.
drm_crtc_wait_one_vblank() and drm_crtc_wait_one_vblank_interruptible()
only check whether the vblank count has changed. Once vblank is off, the
count doesn't change, so the woken waiter sleeps again until the wait
times out after one second and warns "vblank wait timed out". This
happens, for example, when the fbdev damage worker or FBIO_WAITFORVSYNC
waits for a vblank while the driver turns the CRTC off for suspend or a
GPU reset.
Make __drm_crtc_wait_one_vblank(), which implements both functions, also
stop waiting when vblank is turned off, and return -EINVAL if no vblank
passed, the same error that drm_vblank_get() returns when vblank is off.
The wait condition is checked before going to sleep, so this also covers
vblank being turned off between drm_vblank_get() and the wait. Waits
that see a vblank, time out or are interrupted behave as before.
Fixes: e8450f51a4b3 ("drm/irq: Implement a generic vblank_wait function")
Cc: Thomas Zimmermann <tzimmermann@suse.de>
Assisted-by: Copilot:claude-opus-5.5
Signed-off-by: Prike Liang <Prike.Liang@amd.com>
---
drivers/gpu/drm/drm_vblank.c | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c
index c3c9afc08ed2..5e247663978a 100644
--- a/drivers/gpu/drm/drm_vblank.c
+++ b/drivers/gpu/drm/drm_vblank.c
@@ -1315,21 +1315,29 @@ static int __drm_crtc_wait_one_vblank(struct drm_crtc *crtc, bool interruptible)
if (interruptible)
timeout = wait_event_interruptible_timeout(vblank->queue,
- last != drm_vblank_count(dev, pipe),
+ last != drm_vblank_count(dev, pipe) ||
+ !READ_ONCE(vblank->enabled),
timeout);
else
timeout = wait_event_timeout(vblank->queue,
- last != drm_vblank_count(dev, pipe),
+ last != drm_vblank_count(dev, pipe) ||
+ !READ_ONCE(vblank->enabled),
timeout);
drm_WARN(dev, timeout == 0, "vblank wait timed out on crtc %i\n", pipe);
- drm_vblank_put(dev, pipe);
-
if (timeout < 0)
- return timeout;
+ ret = timeout;
+ else if (!timeout)
+ ret = -ETIMEDOUT;
+ else if (last == drm_vblank_count(dev, pipe))
+ ret = -EINVAL; /* drm_crtc_vblank_off() ended the wait */
+ else
+ ret = 0;
- return timeout ? 0 : -ETIMEDOUT;
+ drm_vblank_put(dev, pipe);
+
+ return ret;
}
/**
@@ -1338,7 +1346,8 @@ static int __drm_crtc_wait_one_vblank(struct drm_crtc *crtc, bool interruptible)
*
* This waits for one vblank to pass on @crtc, using the irq driver interfaces.
* It is a failure to call this when the vblank irq for @crtc is disabled, e.g.
- * due to lack of driver support or because the crtc is off.
+ * due to lack of driver support or because the crtc is off. The wait also fails
+ * if drm_crtc_vblank_off() disables the vblank irq before a vblank passes.
*
* Returns: 0 on success, negative error on failures.
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH RESEND 3/5] drm/amd/display: send the flip event right away if vblank is off
2026-11-11 8:55 [PATCH RESEND 1/5] drm/client: Make the FBIO_WAITFORVSYNC vblank wait interruptible Prike Liang
2026-11-11 8:55 ` [PATCH RESEND 2/5] drm/vblank: Stop drm_crtc_wait_one_vblank() when vblank is turned off Prike Liang
@ 2026-11-11 8:55 ` Prike Liang
2026-11-11 8:55 ` [PATCH RESEND 4/5] drm/amd/display: turn vblank off in DRM across GPU reset Prike Liang
2026-11-11 8:55 ` [PATCH RESEND 5/5] drm/amdgpu: keep reading the vblank counter in IRQ context during reset Prike Liang
3 siblings, 0 replies; 8+ messages in thread
From: Prike Liang @ 2026-11-11 8:55 UTC (permalink / raw)
To: amd-gfx, dri-devel
Cc: Alexander.Deucher, Christian.Koenig, Harry.Wentland, Sunpeng.Li,
tzimmermann, Prike Liang, Harry Wentland, Leo Li
dm_arm_vblank_event_pre_programming() takes a vblank reference for a
page flip or cursor update that carries an event, and the handler that
later sends the event drops that reference again. The return value of
drm_crtc_vblank_get() is ignored, though. If vblank is off, the event
is armed anyway, and the handler that eventually sends it drops a
reference that was never taken, which unbalances the vblank refcount.
That can't happen today: DM only turns vblank off for a CRTC that it
disables, and a disabled CRTC has no active planes. The next patch also
turns vblank off while a GPU reset has the streams stopped, though.
Page flips that don't modify the DC state skip
amdgpu_dm_commit_streams() and only take dc_lock after this point, so
they still get here while the reset holds it.
Send the event right away when no vblank reference can be taken, like
drm_crtc_vblank_off() does for queued vblank events. No vblank interrupt
could deliver it in that case anyway.
Add a KUnit test for it.
Cc: Harry Wentland <harry.wentland@amd.com>
Cc: Leo Li <sunpeng.li@amd.com>
Assisted-by: Copilot:claude-opus-5.5
Signed-off-by: Prike Liang <Prike.Liang@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 15 ++++++++--
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 29 +++++++++++++++++++
2 files changed, 42 insertions(+), 2 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 33c73ca9cff1..d3882ffcb5d4 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -3825,6 +3825,11 @@ EXPORT_IF_KUNIT(dm_arm_vblank_event);
* Grab a reference on the vblank counter if a page flip or cursor update is to
* be programmed. Do this before programming so the HW is not in any
* idle-optimized state (such as PSR).
+ *
+ * If vblank is off, no vblank interrupt can deliver the event, and the
+ * handlers that send it would drop a reference that was never taken. Send
+ * the event right away instead, like drm_crtc_vblank_off() does for queued
+ * vblank events.
*/
STATIC_IFN_KUNIT void dm_arm_vblank_event_pre_programming(struct amdgpu_crtc *acrtc,
struct dm_crtc_state *acrtc_state,
@@ -3836,8 +3841,14 @@ STATIC_IFN_KUNIT void dm_arm_vblank_event_pre_programming(struct amdgpu_crtc *ac
if (!acrtc->base.state->event || acrtc_state->active_planes == 0)
return;
- if (pflip_update || cursor_update)
- drm_crtc_vblank_get(&acrtc->base);
+ if (!pflip_update && !cursor_update)
+ return;
+
+ if (drm_crtc_vblank_get(&acrtc->base)) {
+ drm_crtc_send_vblank_event(&acrtc->base,
+ acrtc->base.state->event);
+ acrtc->base.state->event = NULL;
+ }
}
EXPORT_IF_KUNIT(dm_arm_vblank_event_pre_programming);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index b9272b28f53c..ebec1260c510 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -1812,6 +1812,34 @@ static void dm_test_arm_vblank_pre_programming_update(struct kunit *test)
drm_crtc_vblank_put(&ctx->acrtc->base);
}
+/**
+ * dm_test_arm_vblank_pre_programming_vblank_off - Test the event is sent with vblank off
+ * @test: The KUnit test context
+ *
+ * No vblank interrupt can deliver the event, and the handlers that send it
+ * must not drop a vblank reference that was never taken.
+ */
+static void dm_test_arm_vblank_pre_programming_vblank_off(struct kunit *test)
+{
+ struct dm_test_vblank_ctx *ctx = dm_test_vblank_ctx_alloc(test);
+ struct drm_vblank_crtc *vblank;
+
+ KUNIT_ASSERT_EQ(test, drm_vblank_init(&ctx->adev->ddev, 1), 0);
+ vblank = drm_crtc_vblank_crtc(&ctx->acrtc->base);
+ drm_crtc_vblank_off(&ctx->acrtc->base);
+
+ /* Sending an event that has no file frees it. */
+ ctx->acrtc_state->base.event = kzalloc_obj(*ctx->event, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->acrtc_state->base.event);
+
+ dm_test_arm_vblank_pre_programming(ctx, true, false);
+
+ KUNIT_EXPECT_NULL(test, ctx->acrtc_state->base.event);
+ /* Only drm_crtc_vblank_off() holds a reference. */
+ KUNIT_EXPECT_EQ(test, atomic_read(&vblank->refcount), 1);
+ kfree(ctx->acrtc_state->base.event);
+}
+
/**
* dm_test_arm_vblank_event_no_event - Test a commit without an event is a no-op
* @test: The KUnit test context
@@ -5298,6 +5326,7 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_arm_vblank_pre_programming_no_event),
KUNIT_CASE(dm_test_arm_vblank_pre_programming_no_planes),
KUNIT_CASE(dm_test_arm_vblank_pre_programming_update),
+ KUNIT_CASE(dm_test_arm_vblank_pre_programming_vblank_off),
/* dm_update_pflip_irq_state */
KUNIT_CASE(dm_test_update_pflip_irq_state_dcn),
KUNIT_CASE(dm_test_update_pflip_irq_state_dce),
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH RESEND 4/5] drm/amd/display: turn vblank off in DRM across GPU reset
2026-11-11 8:55 [PATCH RESEND 1/5] drm/client: Make the FBIO_WAITFORVSYNC vblank wait interruptible Prike Liang
2026-11-11 8:55 ` [PATCH RESEND 2/5] drm/vblank: Stop drm_crtc_wait_one_vblank() when vblank is turned off Prike Liang
2026-11-11 8:55 ` [PATCH RESEND 3/5] drm/amd/display: send the flip event right away if vblank is off Prike Liang
@ 2026-11-11 8:55 ` Prike Liang
2026-11-11 9:28 ` sashiko-bot
2026-11-11 8:55 ` [PATCH RESEND 5/5] drm/amdgpu: keep reading the vblank counter in IRQ context during reset Prike Liang
3 siblings, 1 reply; 8+ messages in thread
From: Prike Liang @ 2026-11-11 8:55 UTC (permalink / raw)
To: amd-gfx, dri-devel
Cc: Alexander.Deucher, Christian.Koenig, Harry.Wentland, Sunpeng.Li,
tzimmermann, Prike Liang, Harry Wentland, Leo Li,
Christian König, Alex Deucher
For a GPU reset, dm_suspend() stops all streams with
amdgpu_dm_commit_zero_streams() and toggles the OTG interrupts
directly, and dm_resume() restores the cached DC state the same way.
Neither goes through manage_dm_interrupts(), so DRM is never told that
vblank stops: drm_crtc_vblank_get() keeps succeeding while no vblank
interrupt can arrive until the streams are back.
Since commit ebc3351cc4a9 ("drm/amdgpu: remove suspend/resume DRM
clients during GPU recovery"), the fbdev client keeps running during
recovery, and its damage worker rate-limits display updates with
drm_client_modeset_wait_for_vblank(). Every display update during the
reset then waits for a vblank that never comes, until the wait times
out and warns:
amdgpu 0000:03:00.0: [drm] vblank wait timed out on crtc 0
WARNING: drivers/gpu/drm/drm_vblank.c:1320 at drm_crtc_wait_one_vblank+0x194/0x200
Workqueue: events drm_fb_helper_damage_work
Call Trace:
drm_client_modeset_wait_for_vblank+0x63/0x80
drm_fb_helper_damage_work+0x89/0x1a0
FBIO_WAITFORVSYNC runs into the same timeout, and DRM_IOCTL_WAIT_VBLANK
sleeps until its own 3 second timeout.
Do what a regular CRTC disable does: call drm_crtc_vblank_off() for the
CRTCs of the cached DC state before their streams are stopped, and
drm_crtc_vblank_on_config() with their existing configuration once the
streams and interrupts are restored. drm_crtc_vblank_get() then fails
for the duration of the reset, so drm_client_modeset_wait_for_vblank()
doesn't wait at all, and DRM_IOCTL_WAIT_VBLANK waiters are woken up.
Page flips that get to amdgpu_dm_commit_planes() meanwhile have their
event sent right away, see the previous patch.
drm_crtc_vblank_off() also clears the vblank timestamping constants,
which a modeset sets again before it turns vblank back on. Recompute
them from the CRTC state after the reset as well; otherwise the vblank
timestamp query in drm_crtc_vblank_on_config() warns about an
uninitialized mode.
A modeset that waits for dc_lock during the reset may be turning one of
these CRTCs off. Leave vblank off for a CRTC that is no longer enabled;
amdgpu_dm_crtc_set_vblank() would refuse to enable vblank on it anyway.
Neither call can block on the reset: drm_crtc_vblank_off() only takes
spinlocks and flushes the DRM vblank worker, which DM doesn't use, and
amdgpu_dm_crtc_set_vblank() doesn't queue vblank_control_work during a
reset. Unlike suspending the DRM clients, this doesn't make recovery
wait for anything that may itself depend on the reset.
Add KUnit tests for the new helper.
Fixes: ebc3351cc4a9 ("drm/amdgpu: remove suspend/resume DRM clients during GPU recovery")
Cc: Harry Wentland <harry.wentland@amd.com>
Cc: Leo Li <sunpeng.li@amd.com>
Cc: Christian König <christian.koenig@amd.com>
Cc: Alex Deucher <alexander.deucher@amd.com>
Assisted-by: Copilot:claude-opus-5.5
Signed-off-by: Prike Liang <Prike.Liang@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 52 ++++++++
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 1 +
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 112 ++++++++++++++++++
3 files changed, 165 insertions(+)
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 d3882ffcb5d4..007968e892c8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -1496,6 +1496,56 @@ STATIC_IFN_KUNIT void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev,
}
EXPORT_IF_KUNIT(dm_gpureset_toggle_interrupts);
+/**
+ * dm_gpureset_toggle_vblank() - Turn DRM vblank off or on around a GPU reset
+ * @adev: amdgpu device
+ * @state: DC state whose streams the reset stops and restores
+ * @enable: false before the streams are stopped, true once they are restored
+ *
+ * The GPU reset path stops and restores the streams through DC directly and
+ * bypasses manage_dm_interrupts(). Turn vblank off in DRM like a CRTC
+ * disable does, so that drm_crtc_vblank_get() fails while no vblank
+ * interrupt can arrive, instead of letting vblank waiters such as the fbdev
+ * damage worker sleep until they time out.
+ */
+STATIC_IFN_KUNIT void dm_gpureset_toggle_vblank(struct amdgpu_device *adev,
+ struct dc_state *state, bool enable)
+{
+ struct drm_vblank_crtc_config config;
+ struct amdgpu_crtc *acrtc;
+ int i;
+
+ for (i = 0; i < state->stream_count; i++) {
+ int otg_inst = state->stream_status[i].primary_otg_inst;
+
+ acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, otg_inst);
+ if (!acrtc)
+ continue;
+
+ if (!enable) {
+ drm_crtc_vblank_off(&acrtc->base);
+ continue;
+ }
+
+ /*
+ * A modeset waiting for dc_lock may be turning the CRTC off,
+ * and amdgpu_dm_crtc_set_vblank() refuses to enable vblank on
+ * it then. Leave its vblank off.
+ */
+ if (!acrtc->base.enabled)
+ continue;
+
+ /* drm_crtc_vblank_off() cleared the timestamping constants. */
+ drm_calc_timestamping_constants(&acrtc->base,
+ &acrtc->base.state->adjusted_mode);
+
+ /* Keep the off delay that manage_dm_interrupts() configured. */
+ config = drm_crtc_vblank_crtc(&acrtc->base)->config;
+ drm_crtc_vblank_on_config(&acrtc->base, &config);
+ }
+}
+EXPORT_IF_KUNIT(dm_gpureset_toggle_vblank);
+
DEFINE_FREE(state_release, struct dc_state *, dc_state_release(_T))
STATIC_IFN_KUNIT enum dc_status amdgpu_dm_commit_zero_streams(struct dc *dc)
@@ -1630,6 +1680,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block)
return -ENOMEM;
}
+ dm_gpureset_toggle_vblank(adev, dm->cached_dc_state, false);
dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, false);
res = amdgpu_dm_commit_zero_streams(dm->dc);
@@ -1969,6 +2020,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
dm_gpureset_commit_state(dm->cached_dc_state, dm);
dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, true);
+ dm_gpureset_toggle_vblank(adev, dm->cached_dc_state, true);
dc_state_release(dm->cached_dc_state);
dm->cached_dc_state = NULL;
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 f8503ebf28f4..303e9a57e7b8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1351,6 +1351,7 @@ void mmhub_read_system_context(struct amdgpu_device *adev,
struct dc_phy_addr_space_config *pa_config);
int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm);
void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, struct dc_state *state, bool enable);
+void dm_gpureset_toggle_vblank(struct amdgpu_device *adev, struct dc_state *state, bool enable);
enum dc_status amdgpu_dm_commit_zero_streams(struct dc *dc);
int dm_cache_state(struct amdgpu_device *adev);
void dm_destroy_cached_state(struct amdgpu_device *adev);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index ebec1260c510..ed2e65bad674 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -5232,6 +5232,113 @@ static void dm_test_gpureset_toggle_interrupts_dcn(struct kunit *test)
dm_gpureset_toggle_interrupts(adev, state, true);
}
+/* Tests for dm_gpureset_toggle_vblank() */
+
+/*
+ * One stream on the OTG of the CRTC from dm_test_irq_mgmt_ctx_alloc(), which
+ * is enabled with a 1080p60 mode and has its vblank set up like on a regular
+ * CRTC enable: timestamping constants first, then manage_dm_interrupts().
+ */
+static struct dc_state *dm_test_gpureset_vblank_state(struct kunit *test,
+ struct dm_test_irq_mgmt_ctx *ctx)
+{
+ struct drm_display_mode *mode = &ctx->acrtc_state->base.adjusted_mode;
+ struct dc_state *state = dm_kunit_alloc_dc_state(test);
+
+ mode->crtc_clock = 148500;
+ mode->crtc_htotal = 2200;
+ mode->crtc_vtotal = 1125;
+ ctx->acrtc->base.state = &ctx->acrtc_state->base;
+ ctx->acrtc->base.enabled = true;
+ drm_calc_timestamping_constants(&ctx->acrtc->base, mode);
+ ctx->adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 2);
+ manage_dm_interrupts(ctx->adev, ctx->acrtc, ctx->acrtc_state);
+
+ state->stream_count = 1;
+ state->stream_status[0].primary_otg_inst = ctx->acrtc->otg_inst;
+
+ return state;
+}
+
+/**
+ * dm_test_gpureset_toggle_vblank_off - Test vblank is off while streams are down
+ * @test: The KUnit test context
+ *
+ * drm_crtc_vblank_get() has to fail, or vblank waiters such as the fbdev
+ * damage worker sleep until they time out.
+ */
+static void dm_test_gpureset_toggle_vblank_off(struct kunit *test)
+{
+ struct dm_test_irq_mgmt_ctx *ctx = dm_test_irq_mgmt_ctx_alloc(test);
+ struct dc_state *state = dm_test_gpureset_vblank_state(test, ctx);
+ struct drm_crtc *crtc = &ctx->acrtc->base;
+
+ KUNIT_ASSERT_EQ(test, drm_crtc_vblank_get(crtc), 0);
+ drm_crtc_vblank_put(crtc);
+
+ dm_gpureset_toggle_vblank(ctx->adev, state, false);
+
+ KUNIT_EXPECT_FALSE(test, ctx->adev->ddev.vblank[0].enabled);
+ KUNIT_EXPECT_EQ(test, drm_crtc_vblank_get(crtc), -EINVAL);
+}
+
+/**
+ * dm_test_gpureset_toggle_vblank_on - Test vblank comes back with its config and mode
+ * @test: The KUnit test context
+ */
+static void dm_test_gpureset_toggle_vblank_on(struct kunit *test)
+{
+ struct dm_test_irq_mgmt_ctx *ctx = dm_test_irq_mgmt_ctx_alloc(test);
+ struct dc_state *state = dm_test_gpureset_vblank_state(test, ctx);
+ struct drm_crtc *crtc = &ctx->acrtc->base;
+
+ dm_gpureset_toggle_vblank(ctx->adev, state, false);
+ dm_gpureset_toggle_vblank(ctx->adev, state, true);
+
+ /* manage_dm_interrupts() derived this off delay from the timing. */
+ KUNIT_EXPECT_EQ(test, ctx->adev->ddev.vblank[0].config.offdelay_ms, 34);
+ /* Vblank timestamps need the mode that drm_crtc_vblank_off() cleared. */
+ KUNIT_EXPECT_EQ(test, ctx->adev->ddev.vblank[0].hwmode.crtc_clock, 148500);
+ KUNIT_ASSERT_EQ(test, drm_crtc_vblank_get(crtc), 0);
+ drm_crtc_vblank_put(crtc);
+}
+
+/**
+ * dm_test_gpureset_toggle_vblank_no_crtc - Test streams without a CRTC are skipped
+ * @test: The KUnit test context
+ */
+static void dm_test_gpureset_toggle_vblank_no_crtc(struct kunit *test)
+{
+ struct dm_test_irq_mgmt_ctx *ctx = dm_test_irq_mgmt_ctx_alloc(test);
+ struct dc_state *state = dm_test_gpureset_vblank_state(test, ctx);
+ struct drm_crtc *crtc = &ctx->acrtc->base;
+
+ state->stream_status[0].primary_otg_inst = ctx->acrtc->otg_inst + 1;
+
+ dm_gpureset_toggle_vblank(ctx->adev, state, false);
+
+ KUNIT_ASSERT_EQ(test, drm_crtc_vblank_get(crtc), 0);
+ drm_crtc_vblank_put(crtc);
+}
+
+/**
+ * dm_test_gpureset_toggle_vblank_crtc_disabled - Test a disabled CRTC stays off
+ * @test: The KUnit test context
+ *
+ * A modeset that waits for the reset to finish may be turning the CRTC off.
+ */
+static void dm_test_gpureset_toggle_vblank_crtc_disabled(struct kunit *test)
+{
+ struct dm_test_irq_mgmt_ctx *ctx = dm_test_irq_mgmt_ctx_alloc(test);
+ struct dc_state *state = dm_test_gpureset_vblank_state(test, ctx);
+
+ dm_gpureset_toggle_vblank(ctx->adev, state, false);
+ ctx->acrtc->base.enabled = false;
+ dm_gpureset_toggle_vblank(ctx->adev, state, true);
+
+ KUNIT_EXPECT_EQ(test, drm_crtc_vblank_get(&ctx->acrtc->base), -EINVAL);
+}
+
static struct kunit_case amdgpu_dm_tests[] = {
/* Simple DM callbacks */
KUNIT_CASE(dm_test_wait_for_idle),
@@ -5481,6 +5588,11 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_initialize_plane_overlay),
/* dm_gpureset_toggle_interrupts */
KUNIT_CASE(dm_test_gpureset_toggle_interrupts_dcn),
+ /* dm_gpureset_toggle_vblank */
+ KUNIT_CASE(dm_test_gpureset_toggle_vblank_off),
+ KUNIT_CASE(dm_test_gpureset_toggle_vblank_on),
+ KUNIT_CASE(dm_test_gpureset_toggle_vblank_no_crtc),
+ KUNIT_CASE(dm_test_gpureset_toggle_vblank_crtc_disabled),
{}
};
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH RESEND 4/5] drm/amd/display: turn vblank off in DRM across GPU reset
2026-11-11 8:55 ` [PATCH RESEND 4/5] drm/amd/display: turn vblank off in DRM across GPU reset Prike Liang
@ 2026-11-11 9:28 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-11-11 9:28 UTC (permalink / raw)
To: Prike Liang, dri-devel
Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [High] drm/amd/display: active_vblank_irq_count leak in
dm_gpureset_toggle_vblank()
- [High] drm/amd/display: vblank counter not sampled in
dm_gpureset_toggle_vblank()
- [Medium] drm/amd/display: stale crtc->enabled check in
dm_gpureset_toggle_vblank()
- [Medium] drm/amd/display: vblank left off on dm_suspend() zero-streams
failure
- [Medium] drm/amd/display: vblank refcount imbalance in VRR and
writeback during reset
--
commit 15abd68686d02333caad7c1d61e5d6d9f0f989e1
Author: Prike Liang <Prike.Liang@amd.com>
drm/amd/display: turn vblank off in DRM across GPU reset
This commit turns DRM vblank off before stopping DC streams during a GPU
reset and restores it once the streams and interrupts are back.
> 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 22d5ac351f87c..2f60ad0746764 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm.c
> @@ -1494,6 +1494,56 @@ STATIC_IFN_KUNIT void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev,
> }
> EXPORT_IF_KUNIT(dm_gpureset_toggle_interrupts);
>
> +/**
> + * dm_gpureset_toggle_vblank() - Turn DRM vblank off or on around a GPU reset
> + * @adev: amdgpu device
> + * @state: DC state whose streams the reset stops and restores
> + * @enable: false before the streams are stopped, true once they are restored
> + *
> + * The GPU reset path stops and restores the streams through DC directly and
> + * bypasses manage_dm_interrupts(). Turn vblank off in DRM like a CRTC
> + * disable does, so that drm_crtc_vblank_get() fails while no vblank
> + * interrupt can arrive, instead of letting vblank waiters such as the fbdev
> + * damage worker sleep until they time out.
> + */
> +STATIC_IFN_KUNIT void dm_gpureset_toggle_vblank(struct amdgpu_device *adev,
> + struct dc_state *state, bool enable)
> +{
> + struct drm_vblank_crtc_config config;
> + struct amdgpu_crtc *acrtc;
> + int i;
> +
> + for (i = 0; i < state->stream_count; i++) {
> + int otg_inst = state->stream_status[i].primary_otg_inst;
> +
> + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, otg_inst);
> + if (!acrtc)
> + continue;
> +
> + if (!enable) {
> + drm_crtc_vblank_off(&acrtc->base);
> + continue;
> + }
[Severity: Medium]
Can turning vblank off here cause a vblank reference imbalance when a
concurrent atomic commit enables VRR or arms a writeback job?
amdgpu_dm_handle_vrr_transition() and dm_set_writeback() both call
WARN_ON(drm_crtc_vblank_get(...)) outside dm->dc_lock without checking the
return value:
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c:amdgpu_dm_handle_vrr_transition() {
...
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);
scoped_guard(mutex, &dm->dc_lock) {
...
}
When drm_crtc_vblank_get() returns -EINVAL during the reset, it triggers
the WARN_ON and takes no reference, but disabling VRR or completing the
writeback later still calls drm_crtc_vblank_put() unconditionally.
> +
> + /*
> + * A modeset waiting for dc_lock may be turning the CRTC off,
> + * and amdgpu_dm_crtc_set_vblank() refuses to enable vblank on
> + * it then. Leave its vblank off.
> + */
> + if (!acrtc->base.enabled)
> + continue;
> +
> + /* drm_crtc_vblank_off() cleared the timestamping constants. */
> + drm_calc_timestamping_constants(&acrtc->base,
> + &acrtc->base.state->adjusted_mode);
> +
> + /* Keep the off delay that manage_dm_interrupts() configured. */
> + config = drm_crtc_vblank_crtc(&acrtc->base)->config;
> + drm_crtc_vblank_on_config(&acrtc->base, &config);
> + }
> +}
> +EXPORT_IF_KUNIT(dm_gpureset_toggle_vblank);
[Severity: High]
Does this leak dm->active_vblank_irq_count and leave the CRTC's idle state
manager stuck in DM_ISM_STATE_FULL_POWER_RUNNING whenever vblank was
enabled before the reset?
When vblank is enabled before the reset, amdgpu_dm_crtc_vblank_control_worker()
increments dm->active_vblank_irq_count. During the reset,
drm_crtc_vblank_off() disables vblank and wakes any waiters so they drop
their references, while amdgpu_dm_crtc_set_vblank() returns early because
amdgpu_in_reset(adev) is true:
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c:amdgpu_dm_crtc_set_vblank() {
...
if (amdgpu_in_reset(adev))
return 0;
if (dm->vblank_control_workqueue) {
...
}
In dm_resume(), drm_crtc_vblank_on_config() only calls drm_vblank_enable()
when vblank->refcount != 0 || !vblank->config.offdelay_ms. Because the
waiters already dropped their references and offdelay_ms is non-zero,
vblank->enabled stays false without ever queueing a disable work item to
decrement dm->active_vblank_irq_count or send
DM_ISM_EVENT_ENTER_IDLE_REQUESTED. Since dm_ism_commit_idle_optimization_state()
and amdgpu_dm_commit_streams() require dm->active_vblank_irq_count == 0,
display idle power optimizations remain permanently disabled after the
reset.
[Severity: Medium]
Can checking only !acrtc->base.enabled turn vblank back on for a CRTC that
is being disabled or put into DPMS off by a concurrent atomic commit?
CPU0 (DPMS-off commit) CPU1 (GPU reset)
amdgpu_dm_atomic_commit_tail()
amdgpu_dm_commit_streams()
manage_dm_interrupts(..., NULL)
drm_crtc_vblank_off()
dm_suspend()
mutex_lock(&dm->dc_lock)
mutex_lock(&dm->dc_lock)
/* blocks */
dm_resume()
dm_gpureset_toggle_vblank(..., true)
/* acrtc->base.enabled is true */
drm_crtc_vblank_on_config()
mutex_unlock(&dm->dc_lock)
...
remove_stream()
acrtc->otg_inst = -1; <-- vblank left on with no stream
For a DPMS-off commit (new_crtc_state->enable is true,
new_crtc_state->active is false), amdgpu_dm_commit_streams() calls
manage_dm_interrupts(adev, acrtc, NULL) before taking dm->dc_lock to
disable the cursor and clear acrtc->otg_inst. Because acrtc->base.enabled
remains true while acrtc->base.state->active is false,
dm_gpureset_toggle_vblank() turns vblank back on, and the resuming commit
then removes the stream without calling drm_crtc_vblank_off() again.
Also, if a CRTC-disabling commit has swapped state in
drm_atomic_helper_swap_state() but has not yet reached
drm_atomic_helper_update_legacy_modeset_state(), acrtc->base.enabled is
still true while acrtc->base.state->adjusted_mode.crtc_clock is 0, causing
drm_calc_timestamping_constants() to log a dotclock = 0 error and
drm_crtc_vblank_on_config() to warn.
[ ... ]
> @@ -1628,6 +1678,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block)
> return -ENOMEM;
> }
>
> + dm_gpureset_toggle_vblank(adev, dm->cached_dc_state, false);
> dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, false);
>
> res = amdgpu_dm_commit_zero_streams(dm->dc);
[Severity: Medium]
If amdgpu_dm_commit_zero_streams() fails just below, does the error path
leave DRM vblank turned off?
The failure branch releases dm->cached_dc_state and returns -EINVAL without
re-enabling vblank (or interrupts), and amdgpu_device_ip_resume_phase3()
will skip dm_resume() because ip_block->status.hw was not cleared.
> @@ -1967,6 +2018,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
> dm_gpureset_commit_state(dm->cached_dc_state, dm);
>
> dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, true);
> + dm_gpureset_toggle_vblank(adev, dm->cached_dc_state, true);
>
> dc_state_release(dm->cached_dc_state);
> dm->cached_dc_state = NULL;
[Severity: High]
Does drm_crtc_vblank_on_config() in dm_gpureset_toggle_vblank() fail to
reset vblank->last to the post-reset hardware frame counter while
adev->reset_domain->sem is write-locked?
dm_resume() runs in task context while the reset thread holds
down_write(&adev->reset_domain->sem). When drm_crtc_vblank_on_config()
calls drm_reset_vblank_timestamp() -> amdgpu_get_vblank_counter_kms(),
down_read_trylock(&adev->reset_domain->sem) fails:
drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c:amdgpu_get_vblank_counter_kms() {
...
if (!down_read_trylock(&adev->reset_domain->sem))
return drm_crtc_vblank_count(crtc);
...
}
Instead of sampling the hardware OTG frame counter (which reset to 0 when
the streams were recommitted), drm_reset_vblank_timestamp() records the
pre-reset counter in vblank->last (even with commit "drm/amdgpu: keep
reading the vblank counter in IRQ context during reset", which only reads
the hardware counter when !in_task()). On the first vblank after the
reset, drm_update_vblank_count() sees cur_vblank < vblank->last and wraps
modulo max_vblank_count (0x00ffffff), jumping the DRM vblank counter
forward by up to 16.7 million frames.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261111085534.514957-1-Prike.Liang@amd.com?part=4
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH RESEND 5/5] drm/amdgpu: keep reading the vblank counter in IRQ context during reset
2026-11-11 8:55 [PATCH RESEND 1/5] drm/client: Make the FBIO_WAITFORVSYNC vblank wait interruptible Prike Liang
` (2 preceding siblings ...)
2026-11-11 8:55 ` [PATCH RESEND 4/5] drm/amd/display: turn vblank off in DRM across GPU reset Prike Liang
@ 2026-11-11 8:55 ` Prike Liang
2026-11-11 9:28 ` sashiko-bot
2026-11-12 2:52 ` vitaly prosyak
3 siblings, 2 replies; 8+ messages in thread
From: Prike Liang @ 2026-11-11 8:55 UTC (permalink / raw)
To: amd-gfx, dri-devel
Cc: Alexander.Deucher, Christian.Koenig, Harry.Wentland, Sunpeng.Li,
tzimmermann, Prike Liang, Vitaly Prosyak, James Lin, Leo Li
amdgpu_get_vblank_counter_kms() returns the cached vblank->last instead
of reading the hardware counter whenever it can't take
reset_domain->sem for reading. The vblank interrupt reads the counter
through the same callback via drm_crtc_handle_vblank(), so once GPU
recovery holds the semaphore for writing, every vblank interrupt sees
diff == 0 and the DRM vblank count stops advancing. The CRTCs keep
scanning out until dm_suspend() turns vblank off and stops the streams,
though, which only happens after the rest of the recovery preparation.
Vblank waits in that window don't see a vblank anymore. They only end
when dm_suspend() turns vblank off, or after a second with the
"vblank wait timed out" warning if that takes longer. The fbdev damage
worker is likely to be waiting already, because recovery prints
"GPU reset begin!" right before it takes the semaphore.
Vblank interrupts only arrive while a CRTC is scanning out, so the
interrupt path can read the hardware counter.
amdgpu_device_skip_hw_access() likewise only checks the reset semaphore
in task context. Only back off in task context, so the interrupt path
keeps the count moving until DM turns vblank off.
Fixes: f2bc71d16e73 ("drm/amdgpu: serialize vblank counter reads against GPU reset")
Cc: Vitaly Prosyak <vitaly.prosyak@amd.com>
Cc: James Lin <pinglei.lin@amd.com>
Cc: Leo Li <sunpeng.li@amd.com>
Assisted-by: Copilot:claude-opus-5.5
Signed-off-by: Prike Liang <Prike.Liang@amd.com>
---
drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 25 ++++++++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
index 14a72a406d37..06e539c4c3ea 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
@@ -1692,6 +1692,7 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc)
struct drm_device *dev = crtc->dev;
unsigned int pipe = crtc->index;
struct amdgpu_device *adev = drm_to_adev(dev);
+ bool locked = false;
int vpos, hpos, stat;
u32 count;
@@ -1699,9 +1700,26 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc)
DRM_ERROR("Invalid crtc %u\n", pipe);
return -EINVAL;
}
- if (!down_read_trylock(&adev->reset_domain->sem))
- return drm_crtc_vblank_count(crtc);
+ /*
+ * A GPU reset is in progress and the display hardware cannot be read
+ * safely. Return the hardware counter value DRM already cached rather
+ * than the software vblank count: the core feeds this callback's
+ * result back into vblank->last, and a software count lives in a
+ * different numeric domain, which later trips
+ * drm_WARN_ON_ONCE(cur_vblank != vblank->last) in
+ * drm_update_vblank_count().
+ *
+ * Only task context has to back off. Vblank interrupts only arrive
+ * while a CRTC is scanning out, so the interrupt path can read the
+ * hardware, and it has to keep counting: vblank waits that already
+ * sleep when a reset takes the semaphore never complete otherwise.
+ */
+ if (in_task()) {
+ if (!down_read_trylock(&adev->reset_domain->sem))
+ return drm_crtc_vblank_crtc(crtc)->last;
+ locked = true;
+ }
/* The hw increments its frame counter at start of vsync, not at start
* of vblank, as is required by DRM core vblank counter handling.
@@ -1747,7 +1765,8 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc)
DRM_DEBUG_VBL("NULL mode info! Returned count may be wrong.\n");
}
- up_read(&adev->reset_domain->sem);
+ if (locked)
+ up_read(&adev->reset_domain->sem);
return count;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH RESEND 5/5] drm/amdgpu: keep reading the vblank counter in IRQ context during reset
2026-11-11 8:55 ` [PATCH RESEND 5/5] drm/amdgpu: keep reading the vblank counter in IRQ context during reset Prike Liang
@ 2026-11-11 9:28 ` sashiko-bot
2026-11-12 2:52 ` vitaly prosyak
1 sibling, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-11-11 9:28 UTC (permalink / raw)
To: Prike Liang, dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] drm/amdgpu: vblank counter jump after reset in
amdgpu_get_vblank_counter_kms()
- [Low] drm/amdgpu: commit message and Fixes SHA mismatch in
amdgpu_get_vblank_counter_kms()
--
commit 6af07d5a30227ace169063264412acdd7272d49f
Author: Prike Liang <Prike.Liang@amd.com>
> drm/amdgpu: keep reading the vblank counter in IRQ context during reset
>
> amdgpu_get_vblank_counter_kms() returns the cached vblank->last instead
> of reading the hardware counter whenever it can't take
> reset_domain->sem for reading. The vblank interrupt reads the counter
> through the same callback via drm_crtc_handle_vblank(), so once GPU
> recovery holds the semaphore for writing, every vblank interrupt sees
> diff == 0 and the DRM vblank count stops advancing. The CRTCs keep
> scanning out until dm_suspend() turns vblank off and stops the streams,
> though, which only happens after the rest of the recovery preparation.
>
> Vblank waits in that window don't see a vblank anymore. They only end
> when dm_suspend() turns vblank off, or after a second with the
> "vblank wait timed out" warning if that takes longer. The fbdev damage
> worker is likely to be waiting already, because recovery prints
> "GPU reset begin!" right before it takes the semaphore.
>
> Vblank interrupts only arrive while a CRTC is scanning out, so the
> interrupt path can read the hardware counter.
> amdgpu_device_skip_hw_access() likewise only checks the reset semaphore
> in task context. Only back off in task context, so the interrupt path
> keeps the count moving until DM turns vblank off.
>
> Fixes: f2bc71d16e73 ("drm/amdgpu: serialize vblank counter reads against GPU reset")
> Cc: Vitaly Prosyak <vitaly.prosyak@amd.com>
> Cc: James Lin <pinglei.lin@amd.com>
> Cc: Leo Li <sunpeng.li@amd.com>
> Assisted-by: Copilot:claude-opus-5.5
> Signed-off-by: Prike Liang <Prike.Liang@amd.com>
[Severity: Low]
Prior to this patch, amdgpu_get_vblank_counter_kms() returned
drm_crtc_vblank_count(crtc) rather than drm_crtc_vblank_crtc(crtc)->last
when down_read_trylock() failed. Should the commit message mention
changing that fallback from the software counter to vblank->last?
Also, f2bc71d16e73 ("drm/amdgpu: serialize vblank counter reads against
GPU reset") is not an ancestor in this tree's history, where that commit
is 51a4359f7431 ("drm/amdgpu: serialize vblank counter reads against GPU
reset").
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> index 8e7e522a89e42..07d178031abb5 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> @@ -1691,6 +1691,7 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc)
> struct drm_device *dev = crtc->dev;
> unsigned int pipe = crtc->index;
> struct amdgpu_device *adev = drm_to_adev(dev);
> + bool locked = false;
> int vpos, hpos, stat;
> u32 count;
>
> @@ -1698,9 +1699,26 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc)
> DRM_ERROR("Invalid crtc %u\n", pipe);
> return -EINVAL;
> }
> - if (!down_read_trylock(&adev->reset_domain->sem))
> - return drm_crtc_vblank_count(crtc);
>
> + /*
> + * A GPU reset is in progress and the display hardware cannot be read
> + * safely. Return the hardware counter value DRM already cached rather
> + * than the software vblank count: the core feeds this callback's
> + * result back into vblank->last, and a software count lives in a
> + * different numeric domain, which later trips
> + * drm_WARN_ON_ONCE(cur_vblank != vblank->last) in
> + * drm_update_vblank_count().
> + *
> + * Only task context has to back off. Vblank interrupts only arrive
> + * while a CRTC is scanning out, so the interrupt path can read the
> + * hardware, and it has to keep counting: vblank waits that already
> + * sleep when a reset takes the semaphore never complete otherwise.
> + */
> + if (in_task()) {
> + if (!down_read_trylock(&adev->reset_domain->sem))
> + return drm_crtc_vblank_crtc(crtc)->last;
> + locked = true;
> + }
[Severity: Medium]
Does returning vblank->last here prevent dm_resume() from re-baselining
vblank->last after an ASIC reset, causing vblank->count to jump forward by
up to ~16.7 million frames?
During GPU recovery, amdgpu_device_gpu_recover() holds reset_domain->sem
for writing on the recovery worker thread (where in_task() is true) across
amdgpu_device_asic_reset(). The ASIC reset zeroes the 24-bit hardware
frame counter, and dm_resume() then runs:
dm_resume()
-> dm_gpureset_toggle_vblank()
-> drm_crtc_vblank_on_config()
-> drm_reset_vblank_timestamp()
-> get_vblank_counter_and_timestamp()
-> amdgpu_get_vblank_counter_kms()
Because the recovery thread itself holds the write lock on
reset_domain->sem, down_read_trylock() fails and returns the stale
pre-reset vblank->last instead of reading the reset hardware counter (0).
drm_reset_vblank_timestamp() stores that stale pre-reset value back into
vblank->last. On the first post-reset vblank interrupt,
drm_update_vblank_count() computes:
diff = (cur_vblank - vblank->last) & max_vblank_count;
With cur_vblank near 0 and vblank->last still holding the pre-reset count,
this wraps modulo max_vblank_count (0x00ffffff) and advances the DRM
vblank counter by millions of bogus frames (and similarly stops
dm_suspend() -> drm_crtc_vblank_off() from sampling the final pre-reset
hardware counter). Unlike amdgpu_device_skip_hw_access(), which allows
access when the current task holds the write lock on reset_domain->sem,
down_read_trylock() also rejects the reset thread itself.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261111085534.514957-1-Prike.Liang@amd.com?part=5
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH RESEND 5/5] drm/amdgpu: keep reading the vblank counter in IRQ context during reset
2026-11-11 8:55 ` [PATCH RESEND 5/5] drm/amdgpu: keep reading the vblank counter in IRQ context during reset Prike Liang
2026-11-11 9:28 ` sashiko-bot
@ 2026-11-12 2:52 ` vitaly prosyak
1 sibling, 0 replies; 8+ messages in thread
From: vitaly prosyak @ 2026-11-12 2:52 UTC (permalink / raw)
To: Prike Liang, amd-gfx, dri-devel
Cc: Alexander.Deucher, Christian.Koenig, Harry.Wentland, Sunpeng.Li,
tzimmermann, Vitaly Prosyak, James Lin
Hi Prike,
Thanks for the fix. Would !in_hardirq() be better than in_task() here?
The vblank-disable timer also reaches this callback via
vblank_disable_fn() -> drm_vblank_disable_and_save() ->
drm_update_vblank_count(). In softirq context, in_task() is false,
so the proposed check also bypasses the reset semaphore for timer reads.
Using !in_hardirq() would protect that path while preserving the hard-IRQ
bypass.
What do you think?
Thanks, Vitaly
On 2026-11-11 03:55, Prike Liang wrote:
> amdgpu_get_vblank_counter_kms() returns the cached vblank->last instead
> of reading the hardware counter whenever it can't take
> reset_domain->sem for reading. The vblank interrupt reads the counter
> through the same callback via drm_crtc_handle_vblank(), so once GPU
> recovery holds the semaphore for writing, every vblank interrupt sees
> diff == 0 and the DRM vblank count stops advancing. The CRTCs keep
> scanning out until dm_suspend() turns vblank off and stops the streams,
> though, which only happens after the rest of the recovery preparation.
>
> Vblank waits in that window don't see a vblank anymore. They only end
> when dm_suspend() turns vblank off, or after a second with the
> "vblank wait timed out" warning if that takes longer. The fbdev damage
> worker is likely to be waiting already, because recovery prints
> "GPU reset begin!" right before it takes the semaphore.
>
> Vblank interrupts only arrive while a CRTC is scanning out, so the
> interrupt path can read the hardware counter.
> amdgpu_device_skip_hw_access() likewise only checks the reset semaphore
> in task context. Only back off in task context, so the interrupt path
> keeps the count moving until DM turns vblank off.
>
> Fixes: f2bc71d16e73 ("drm/amdgpu: serialize vblank counter reads against GPU reset")
> Cc: Vitaly Prosyak <vitaly.prosyak@amd.com>
> Cc: James Lin <pinglei.lin@amd.com>
> Cc: Leo Li <sunpeng.li@amd.com>
> Assisted-by: Copilot:claude-opus-5.5
> Signed-off-by: Prike Liang <Prike.Liang@amd.com>
> ---
> drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 25 ++++++++++++++++++++++---
> 1 file changed, 22 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> index 14a72a406d37..06e539c4c3ea 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> @@ -1692,6 +1692,7 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc)
> struct drm_device *dev = crtc->dev;
> unsigned int pipe = crtc->index;
> struct amdgpu_device *adev = drm_to_adev(dev);
> + bool locked = false;
> int vpos, hpos, stat;
> u32 count;
>
> @@ -1699,9 +1700,26 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc)
> DRM_ERROR("Invalid crtc %u\n", pipe);
> return -EINVAL;
> }
> - if (!down_read_trylock(&adev->reset_domain->sem))
> - return drm_crtc_vblank_count(crtc);
>
> + /*
> + * A GPU reset is in progress and the display hardware cannot be read
> + * safely. Return the hardware counter value DRM already cached rather
> + * than the software vblank count: the core feeds this callback's
> + * result back into vblank->last, and a software count lives in a
> + * different numeric domain, which later trips
> + * drm_WARN_ON_ONCE(cur_vblank != vblank->last) in
> + * drm_update_vblank_count().
> + *
> + * Only task context has to back off. Vblank interrupts only arrive
> + * while a CRTC is scanning out, so the interrupt path can read the
> + * hardware, and it has to keep counting: vblank waits that already
> + * sleep when a reset takes the semaphore never complete otherwise.
> + */
> + if (in_task()) {
> + if (!down_read_trylock(&adev->reset_domain->sem))
> + return drm_crtc_vblank_crtc(crtc)->last;
> + locked = true;
> + }
>
> /* The hw increments its frame counter at start of vsync, not at start
> * of vblank, as is required by DRM core vblank counter handling.
> @@ -1747,7 +1765,8 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc)
> DRM_DEBUG_VBL("NULL mode info! Returned count may be wrong.\n");
> }
>
> - up_read(&adev->reset_domain->sem);
> + if (locked)
> + up_read(&adev->reset_domain->sem);
> return count;
> }
>
^ permalink raw reply [flat|nested] 8+ messages in thread