From: Alex Hung <[email protected]>

[WHAT]
Expand the amdgpu_dm_irq KUnit suite with the first batch of tests
covering IRQ source-function setup, suspend/resume of the IRQ
clients, and the HPD RX offload workqueue lifecycle.

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 |   7 +
 .../amdgpu_dm/tests/amdgpu_dm_irq_test.c      | 353 ++++++++++++++++++
 2 files changed, 360 insertions(+)

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 c5467f34c51f..ee74b212aebf 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
@@ -537,6 +537,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev)
        if (dev->mode_config.poll_enabled)
                drm_kms_helper_poll_disable(dev);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_irq_suspend);

 void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev)
 {
@@ -558,6 +559,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev)

        DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_early);

 void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev)
 {
@@ -586,6 +588,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev)
        if (dev->mode_config.poll_enabled)
                drm_kms_helper_poll_enable(dev);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_late);

 /*
  * amdgpu_dm_irq_schedule_work - schedule all work items registered for the
@@ -900,6 +903,8 @@ void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev)
        adev->hpd_irq.num_types = adev->mode_info.num_hpd;
        adev->hpd_irq.funcs = &dm_hpd_irq_funcs;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_set_irq_funcs);
+
 void amdgpu_dm_outbox_init(struct amdgpu_device *adev)
 {
        dc_interrupt_set(adev->dm.dc,
@@ -1187,6 +1192,7 @@ struct hpd_rx_irq_offload_work_queue 
*amdgpu_dm_hpd_rx_irq_create_workqueue(stru
        kfree(hpd_rx_offload_wq);
        return NULL;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_hpd_rx_irq_create_workqueue);

 void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm)
 {
@@ -1197,6 +1203,7 @@ void amdgpu_dm_hpd_rx_irq_work_suspend(struct 
amdgpu_display_manager *dm)
                        flush_workqueue(dm->hpd_rx_offload_wq[i].wq);
        }
 }
+EXPORT_IF_KUNIT(amdgpu_dm_hpd_rx_irq_work_suspend);

 STATIC_IFN_KUNIT bool are_sinks_equal(const struct dc_sink *sink1, const 
struct dc_sink *sink2)
 {
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 a73a6dd146d6..7ea6f93ca998 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
@@ -7,14 +7,23 @@

 #include <kunit/test.h>
 #include <drm/drm_kunit_helpers.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_probe_helper.h>

 #include "dc.h"
+#include "inc/core_types.h"
+#include "irq/irq_service.h"
 #include "amdgpu.h"
 #include "amdgpu_mode.h"
 #include "amdgpu_dm.h"
 #include "amdgpu_dm_irq.h"
 #include "amdgpu_dm_kunit_test_helpers.h"
+#include "dc_dmub_srv.h"
+#include "ivsrcid/ivsrcid_vislands30.h"
+#include "ivsrcid/dcn/irqsrcs_dcn_1_0.h"
+#include "link_service.h"
 #include "dmub/dmub_srv.h"
+#include "dal_asic_id.h"

 static void dm_test_irq_handler(void *arg)
 {
@@ -31,6 +40,22 @@ static void dm_test_crtc_list_del(void *data)
        list_del_init(&acrtc->base.head);
 }

+struct dm_test_hpd_rx_wq_ctx {
+       struct hpd_rx_irq_offload_work_queue *wq;
+       int count;
+};
+
+static void dm_test_destroy_hpd_rx_wq(void *data)
+{
+       struct dm_test_hpd_rx_wq_ctx *ctx = data;
+       int i;
+
+       for (i = 0; i < ctx->count; i++)
+               if (ctx->wq[i].wq)
+                       destroy_workqueue(ctx->wq[i].wq);
+       kfree(ctx->wq);
+}
+
 /* Tests for amdgpu_dm_hpd_to_dal_irq_source() */

 /**
@@ -842,6 +867,318 @@ static void 
dm_test_get_crtc_by_otg_inst_empty_list(struct kunit *test)
        KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 0));
 }

+/* Tests for amdgpu_dm_set_irq_funcs() */
+
+/**
+ * dm_test_set_irq_funcs - Test irq src funcs and counts are populated
+ * @test: The KUnit test context
+ */
+static void dm_test_set_irq_funcs(struct kunit *test)
+{
+       struct amdgpu_device *adev;
+
+       adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+       adev->mode_info.num_crtc = 6;
+       adev->mode_info.num_hpd = 4;
+
+       amdgpu_dm_set_irq_funcs(adev);
+
+       KUNIT_EXPECT_EQ(test, adev->crtc_irq.num_types, 6);
+       KUNIT_EXPECT_EQ(test, adev->vline0_irq.num_types, 6);
+       KUNIT_EXPECT_EQ(test, adev->vupdate_irq.num_types, 6);
+       KUNIT_EXPECT_EQ(test, adev->pageflip_irq.num_types, 6);
+       KUNIT_EXPECT_EQ(test, adev->dmub_outbox_irq.num_types, 1);
+       KUNIT_EXPECT_EQ(test, adev->dmub_trace_irq.num_types, 1);
+       KUNIT_EXPECT_EQ(test, adev->hpd_irq.num_types, 4);
+
+       KUNIT_EXPECT_TRUE(test, adev->crtc_irq.funcs != NULL);
+       KUNIT_EXPECT_TRUE(test, adev->vline0_irq.funcs != NULL);
+       KUNIT_EXPECT_TRUE(test, adev->dmub_outbox_irq.funcs != NULL);
+       KUNIT_EXPECT_TRUE(test, adev->vupdate_irq.funcs != NULL);
+       KUNIT_EXPECT_TRUE(test, adev->dmub_trace_irq.funcs != NULL);
+       KUNIT_EXPECT_TRUE(test, adev->pageflip_irq.funcs != NULL);
+       KUNIT_EXPECT_TRUE(test, adev->hpd_irq.funcs != NULL);
+}
+
+/* Tests for amdgpu_dm_irq_suspend()/resume_early()/resume_late() */
+
+/**
+ * dm_test_irq_suspend_empty - Test suspend walks empty handler tables safely
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_suspend_empty(struct kunit *test)
+{
+       struct amdgpu_device *adev;
+       int src;
+
+       adev = dm_kunit_alloc_adev(test);
+       KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+       /*
+        * With no registered handlers the HW dc_interrupt_set() calls are
+        * skipped, so suspend must complete without touching the (absent) DC.
+        */
+       amdgpu_dm_irq_suspend(adev);
+
+       for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) {
+               KUNIT_EXPECT_TRUE(test, 
list_empty(&adev->dm.irq_handler_list_low_tab[src]));
+               KUNIT_EXPECT_TRUE(test, 
list_empty(&adev->dm.irq_handler_list_high_tab[src]));
+       }
+}
+
+/**
+ * dm_test_irq_resume_early_empty - Test early resume walks empty tables safely
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_resume_early_empty(struct kunit *test)
+{
+       struct amdgpu_device *adev;
+       int src;
+
+       adev = dm_kunit_alloc_adev(test);
+       KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+       amdgpu_dm_irq_resume_early(adev);
+
+       for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++)
+               KUNIT_EXPECT_TRUE(test, 
list_empty(&adev->dm.irq_handler_list_high_tab[src]));
+}
+
+/**
+ * dm_test_irq_resume_late_empty - Test late resume walks empty tables safely
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_resume_late_empty(struct kunit *test)
+{
+       struct amdgpu_device *adev;
+       int src;
+
+       adev = dm_kunit_alloc_adev(test);
+       KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+       amdgpu_dm_irq_resume_late(adev);
+
+       for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++)
+               KUNIT_EXPECT_TRUE(test, 
list_empty(&adev->dm.irq_handler_list_low_tab[src]));
+}
+
+/**
+ * dm_test_irq_suspend_registered - Test suspend reaches the dc_interrupt_set 
path
+ * @test: The KUnit test context
+ *
+ * Registers a low-context HPD handler so the handler list is non-empty,
+ * forcing amdgpu_dm_irq_suspend() to call dc_interrupt_set() (NULL-safe with
+ * no DC) and flush_work() on the registered handler.
+ */
+static void dm_test_irq_suspend_registered(struct kunit *test)
+{
+       struct dc_interrupt_params int_params = { 0 };
+       struct amdgpu_device *adev;
+       void *handler;
+
+       adev = dm_kunit_alloc_adev(test);
+       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, adev);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+       amdgpu_dm_irq_suspend(adev);
+
+       amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1,
+                                          dm_test_irq_handler);
+}
+
+/**
+ * dm_test_irq_suspend_disables_polling - Test suspend disables KMS polling
+ * @test: The KUnit test context
+ *
+ * With KMS polling active, suspend must take the poll-disable branch.
+ * drm_kms_helper_poll_disable() only cancels the poll work; it leaves the
+ * poll_enabled flag set (cleared later by drm_kms_helper_poll_fini()).
+ */
+static void dm_test_irq_suspend_disables_polling(struct kunit *test)
+{
+       struct amdgpu_device *adev;
+
+       adev = dm_kunit_alloc_adev(test);
+       KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+       /* Enable KMS polling so suspend takes the poll-disable branch. */
+       drm_kms_helper_poll_init(&adev->ddev);
+       KUNIT_ASSERT_TRUE(test, adev->ddev.mode_config.poll_enabled);
+
+       amdgpu_dm_irq_suspend(adev);
+
+       KUNIT_EXPECT_TRUE(test, adev->ddev.mode_config.poll_enabled);
+
+       drm_kms_helper_poll_fini(&adev->ddev);
+}
+
+/**
+ * dm_test_irq_resume_early_registered - Test early resume reaches 
dc_interrupt_set
+ * @test: The KUnit test context
+ *
+ * Registers a low-context HPD RX handler so early resume calls
+ * dc_interrupt_set() for the short-pulse interrupt source.
+ */
+static void dm_test_irq_resume_early_registered(struct kunit *test)
+{
+       struct dc_interrupt_params int_params = { 0 };
+       struct amdgpu_device *adev;
+       void *handler;
+
+       adev = dm_kunit_alloc_adev(test);
+       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_HPD1RX;
+       handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+                                                  dm_test_irq_handler, adev);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+       amdgpu_dm_irq_resume_early(adev);
+
+       amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1RX,
+                                          dm_test_irq_handler);
+}
+
+/**
+ * dm_test_irq_resume_late_registered - Test late resume reaches 
dc_interrupt_set
+ * @test: The KUnit test context
+ *
+ * Registers a low-context HPD handler so late resume calls dc_interrupt_set()
+ * for the HPD interrupt source.
+ */
+static void dm_test_irq_resume_late_registered(struct kunit *test)
+{
+       struct dc_interrupt_params int_params = { 0 };
+       struct amdgpu_device *adev;
+       void *handler;
+
+       adev = dm_kunit_alloc_adev(test);
+       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, adev);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+       amdgpu_dm_irq_resume_late(adev);
+
+       amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1,
+                                          dm_test_irq_handler);
+}
+
+/**
+ * dm_test_irq_resume_late_enables_polling - Test late resume re-enables 
polling
+ * @test: The KUnit test context
+ *
+ * With KMS polling active, late resume must take the poll-enable branch and
+ * leave polling enabled.
+ */
+static void dm_test_irq_resume_late_enables_polling(struct kunit *test)
+{
+       struct amdgpu_device *adev;
+
+       adev = dm_kunit_alloc_adev(test);
+       KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+       /* Enable KMS polling so resume_late takes the poll-enable branch. */
+       drm_kms_helper_poll_init(&adev->ddev);
+       KUNIT_ASSERT_TRUE(test, adev->ddev.mode_config.poll_enabled);
+
+       amdgpu_dm_irq_resume_late(adev);
+
+       KUNIT_EXPECT_TRUE(test, adev->ddev.mode_config.poll_enabled);
+
+       drm_kms_helper_poll_fini(&adev->ddev);
+}
+
+/* Tests for amdgpu_dm_hpd_rx_irq_create_workqueue() */
+
+/**
+ * dm_test_hpd_rx_irq_create_workqueue - Test workqueue array creation
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_rx_irq_create_workqueue(struct kunit *test)
+{
+       struct dm_test_hpd_rx_wq_ctx *ctx;
+       struct amdgpu_device *adev;
+       struct dc *dc;
+       int i;
+
+       adev = dm_kunit_alloc_adev(test);
+
+       dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
+       dc->caps.max_links = 4;
+       adev->dm.dc = dc;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
+
+       ctx->wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev);
+       KUNIT_ASSERT_NOT_NULL(test, ctx->wq);
+       ctx->count = dc->caps.max_links;
+       KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, 
dm_test_destroy_hpd_rx_wq, ctx), 0);
+
+       for (i = 0; i < dc->caps.max_links; i++)
+               KUNIT_EXPECT_TRUE(test, ctx->wq[i].wq != NULL);
+}
+
+/* Tests for amdgpu_dm_hpd_rx_irq_work_suspend() */
+
+/**
+ * dm_test_hpd_rx_irq_work_suspend_null - Test suspend with no work queue
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_rx_irq_work_suspend_null(struct kunit *test)
+{
+       struct amdgpu_display_manager *dm;
+
+       dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm);
+
+       /* A NULL hpd_rx_offload_wq must be a safe no-op (DC untouched). */
+       amdgpu_dm_hpd_rx_irq_work_suspend(dm);
+}
+
+/**
+ * dm_test_hpd_rx_irq_work_suspend_flushes - Test suspend flushes queues
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_rx_irq_work_suspend_flushes(struct kunit *test)
+{
+       struct dm_test_hpd_rx_wq_ctx *ctx;
+       struct amdgpu_device *adev;
+       struct dc *dc;
+
+       adev = dm_kunit_alloc_adev(test);
+
+       dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
+       dc->caps.max_links = 2;
+       adev->dm.dc = dc;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
+
+       ctx->wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev);
+       KUNIT_ASSERT_NOT_NULL(test, ctx->wq);
+       ctx->count = dc->caps.max_links;
+       KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, 
dm_test_destroy_hpd_rx_wq, ctx), 0);
+
+       adev->dm.hpd_rx_offload_wq = ctx->wq;
+
+       amdgpu_dm_hpd_rx_irq_work_suspend(&adev->dm);
+}
+
 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),
@@ -894,6 +1231,22 @@ static struct kunit_case amdgpu_dm_irq_tests[] = {
        KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_match),
        KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_null),
        KUNIT_CASE(dm_test_get_crtc_by_otg_inst_empty_list),
+       /* amdgpu_dm_set_irq_funcs */
+       KUNIT_CASE(dm_test_set_irq_funcs),
+       /* amdgpu_dm_irq_suspend/resume_early/resume_late */
+       KUNIT_CASE(dm_test_irq_suspend_empty),
+       KUNIT_CASE(dm_test_irq_resume_early_empty),
+       KUNIT_CASE(dm_test_irq_resume_late_empty),
+       KUNIT_CASE(dm_test_irq_suspend_registered),
+       KUNIT_CASE(dm_test_irq_suspend_disables_polling),
+       KUNIT_CASE(dm_test_irq_resume_early_registered),
+       KUNIT_CASE(dm_test_irq_resume_late_registered),
+       KUNIT_CASE(dm_test_irq_resume_late_enables_polling),
+       /* amdgpu_dm_hpd_rx_irq_create_workqueue */
+       KUNIT_CASE(dm_test_hpd_rx_irq_create_workqueue),
+       /* amdgpu_dm_hpd_rx_irq_work_suspend */
+       KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_null),
+       KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_flushes),
        {}
 };

--
2.55.0

Reply via email to