From: Bhawanpreet Lakha <[email protected]>

Expose amdgpu_dm_idle_worker() for KUnit and add tests covering the
disabled exit, both loop break paths, and the enable-body path. Add
dm_kunit_alloc_dc_state() and dm_kunit_alloc_clk_mgr() helpers to
support the new tests.

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    |   3 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_crtc.h    |   1 +
 .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c     | 181 ++++++++++++++++++
 .../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c |  29 +++
 .../tests/amdgpu_dm_kunit_test_helpers.h      |   3 +
 5 files changed, 216 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 2a4e241350f0..fd816be907c4 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
@@ -165,7 +165,7 @@ bool amdgpu_dm_is_headless(struct amdgpu_device *adev)
 }
 EXPORT_IF_KUNIT(amdgpu_dm_is_headless);
 
-static void amdgpu_dm_idle_worker(struct work_struct *work)
+STATIC_IFN_KUNIT void amdgpu_dm_idle_worker(struct work_struct *work)
 {
        struct idle_workqueue *idle_work;
 
@@ -199,6 +199,7 @@ static void amdgpu_dm_idle_worker(struct work_struct *work)
        }
        idle_work->dm->idle_workqueue->running = false;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_idle_worker);
 
 struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev)
 {
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 c68b7b2e1916..d1e2cb4efc54 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
@@ -53,6 +53,7 @@ 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);
+void amdgpu_dm_idle_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 4d4b4061c789..f5d9424171e8 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
@@ -457,6 +457,182 @@ static void dm_test_idle_create_workqueue(struct kunit 
*test)
        kfree(idle_work);
 }
 
+/**
+ * dm_test_idle_worker_disabled_clears_running - Test worker exits when 
disabled
+ * @test: The KUnit test context
+ *
+ * With the idle workqueue disabled, amdgpu_dm_idle_worker() must skip the idle
+ * optimization loop entirely and leave the shared running flag cleared.
+ */
+static void dm_test_idle_worker_disabled_clears_running(struct kunit *test)
+{
+       struct idle_workqueue *idle_work;
+       struct amdgpu_device *adev;
+
+       adev = dm_kunit_alloc_adev(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+       idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, idle_work);
+
+       idle_work->dm = &adev->dm;
+       idle_work->enable = false;
+       idle_work->running = true;
+       /* The worker toggles running through dm->idle_workqueue. */
+       adev->dm.idle_workqueue = idle_work;
+
+       amdgpu_dm_idle_worker(&idle_work->work);
+
+       KUNIT_EXPECT_FALSE(test, idle_work->running);
+}
+
+/**
+ * dm_test_idle_worker_enabled_breaks_when_idle_disallowed - Test loop 
entry/exit
+ * @test: The KUnit test context
+ *
+ * With the workqueue enabled but idle optimizations disallowed, the worker 
enters
+ * the detection loop once, takes the early break, and clears the running flag.
+ */
+static void dm_test_idle_worker_enabled_breaks_when_idle_disallowed(struct 
kunit *test)
+{
+       struct idle_workqueue *idle_work;
+       struct amdgpu_device *adev;
+
+       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);
+       /* First loop iteration breaks before any 
dc_allow_idle_optimizations(). */
+       adev->dm.dc->idle_optimizations_allowed = false;
+
+       idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, idle_work);
+
+       idle_work->dm = &adev->dm;
+       idle_work->enable = true;
+       adev->dm.idle_workqueue = idle_work;
+
+       amdgpu_dm_idle_worker(&idle_work->work);
+
+       KUNIT_EXPECT_FALSE(test, idle_work->running);
+}
+
+/**
+ * dm_test_idle_worker_enabled_breaks_when_not_headless - Test second break 
path
+ * @test: The KUnit test context
+ *
+ * With idle optimizations allowed, the worker passes the first branch and runs
+ * dc_allow_idle_optimizations(). A connected display makes the device 
non-headless
+ * while no PSR is active, so the worker takes the second break and stops 
running.
+ */
+static void dm_test_idle_worker_enabled_breaks_when_not_headless(struct kunit 
*test)
+{
+       struct idle_workqueue *idle_work;
+       struct drm_connector *display;
+       struct amdgpu_device *adev;
+
+       adev = dm_kunit_alloc_adev(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+       mutex_init(&adev->dm.dc_lock);
+       adev->dm.adev = adev;
+       adev->dm.ddev = dm_kunit_alloc_drm_with_connector_list(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.ddev);
+
+       adev->dm.dc = dm_kunit_alloc_dc_with_ctx(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.dc);
+       /* Allow idle so the first branch is skipped and dc_allow() is 
exercised. */
+       adev->dm.dc->idle_optimizations_allowed = true;
+       /* is_apu path avoids DC_LOG_DC()'s NULL-logger dereference. */
+       adev->dm.dc->caps.is_apu = true;
+       /* Empty stream list -> amdgpu_dm_psr_is_active_allowed() returns 
false. */
+       adev->dm.dc->current_state = dm_kunit_alloc_dc_state(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.dc->current_state);
+
+       /* A connected display makes amdgpu_dm_is_headless() false. */
+       display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display);
+       dm_test_add_connector(adev->dm.ddev, display, DRM_MODE_CONNECTOR_HDMIA,
+                             connector_status_connected);
+
+       idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, idle_work);
+       idle_work->dm = &adev->dm;
+       idle_work->enable = true;
+       adev->dm.idle_workqueue = idle_work;
+
+       amdgpu_dm_idle_worker(&idle_work->work);
+
+       KUNIT_EXPECT_FALSE(test, idle_work->running);
+}
+
+/*
+ * Report success only when disabling idle. dc_allow_idle_optimizations() then
+ * clears dc->idle_optimizations_allowed on the disable call but leaves it 
clear
+ * on the re-enable call inside the worker's enable-body, so the next loop
+ * iteration breaks at the first branch instead of looping forever.
+ */
+static bool dm_test_idle_apply_flip(struct dc *dc, bool enable)
+{
+       return !enable;
+}
+
+/**
+ * dm_test_idle_worker_enabled_runs_body - Test the enable-body path
+ * @test: The KUnit test context
+ *
+ * A headless device makes the second branch false so the worker runs the
+ * enable-body (dc_post_update_surfaces_to_stream() + re-enable). An injected
+ * hwss.apply_idle_power_optimizations() callback lets 
dc_allow_idle_optimizations()
+ * clear idle_optimizations_allowed on the disable half, so the following loop
+ * iteration breaks at the first branch and the worker stops.
+ */
+static void dm_test_idle_worker_enabled_runs_body(struct kunit *test)
+{
+       struct idle_workqueue *idle_work;
+       struct amdgpu_device *adev;
+       struct dal_logger *logger;
+
+       adev = dm_kunit_alloc_adev(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+       mutex_init(&adev->dm.dc_lock);
+       adev->dm.adev = adev;
+       /* Empty connector list keeps the device headless -> second branch 
false. */
+       adev->dm.ddev = dm_kunit_alloc_drm_with_connector_list(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.ddev);
+
+       adev->dm.dc = dm_kunit_alloc_dc_with_ctx(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.dc);
+       /* Allow idle so the first branch is skipped and dc_allow() is 
exercised. */
+       adev->dm.dc->idle_optimizations_allowed = true;
+       /* is_apu path avoids DC_LOG_DC()'s NULL-logger dereference. */
+       adev->dm.dc->caps.is_apu = true;
+       /* dc_allow() logs via DC_LOG_DEBUG() when it flips the flag. */
+       logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger);
+       logger->dev = &adev->ddev;
+       adev->dm.dc->ctx->logger = logger;
+       /* dc_allow() only flips the flag when clk_mgr and apply() are present. 
*/
+       adev->dm.dc->clk_mgr = dm_kunit_alloc_clk_mgr(test);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.dc->clk_mgr);
+       adev->dm.dc->hwss.apply_idle_power_optimizations = 
dm_test_idle_apply_flip;
+
+       idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, idle_work);
+       idle_work->dm = &adev->dm;
+       idle_work->enable = true;
+       adev->dm.idle_workqueue = idle_work;
+
+       amdgpu_dm_idle_worker(&idle_work->work);
+
+       /* Enable-body ran, then the next iteration disabled idle and stopped. 
*/
+       KUNIT_EXPECT_FALSE(test, adev->dm.dc->idle_optimizations_allowed);
+       KUNIT_EXPECT_FALSE(test, idle_work->running);
+}
+
 /* Tests for amdgpu_dm_crtc_set_static_screen_optimze() */
 
 /**
@@ -857,6 +1033,11 @@ static struct kunit_case amdgpu_dm_crtc_tests[] = {
        KUNIT_CASE(dm_test_crtc_set_vupdate_irq_no_otg),
        /* idle_create_workqueue */
        KUNIT_CASE(dm_test_idle_create_workqueue),
+       /* amdgpu_dm_idle_worker */
+       KUNIT_CASE(dm_test_idle_worker_disabled_clears_running),
+       KUNIT_CASE(dm_test_idle_worker_enabled_breaks_when_idle_disallowed),
+       KUNIT_CASE(dm_test_idle_worker_enabled_breaks_when_not_headless),
+       KUNIT_CASE(dm_test_idle_worker_enabled_runs_body),
        /* amdgpu_dm_crtc_set_static_screen_optimze */
        KUNIT_CASE(dm_test_crtc_set_static_screen_optimze_no_sr_entry),
        /* amdgpu_dm_crtc_enable_vblank */
diff --git 
a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c 
b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
index 862ada873fbb..aad122c7229f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
@@ -12,6 +12,7 @@
 
 #include "dc.h"
 #include "core_types.h"
+#include "clk_mgr.h"
 #include "amdgpu.h"
 #include "amdgpu_mode.h"
 #include "amdgpu_dm.h"
@@ -114,6 +115,34 @@ struct dc_stream_state *dm_kunit_alloc_stream(struct kunit 
*test,
 }
 EXPORT_SYMBOL(dm_kunit_alloc_stream);
 
+struct dc_state *dm_kunit_alloc_dc_state(struct kunit *test)
+{
+       struct dc_state *state;
+
+       state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, state);
+
+       return state;
+}
+EXPORT_SYMBOL(dm_kunit_alloc_dc_state);
+
+struct clk_mgr *dm_kunit_alloc_clk_mgr(struct kunit *test)
+{
+       struct clk_mgr *clk_mgr;
+       struct clk_mgr_funcs *funcs;
+
+       clk_mgr = kunit_kzalloc(test, sizeof(*clk_mgr), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, clk_mgr);
+
+       funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, funcs);
+
+       clk_mgr->funcs = funcs;
+
+       return clk_mgr;
+}
+EXPORT_SYMBOL(dm_kunit_alloc_clk_mgr);
+
 void dm_kunit_add_stream_to_state(struct kunit *test, struct dc_state *state,
                                  unsigned int index, struct dc_link *link)
 {
diff --git 
a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h 
b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h
index 49b358681d60..7d7bcd321775 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h
@@ -13,6 +13,7 @@
 struct amdgpu_device;
 struct amdgpu_display_manager;
 struct amdgpu_dm_connector;
+struct clk_mgr;
 struct dc;
 struct dc_link;
 struct dc_state;
@@ -25,6 +26,8 @@ struct dc_link *dm_kunit_alloc_link(struct kunit *test);
 struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test);
 struct amdgpu_display_manager *dm_kunit_alloc_dm(struct kunit *test);
 struct drm_device *dm_kunit_alloc_drm_with_connector_list(struct kunit *test);
+struct dc_state *dm_kunit_alloc_dc_state(struct kunit *test);
+struct clk_mgr *dm_kunit_alloc_clk_mgr(struct kunit *test);
 struct dc_stream_state *dm_kunit_alloc_stream(struct kunit *test,
                                              struct dc_link *link);
 void dm_kunit_add_stream_to_state(struct kunit *test, struct dc_state *state,
-- 
2.34.1

Reply via email to