[PATCH 67/82] drm/amd/display: Test dm_dmub_outbox1_low_irq drain and work guards

<[email protected]>
Newsgroups org.freedesktop.lists.amd-gfx
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.