From: Alex Hung <[email protected]>

[WHAT]
Add KUnit tests for dm_dmub_outbox1_low_irq() draining a trace ring up
to the DMUB_TRACE_MAX_READ cap, dm_handle_hpd_work() rejecting a work
item with no notification, and amdgpu_dm_irq_schedule_work() not
queueing once the IRQ workqueue is gone.

[HOW]
Back dmub->outbox0_rb with a fake ring whose write pointer sits one
entry past the cap, and keep the ring larger so the read pointer does
not wrap. dm_handle_hpd_work() returns before its own kfree() on the
NULL-notify path, so the test frees the work item.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Alex Hung <[email protected]>
Signed-off-by: Ivan Lipski <[email protected]>
---
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 110 ++++++++++++++++++
 1 file changed, 110 insertions(+)

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 4f22230a5520e..933a33a360fbe 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
@@ -220,6 +220,18 @@ static uint32_t dm_test_dmub_get_outbox0_wptr(struct 
dmub_srv *dmub)
        return 0;
 }
 
+/* One more than DMUB_TRACE_MAX_READ, so the drain loop reaches its cap. */
+#define DM_TEST_OUTBOX0_TRACE_ENTRIES  65
+#define DM_TEST_OUTBOX0_TRACE_BYTES \
+       (DM_TEST_OUTBOX0_TRACE_ENTRIES * sizeof(struct dmcub_trace_buf_entry))
+/* Keep the ring larger than the write pointer so rptr does not wrap. */
+#define DM_TEST_OUTBOX0_RB_SIZE                (2 * 
DM_TEST_OUTBOX0_TRACE_BYTES)
+
+static uint32_t dm_test_dmub_get_outbox0_wptr_full(struct dmub_srv *dmub)
+{
+       return DM_TEST_OUTBOX0_TRACE_BYTES;
+}
+
 static uint32_t dm_test_dmub_get_outbox1_wptr(struct dmub_srv *dmub)
 {
        return 0;
@@ -1941,6 +1953,39 @@ static void 
dm_test_irq_schedule_work_requeue_fallback(struct kunit *test)
        amdgpu_dm_irq_fini(adev);
 }
 
+/**
+ * dm_test_irq_schedule_work_no_workqueue - Test schedule work after wq 
teardown
+ * @test: The KUnit test context
+ *
+ * An interrupt that races DM teardown can reach the scheduler after the IRQ
+ * workqueue is gone, so a registered handler must simply not be queued.
+ */
+static void dm_test_irq_schedule_work_no_workqueue(struct kunit *test)
+{
+       struct dc_interrupt_params int_params = { 0 };
+       struct amdgpu_device *adev;
+       int count = 0;
+       void *handler;
+
+       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);
+
+       int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+       int_params.irq_source = DC_IRQ_SOURCE_HPD1;
+       handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+                                                  dm_test_irq_handler_count, 
&count);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+       destroy_workqueue(adev->dm.irq_wq);
+       adev->dm.irq_wq = NULL;
+
+       amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1);
+       KUNIT_EXPECT_EQ(test, count, 0);
+
+       amdgpu_dm_irq_fini(adev);
+}
+
 /* Tests for amdgpu_dm_set_hpd_irq_state() */
 
 /**
@@ -3938,6 +3983,28 @@ static void dm_test_handle_hpd_work_out_of_range(struct 
kunit *test)
        dm_handle_hpd_work(&hpd_work->handle_hpd_work);
 }
 
+/**
+ * dm_test_handle_hpd_work_null_notify - Test HPD work with no notification
+ * @test: The KUnit test context
+ *
+ * A work item carrying no notification must be rejected before dispatch.
+ */
+static void dm_test_handle_hpd_work_null_notify(struct kunit *test)
+{
+       struct dmub_hpd_work *hpd_work;
+       struct amdgpu_device *adev;
+
+       adev = dm_kunit_alloc_adev(test);
+       hpd_work = kzalloc_obj(*hpd_work, GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hpd_work);
+       hpd_work->adev = adev;
+       INIT_WORK(&hpd_work->handle_hpd_work, dm_handle_hpd_work);
+
+       /* The handler bails out before its own kfree(), so free it here. */
+       dm_handle_hpd_work(&hpd_work->handle_hpd_work);
+       kfree(hpd_work);
+}
+
 /* Tests for dm_dmub_outbox1_low_irq() */
 
 /**
@@ -3969,6 +4036,46 @@ static void dm_test_dmub_outbox1_low_irq_empty(struct 
kunit *test)
        dm_dmub_outbox1_low_irq(&params);
 }
 
+/**
+ * dm_test_dmub_outbox1_low_irq_drains_trace - Test the trace drain loop
+ * @test: The KUnit test context
+ *
+ * A trace ring holding more entries than the handler reads in one pass must be
+ * drained up to the DMUB_TRACE_MAX_READ cap, leaving the read pointer parked
+ * after the last entry the handler consumed.
+ */
+static void dm_test_dmub_outbox1_low_irq_drains_trace(struct kunit *test)
+{
+       struct common_irq_params params = { 0 };
+       struct dc_dmub_srv *dc_dmub_srv;
+       struct amdgpu_device *adev;
+       struct dmub_srv *dmub;
+       struct dc *dc;
+       void *rb;
+
+       adev = dm_kunit_alloc_adev(test);
+       dc = dm_kunit_alloc_dc_with_ctx(test);
+       dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv);
+       dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub);
+       rb = kunit_kzalloc(test, DM_TEST_OUTBOX0_RB_SIZE, GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rb);
+
+       dmub->outbox0_rb.base_address = rb;
+       dmub->outbox0_rb.capacity = DM_TEST_OUTBOX0_RB_SIZE;
+       dmub->hw_funcs.get_outbox0_wptr = dm_test_dmub_get_outbox0_wptr_full;
+       dc_dmub_srv->dmub = dmub;
+       dc->ctx->dmub_srv = dc_dmub_srv;
+       adev->dm.dc = dc;
+       params.adev = adev;
+       params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX;
+
+       dm_dmub_outbox1_low_irq(&params);
+
+       KUNIT_EXPECT_EQ(test, dmub->outbox0_rb.rptr, 
DM_TEST_OUTBOX0_TRACE_BYTES);
+}
+
 /*
  * dm_test_alloc_adev_outbox_notify - Build an adev wired for DMUB outbox
  * notification handling.
@@ -4487,6 +4594,7 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
        KUNIT_CASE(dm_test_irq_schedule_work_empty),
        KUNIT_CASE(dm_test_irq_schedule_work_queues_handler),
        KUNIT_CASE(dm_test_irq_schedule_work_requeue_fallback),
+       KUNIT_CASE(dm_test_irq_schedule_work_no_workqueue),
        /* amdgpu_dm_set_hpd_irq_state */
        KUNIT_CASE(dm_test_set_hpd_irq_state_null_dc),
        /* amdgpu_dm_set_dmub_outbox_irq_state */
@@ -4555,8 +4663,10 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
        KUNIT_CASE(dm_test_crtc_high_irq_schedules_vmin_vmax),
        /* dm_handle_hpd_work */
        KUNIT_CASE(dm_test_handle_hpd_work_out_of_range),
+       KUNIT_CASE(dm_test_handle_hpd_work_null_notify),
        /* dm_dmub_outbox1_low_irq */
        KUNIT_CASE(dm_test_dmub_outbox1_low_irq_empty),
+       KUNIT_CASE(dm_test_dmub_outbox1_low_irq_drains_trace),
        KUNIT_CASE(dm_test_dmub_outbox1_low_irq_no_handler),
        KUNIT_CASE(dm_test_dmub_outbox1_low_irq_direct_callback),
        KUNIT_CASE(dm_test_dmub_outbox1_low_irq_offload),
-- 
2.43.0

Reply via email to