From: Alex Hung <[email protected]>

[WHAT]
Add KUnit coverage for the two remaining uncovered functions in
amdgpu_dm_irq.c: amdgpu_dm_irq_handler() and dm_handle_vmin_vmax_update().

amdgpu_dm_irq_handler() is driven with a fake dc whose irq_service maps
the hardware IRQ entry to a DC source and whose per-source info table
lets dc_interrupt_ack() succeed; high- and low-context counting handlers
verify the immediate and scheduled dispatch paths.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Alex Hung <[email protected]>
Signed-off-by: George Zhang <[email protected]>
---
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c |   6 +-
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h |   5 +
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 125 ++++++++++++++++++
 3 files changed, 134 insertions(+), 2 deletions(-)

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 dab970e064ff..32caba06927a 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
@@ -679,7 +679,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work);
  * Calls all registered high irq work immediately, and schedules work for low
  * irq. The DM IRQ table is used to find the corresponding handlers.
  */
-static int amdgpu_dm_irq_handler(struct amdgpu_device *adev,
+STATIC_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev,
                                 struct amdgpu_irq_src *source,
                                 struct amdgpu_iv_entry *entry)
 {
@@ -699,6 +699,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev,

        return 0;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_irq_handler);

 STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned 
int type)
 {
@@ -1874,7 +1875,7 @@ STATIC_IFN_KUNIT void dm_pflip_high_irq(void 
*interrupt_params)
 }
 EXPORT_IF_KUNIT(dm_pflip_high_irq);

-static void dm_handle_vmin_vmax_update(struct work_struct *offload_work)
+STATIC_IFN_KUNIT void dm_handle_vmin_vmax_update(struct work_struct 
*offload_work)
 {
        struct vupdate_offload_work *work = container_of(offload_work, struct 
vupdate_offload_work, work);
        struct amdgpu_device *adev = work->adev;
@@ -1889,6 +1890,7 @@ static void dm_handle_vmin_vmax_update(struct work_struct 
*offload_work)
        kfree(work->adjust);
        kfree(work);
 }
+EXPORT_IF_KUNIT(dm_handle_vmin_vmax_update);

 static void schedule_dc_vmin_vmax(struct amdgpu_device *adev,
        struct dc_stream_state *stream,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h 
b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
index da698f224d85..4c200a9614a7 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
@@ -124,6 +124,7 @@ int amdgpu_dm_register_outbox_irq_handlers(struct 
amdgpu_device *adev);

 #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
 struct amdgpu_irq_src;
+struct amdgpu_iv_entry;
 enum amdgpu_interrupt_state;

 enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type);
@@ -178,6 +179,10 @@ void dm_vupdate_high_irq(void *interrupt_params);
 void dm_crtc_high_irq(void *interrupt_params);
 void dm_handle_hpd_work(struct work_struct *work);
 void dm_dmub_outbox1_low_irq(void *interrupt_params);
+int amdgpu_dm_irq_handler(struct amdgpu_device *adev,
+                         struct amdgpu_irq_src *source,
+                         struct amdgpu_iv_entry *entry);
+void dm_handle_vmin_vmax_update(struct work_struct *offload_work);
 #endif

 #endif /* __AMDGPU_DM_IRQ_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c 
b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c
index c356c2c836b4..5a4fecdf3135 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c
@@ -3802,6 +3802,127 @@ static void 
dm_test_register_outbox_irq_handlers_with_dmub(struct kunit *test)
        amdgpu_dm_irq_fini(adev);
 }

+/* Tests for amdgpu_dm_irq_handler() */
+
+/**
+ * dm_test_irq_handler_dispatches_work - Test the top-level IRQ handler
+ * @test: The KUnit test context
+ *
+ * amdgpu_dm_irq_handler() translates the hardware IRQ entry to a DC IRQ
+ * source, acknowledges it, then dispatches to the high-context (immediate)
+ * and low-context (scheduled) handler lists. A fake dc with a stubbed
+ * irq_service maps the source id to DC_IRQ_SOURCE_VBLANK1 and lets the ack
+ * succeed without touching hardware registers.
+ */
+static void dm_test_irq_handler_dispatches_work(struct kunit *test)
+{
+       struct dc_interrupt_params int_params = { 0 };
+       struct amdgpu_iv_entry entry = { 0 };
+       struct amdgpu_device *adev;
+       int high_count = 0;
+       int low_count = 0;
+       void *handler;
+       struct dc *dc;
+
+       adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+       KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+       dc = dm_test_alloc_dc_with_irq_service(test,
+                                              
&dm_test_irq_service_funcs_dce110);
+       adev->dm.dc = dc;
+
+       /* High-context (immediate) handler on VBLANK1. */
+       int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+       int_params.irq_source = DC_IRQ_SOURCE_VBLANK1;
+       handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+                                                  dm_test_irq_handler_count,
+                                                  &high_count);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+       /* Low-context (scheduled) handler on VBLANK1. */
+       int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+       handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+                                                  dm_test_irq_handler_count,
+                                                  &low_count);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+       /* src_id maps to DC_IRQ_SOURCE_VBLANK1 via the dce110 stub. */
+       entry.src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0;
+       entry.src_data[0] = 0;
+
+       KUNIT_EXPECT_EQ(test, amdgpu_dm_irq_handler(adev, NULL, &entry), 0);
+
+       /* High-context handler runs synchronously in-place. */
+       KUNIT_EXPECT_EQ(test, high_count, 1);
+
+       /*
+        * Low-context work runs asynchronously; amdgpu_dm_irq_fini() flushes
+        * each pending work item before freeing, so it has run afterwards.
+        */
+       amdgpu_dm_irq_fini(adev);
+       KUNIT_EXPECT_EQ(test, low_count, 1);
+}
+
+/* Tests for dm_handle_vmin_vmax_update() */
+
+/**
+ * dm_test_handle_vmin_vmax_update - Test the deferred vmin/vmax worker
+ * @test: The KUnit test context
+ *
+ * The worker applies the cached timing adjust to the stream via
+ * dc_stream_adjust_vmin_vmax(), drops the stream reference taken when the
+ * work was scheduled, and frees the work and its adjust copy. A fake dc with
+ * a current_state lets the adjust walk an empty pipe list without touching
+ * hardware. The work and adjust are kmalloc'd because the worker frees them.
+ */
+static void dm_test_handle_vmin_vmax_update(struct kunit *test)
+{
+       struct dc_crtc_timing_adjust *adjust;
+       struct vupdate_offload_work *work;
+       struct dc_stream_state *stream;
+       struct amdgpu_device *adev;
+       struct dc *dc;
+
+       adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+       mutex_init(&adev->dm.dc_lock);
+
+       dc = dm_test_alloc_dc_with_ctx(test);
+       dc->current_state = kunit_kzalloc(test, sizeof(*dc->current_state),
+                                         GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc->current_state);
+       adev->dm.dc = dc;
+
+       stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+       /*
+        * Start at two references: the worker's dc_stream_release() drops one,
+        * leaving the kunit-managed allocation intact (no kfree).
+        */
+       kref_init(&stream->refcount);
+       kref_get(&stream->refcount);
+
+       /* The worker kfree()s both, so they must come from the slab. */
+       work = kzalloc(sizeof(*work), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, work);
+       adjust = kzalloc(sizeof(*adjust), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adjust);
+
+       work->adev = adev;
+       work->stream = stream;
+       work->adjust = adjust;
+       adjust->v_total_min = 1000;
+       adjust->v_total_max = 1100;
+
+       dm_handle_vmin_vmax_update(&work->work);
+
+       /* The adjust was applied and one stream reference was dropped. */
+       KUNIT_EXPECT_EQ(test, stream->adjust.v_total_min, 1000);
+       KUNIT_EXPECT_EQ(test, stream->adjust.v_total_max, 1100);
+       KUNIT_EXPECT_EQ(test, kref_read(&stream->refcount), 1);
+}
+
 static struct kunit_case amdgpu_dm_irq_tests[] = {
        /* amdgpu_dm_hpd_to_dal_irq_source */
        KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1),
@@ -3958,6 +4079,10 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
        KUNIT_CASE(dm_test_dcn10_register_irq_handlers_one_crtc),
        KUNIT_CASE(dm_test_register_outbox_irq_handlers_without_dmub),
        KUNIT_CASE(dm_test_register_outbox_irq_handlers_with_dmub),
+       /* amdgpu_dm_irq_handler */
+       KUNIT_CASE(dm_test_irq_handler_dispatches_work),
+       /* dm_handle_vmin_vmax_update */
+       KUNIT_CASE(dm_test_handle_vmin_vmax_update),
        {}
 };

--
2.55.0

Reply via email to