[PATCH 15/41] drm/amd/display: Add vblank handling tests for crtc
<[email protected]> Fri, 31 Jul 2026 17:12:36 -0400
| Newsgroups | org.freedesktop.lists.amd-gfx |
|---|---|
| Message-ID | <[email protected]> |
From: Bhawanpreet Lakha <[email protected]> Add KUnit coverage for the CRTC vblank paths: - amdgpu_dm_crtc_handle_vblank: no-event completion and the AMDGPU_FLIP_SUBMITTED guard that keeps a pending event pending. - amdgpu_dm_crtc_vblank_control_worker: enable increments, disable decrements, and disable clamps the active vblank IRQ count at zero. - amdgpu_dm_crtc_disable_vblank: disable path returns cleanly when the IRQ subsystem is not installed. Expose amdgpu_dm_crtc_vblank_control_worker for KUnit via STATIC_IFN_KUNIT/EXPORT_IF_KUNIT and declare it in the header. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung <[email protected]> Signed-off-by: Bhawanpreet Lakha <[email protected]> Signed-off-by: Roman Li <[email protected]> --- .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 5 +- .../amd/display/amdgpu_dm/amdgpu_dm_crtc.h | 1 + .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 213 ++++++++++++++++++ 3 files changed, 218 insertions(+), 1 deletion(-) 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 ce42102109bb..2a4e241350f0 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 @@ -59,6 +59,7 @@ void amdgpu_dm_crtc_handle_vblank(struct amdgpu_crtc *acrtc) spin_unlock_irqrestore(&dev->event_lock, flags); } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_handle_vblank); bool amdgpu_dm_crtc_modeset_required(struct drm_crtc_state *crtc_state, struct dc_stream_state *new_stream, @@ -216,7 +217,7 @@ struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev) } EXPORT_IF_KUNIT(idle_create_workqueue); -static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work) +STATIC_IFN_KUNIT void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work) { struct vblank_control_work *vblank_work = container_of(work, struct vblank_control_work, work); @@ -241,6 +242,7 @@ static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work) kfree(vblank_work); } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_vblank_control_worker); static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) { @@ -400,6 +402,7 @@ void amdgpu_dm_crtc_disable_vblank(struct drm_crtc *crtc) { amdgpu_dm_crtc_set_vblank(crtc, false); } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_disable_vblank); STATIC_IFN_KUNIT void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *state) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h index ed6e8021d69e..c68b7b2e1916 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h @@ -52,6 +52,7 @@ struct drm_crtc_state *amdgpu_dm_crtc_duplicate_state(struct drm_crtc *crtc); void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc); void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc, struct drm_crtc_state *new_crtc_state); +void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work); #endif bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index ce20692959d0..4d4b4061c789 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -9,6 +9,7 @@ #include <drm/drm_atomic.h> #include <drm/drm_connector.h> #include <drm/drm_kunit_helpers.h> +#include <drm/drm_vblank.h> #include "dc.h" #include "amdgpu.h" @@ -621,6 +622,209 @@ static void dm_test_crtc_destroy_state_no_stream(struct kunit *test) amdgpu_dm_crtc_destroy_state(NULL, &dm_state->base); } +/** + * dm_test_crtc_handle_vblank_no_event - Test vblank handling with no pending event + * @test: The KUnit test context + * + * With no flip event pending, handling a vblank must complete without sending a + * vblank event and must leave acrtc->event untouched (NULL). + */ +static void dm_test_crtc_handle_vblank_no_event(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* Initialise vblank so drm_crtc_handle_vblank() runs cleanly. */ + KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + acrtc->base.dev = &adev->ddev; + acrtc->event = NULL; + + amdgpu_dm_crtc_handle_vblank(acrtc); + + KUNIT_EXPECT_NULL(test, acrtc->event); +} + +/** + * dm_test_crtc_handle_vblank_skips_when_flip_submitted - Test event kept on submit + * @test: The KUnit test context + * + * A pending event whose flip is still AMDGPU_FLIP_SUBMITTED must not be signalled + * on vblank; acrtc->event must remain set for later completion. + */ +static void dm_test_crtc_handle_vblank_skips_when_flip_submitted(struct kunit *test) +{ + struct drm_pending_vblank_event *event; + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + event = kunit_kzalloc(test, sizeof(*event), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + acrtc->base.dev = &adev->ddev; + acrtc->event = event; + acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; + + amdgpu_dm_crtc_handle_vblank(acrtc); + + /* Flip still in-flight: event must be preserved, not signalled. */ + KUNIT_EXPECT_PTR_EQ(test, acrtc->event, event); +} + +/** + * dm_test_vblank_control_worker_setup - Build a vblank_control_work for the worker + * @test: The KUnit test context + * @enable: Value for vblank_work->enable + * @count: Initial dm->active_vblank_irq_count + * + * Returns a work item wired to a freshly allocated adev/crtc/stream. The CRTC is + * left without an atomic state so amdgpu_dm_ism_commit_event() short-circuits and + * only the vblank IRQ accounting in the worker runs. + */ +static struct vblank_control_work * +dm_test_vblank_control_worker_setup(struct kunit *test, bool enable, + uint32_t count) +{ + struct dc_stream_state *stream; + struct vblank_control_work *work; + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + mutex_init(&adev->dm.dc_lock); + adev->dm.dc = dm_kunit_alloc_dc_with_ctx(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.dc); + adev->dm.active_vblank_irq_count = count; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + acrtc->base.dev = &adev->ddev; + acrtc->base.state = NULL; + + stream = dm_kunit_alloc_stream(test, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + /* Worker releases the stream; keep an extra ref so kunit owns the free. */ + kref_get(&stream->refcount); + + /* Worker kfree()s the work item, so it must be a plain allocation. */ + work = kzalloc_obj(*work, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, work); + work->dm = &adev->dm; + work->acrtc = acrtc; + work->stream = stream; + work->enable = enable; + + return work; +} + +/** + * dm_test_vblank_control_worker_enable_increments - Test enable bumps IRQ count + * @test: The KUnit test context + * + * Running the worker with enable set must increment the active vblank IRQ count. + */ +static void dm_test_vblank_control_worker_enable_increments(struct kunit *test) +{ + struct vblank_control_work *work; + struct amdgpu_display_manager *dm; + + work = dm_test_vblank_control_worker_setup(test, true, 0); + dm = work->dm; + + amdgpu_dm_crtc_vblank_control_worker(&work->work); + + KUNIT_EXPECT_EQ(test, dm->active_vblank_irq_count, 1); +} + +/** + * dm_test_vblank_control_worker_disable_decrements - Test disable drops IRQ count + * @test: The KUnit test context + * + * Running the worker with enable clear must decrement a non-zero active vblank + * IRQ count. + */ +static void dm_test_vblank_control_worker_disable_decrements(struct kunit *test) +{ + struct vblank_control_work *work; + struct amdgpu_display_manager *dm; + + work = dm_test_vblank_control_worker_setup(test, false, 2); + dm = work->dm; + + amdgpu_dm_crtc_vblank_control_worker(&work->work); + + KUNIT_EXPECT_EQ(test, dm->active_vblank_irq_count, 1); +} + +/** + * dm_test_vblank_control_worker_disable_clamps_zero - Test disable clamps at zero + * @test: The KUnit test context + * + * Disabling when the active vblank IRQ count is already zero must not underflow. + */ +static void dm_test_vblank_control_worker_disable_clamps_zero(struct kunit *test) +{ + struct vblank_control_work *work; + struct amdgpu_display_manager *dm; + + work = dm_test_vblank_control_worker_setup(test, false, 0); + dm = work->dm; + + amdgpu_dm_crtc_vblank_control_worker(&work->work); + + KUNIT_EXPECT_EQ(test, dm->active_vblank_irq_count, 0); +} + +/** + * dm_test_crtc_disable_vblank_no_irq_installed - Test disable with IRQ uninstalled + * @test: The KUnit test context + * + * Disabling vblank walks amdgpu_dm_crtc_set_vblank()'s disable path. With the + * IRQ subsystem not installed, amdgpu_irq_put() returns early so the routine + * completes without touching the vblank workqueue or the active IRQ count. + */ +static void dm_test_crtc_disable_vblank_no_irq_installed(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->dm.dc = dm_kunit_alloc_dc_with_ctx(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.dc); + /* DCE_VERSION_6_0 has no VRR, so the vupdate-irq branch is skipped. */ + adev->dm.dc->ctx->dce_version = DCE_VERSION_6_0; + adev->dm.active_vblank_irq_count = 0; + + /* No CRTCs registered and IRQs not installed -> irq_put returns early. */ + adev->mode_info.num_crtc = 0; + adev->irq.installed = false; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + acrtc->base.dev = &adev->ddev; + acrtc->crtc_id = 0; + + amdgpu_dm_crtc_disable_vblank(&acrtc->base); + + KUNIT_EXPECT_EQ(test, adev->dm.active_vblank_irq_count, 0); +} + static struct kunit_case amdgpu_dm_crtc_tests[] = { /* amdgpu_dm_crtc_modeset_required */ KUNIT_CASE(dm_test_crtc_modeset_required_active_mode_changed), @@ -665,6 +869,15 @@ static struct kunit_case amdgpu_dm_crtc_tests[] = { KUNIT_CASE(dm_test_crtc_reset_state_allocates_state), /* amdgpu_dm_crtc_destroy_state */ KUNIT_CASE(dm_test_crtc_destroy_state_no_stream), + /* amdgpu_dm_crtc_handle_vblank */ + KUNIT_CASE(dm_test_crtc_handle_vblank_no_event), + KUNIT_CASE(dm_test_crtc_handle_vblank_skips_when_flip_submitted), + /* amdgpu_dm_crtc_vblank_control_worker */ + KUNIT_CASE(dm_test_vblank_control_worker_enable_increments), + KUNIT_CASE(dm_test_vblank_control_worker_disable_decrements), + KUNIT_CASE(dm_test_vblank_control_worker_disable_clamps_zero), + /* amdgpu_dm_crtc_disable_vblank */ + KUNIT_CASE(dm_test_crtc_disable_vblank_no_irq_installed), {} }; -- 2.34.1