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

Reply via email to