[PATCH 02/59] drm/amd/display: Test ISM idle commit

Alex Hung <[email protected]>
Newsgroups org.freedesktop.lists.amd-gfx
Message-ID <[email protected]>
[WHAT]
Add KUnit tests for the three idle-optimization commit arms of the ISM
FSM: OPTIMIZED_IDLE, OPTIMIZED_IDLE_SSO and TIMER_ABORTED.

[HOW]
Record idle changes with a stub on
dc->hwss.apply_idle_power_optimizations() and read the outcome from
dc->idle_optimizations_allowed.

DC already owns that callback, so it is the natural injection point.
dc_allow_idle_optimizations() returns early when the requested state
already matches, so the stub runs only on a real transition and its
call count is the number of idle changes the FSM asked for. That count
is what separates the three arms, which otherwise all leave idle in
the same state.

The remaining two commit calls need no double:
dc_post_update_surfaces_to_stream() returns on !dc->optimized_required
and amdgpu_dm_crtc_set_static_screen_optimze() stops in the replay and
PSR event helpers when the link has neither feature enabled. Both are
already no-ops on a test dc, so a double would only add upkeep.

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

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c
index 6394b967ac3a..5b348ded482f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c
@@ -865,18 +865,14 @@ static struct amdgpu_crtc *alloc_test_acrtc(struct kunit *test,
 {
 	struct amdgpu_device *adev;
 	struct amdgpu_crtc *acrtc;
-	struct dc *dc;
 
 	adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
 	KUNIT_ASSERT_NOT_NULL(test, adev);
 
-	dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_NULL(test, dc);
-
 	acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
 	KUNIT_ASSERT_NOT_NULL(test, acrtc);
 
-	adev->dm.dc = dc;
+	adev->dm.dc = dm_kunit_alloc_dc_with_ctx(test);
 	adev->dm.ddev = &adev->ddev;
 	mutex_init(&adev->dm.dc_lock);
 
@@ -1190,6 +1186,194 @@ static void dm_test_ism_dispatch_optimized_idle_defers_sso(struct kunit *test)
 	amdgpu_dm_ism_fini(&acrtc->ism);
 }
 
+/*
+ * ===== Behavioural tests for dm_ism_commit_idle_optimization_state =====
+ *
+ * The commit path runs the real DC calls. dc_allow_idle_optimizations() only
+ * reaches hardware through dc->hwss.apply_idle_power_optimizations(), so the
+ * spy is installed there — DC's own seam — and the outcome is read back from
+ * dc->idle_optimizations_allowed. The callback fires only on an actual
+ * transition, so the call count is the number of idle state changes requested.
+ */
+
+static struct {
+	int calls;
+	bool last_allow;
+} dm_ism_test_idle;
+
+static bool dm_ism_test_apply_idle(struct dc *dc, bool allow)
+{
+	dm_ism_test_idle.calls++;
+	dm_ism_test_idle.last_allow = allow;
+
+	return true;
+}
+
+/*
+ * Initialise @acrtc's ISM against a stream with a non-NULL link (required by
+ * the static-screen arm) and make dc_allow_idle_optimizations() observable.
+ */
+static struct dc_stream_state *setup_commit_test(struct kunit *test,
+						 struct amdgpu_crtc *acrtc,
+						 struct amdgpu_dm_ism_config *config)
+{
+	struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
+	struct dc_stream_state *stream;
+	struct dm_crtc_state *dm_state;
+	struct dal_logger *logger;
+	struct dc *dc = adev->dm.dc;
+
+	stream = dm_kunit_alloc_stream(test, dm_kunit_alloc_link(test));
+	stream->timing.v_total = 1125;
+	stream->timing.h_total = 2200;
+	stream->timing.pix_clk_100hz = 1485000;
+
+	dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, dm_state);
+	dm_state->stream = stream;
+
+	logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, logger);
+	logger->dev = &adev->ddev;
+	dc->ctx->logger = logger;
+	/* is_apu path avoids DC_LOG_DC()'s NULL-logger dereference. */
+	dc->caps.is_apu = true;
+	/* dc_allow() only calls apply() when clk_mgr is present. */
+	dc->clk_mgr = dm_kunit_alloc_clk_mgr(test);
+	dc->hwss.apply_idle_power_optimizations = dm_ism_test_apply_idle;
+	dm_ism_test_idle = (typeof(dm_ism_test_idle)){ };
+
+	amdgpu_dm_ism_init(&acrtc->ism, config);
+	acrtc->base.state = &dm_state->base;
+
+	return stream;
+}
+
+/**
+ * dm_test_ism_commit_allows_idle_on_optimized_idle - OPTIMIZED_IDLE commits idle-allow
+ * @test: KUnit test context
+ *
+ * With idle-allow filtering disabled (delay 0) ENTER_IDLE_REQUESTED cascades
+ * FULL_POWER_RUNNING -> HYSTERESIS_WAITING -> OPTIMIZED_IDLE. Because
+ * sso_num_frames >= filter_num_frames the OPTIMIZED_IDLE arm commits the idle
+ * state. With no active vblank and no SSO the commit must request allow-idle
+ * exactly once, without a preceding disallow.
+ */
+static void dm_test_ism_commit_allows_idle_on_optimized_idle(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev);
+	struct amdgpu_dm_ism_config config = {
+		.filter_num_frames = 0,
+		.sso_num_frames = 2,
+	};
+
+	setup_commit_test(test, acrtc, &config);
+
+	scoped_guard(mutex, &adev->dm.dc_lock) {
+		amdgpu_dm_ism_commit_event(&acrtc->ism,
+					   DM_ISM_EVENT_ENTER_IDLE_REQUESTED);
+
+		KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state,
+				(int)DM_ISM_STATE_OPTIMIZED_IDLE);
+
+		/* vblank disabled + no SSO -> a single allow-idle transition. */
+		KUNIT_EXPECT_EQ(test, dm_ism_test_idle.calls, 1);
+		KUNIT_EXPECT_TRUE(test, dm_ism_test_idle.last_allow);
+		KUNIT_EXPECT_TRUE(test, adev->dm.dc->idle_optimizations_allowed);
+
+		cancel_delayed_work(&acrtc->ism.sso_delayed_work);
+	}
+
+	amdgpu_dm_ism_fini(&acrtc->ism);
+}
+
+/**
+ * dm_test_ism_commit_enables_sso_on_optimized_idle_sso - SSO arm re-arms idle
+ * @test: KUnit test context
+ *
+ * With both idle-allow and SSO delays disabled the FSM cascades through
+ * OPTIMIZED_IDLE into OPTIMIZED_IDLE_SSO, whose arm commits with
+ * allow_panel_sso = true. Engaging SSO must first disallow idle and then
+ * re-allow it, so starting from an idle-allowed DC yields two transitions.
+ */
+static void dm_test_ism_commit_enables_sso_on_optimized_idle_sso(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev);
+	struct amdgpu_dm_ism_config config = {
+		.filter_num_frames = 0,
+		.sso_num_frames = 0,
+	};
+
+	setup_commit_test(test, acrtc, &config);
+	adev->dm.dc->idle_optimizations_allowed = true;
+
+	scoped_guard(mutex, &adev->dm.dc_lock) {
+		amdgpu_dm_ism_commit_event(&acrtc->ism,
+					   DM_ISM_EVENT_ENTER_IDLE_REQUESTED);
+
+		KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state,
+				(int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO);
+
+		/* SSO engagement disallows idle, then re-allows it. */
+		KUNIT_EXPECT_EQ(test, dm_ism_test_idle.calls, 2);
+		KUNIT_EXPECT_TRUE(test, dm_ism_test_idle.last_allow);
+		KUNIT_EXPECT_TRUE(test, adev->dm.dc->idle_optimizations_allowed);
+	}
+
+	amdgpu_dm_ism_fini(&acrtc->ism);
+}
+
+/**
+ * dm_test_ism_commit_disallows_idle_on_timer_aborted - aborted timer disallows idle
+ * @test: KUnit test context
+ *
+ * A non-zero idle-allow delay parks the FSM in HYSTERESIS_WAITING.
+ * EXIT_IDLE_REQUESTED then drives HYSTERESIS_WAITING -> TIMER_ABORTED, whose
+ * arm commits with vblank_enabled = true: idle must be disallowed and, because
+ * vblank is active, never re-allowed. The FSM returns to FULL_POWER_RUNNING.
+ */
+static void dm_test_ism_commit_disallows_idle_on_timer_aborted(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev);
+	struct dc_stream_state *stream;
+	struct amdgpu_dm_ism_config config = {
+		.filter_num_frames = 5,
+		.filter_entry_count = 3,
+		.activation_num_delay_frames = 10,
+		.filter_history_size = 8,
+		.filter_old_history_threshold = 0,
+		.sso_num_frames = 0,
+	};
+
+	stream = setup_commit_test(test, acrtc, &config);
+	setup_idle_delay_history(&acrtc->ism, stream);
+	adev->dm.dc->idle_optimizations_allowed = true;
+
+	scoped_guard(mutex, &adev->dm.dc_lock) {
+		/* Non-zero delay parks the FSM in HYSTERESIS_WAITING. */
+		amdgpu_dm_ism_commit_event(&acrtc->ism,
+					   DM_ISM_EVENT_ENTER_IDLE_REQUESTED);
+		KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state,
+				(int)DM_ISM_STATE_HYSTERESIS_WAITING);
+
+		/* Abort: HYSTERESIS_WAITING -> TIMER_ABORTED -> FULL_POWER_RUNNING. */
+		amdgpu_dm_ism_commit_event(&acrtc->ism,
+					   DM_ISM_EVENT_EXIT_IDLE_REQUESTED);
+		KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state,
+				(int)DM_ISM_STATE_FULL_POWER_RUNNING);
+
+		/* TIMER_ABORTED commit runs with vblank_enabled=true. */
+		KUNIT_EXPECT_EQ(test, dm_ism_test_idle.calls, 1);
+		KUNIT_EXPECT_FALSE(test, dm_ism_test_idle.last_allow);
+		KUNIT_EXPECT_FALSE(test, adev->dm.dc->idle_optimizations_allowed);
+	}
+
+	amdgpu_dm_ism_fini(&acrtc->ism);
+}
+
 static struct kunit_case dm_ism_test_cases[] = {
 	/* dm_ism_next_state — FULL_POWER_RUNNING */
 	KUNIT_CASE(dm_test_ism_next_state_running_enter_idle),
@@ -1263,6 +1447,10 @@ static struct kunit_case dm_ism_test_cases[] = {
 	/* dm_ism_dispatch_power_state (via commit_event) */
 	KUNIT_CASE(dm_test_ism_dispatch_hysteresis_schedule_and_cancel),
 	KUNIT_CASE(dm_test_ism_dispatch_optimized_idle_defers_sso),
+	/* dm_ism_commit_idle_optimization_state (behavioural, via dc->hwss) */
+	KUNIT_CASE(dm_test_ism_commit_allows_idle_on_optimized_idle),
+	KUNIT_CASE(dm_test_ism_commit_enables_sso_on_optimized_idle_sso),
+	KUNIT_CASE(dm_test_ism_commit_disallows_idle_on_timer_aborted),
 	{}
 };
 
-- 
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.