From: Alex Hung <[email protected]>

Add KUnit tests for three static functions in amdgpu_dm_ism.c:
dm_ism_next_state, dm_ism_get_sso_delay, and
dm_ism_get_idle_allow_delay.

The 32 test cases cover the full FSM transition table,
SSO delay calculation with various timings, and
hysteresis-based idle allow delay including circular
buffer wraparound and old history cutoff logic.

Conditionally remove static linkage and export the three
functions under CONFIG_DRM_AMD_DC_KUNIT_TEST so the test
module can call them.

Assisted-by: Copilot:Claude-Opus-4.6

Reviewed-by: Harry Wentland <[email protected]>
Signed-off-by: Alex Hung <[email protected]>
Signed-off-by: Ivan Lipski <[email protected]>
---
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c |  20 +-
 .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h |  10 +
 .../drm/amd/display/amdgpu_dm/tests/Makefile  |   1 +
 .../amdgpu_dm/tests/amdgpu_dm_ism_test.c      | 636 ++++++++++++++++++
 4 files changed, 661 insertions(+), 6 deletions(-)
 create mode 100644 
drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c 
b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c
index d03ea3bafd46..bc7db5e759d1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c
@@ -32,6 +32,8 @@
 #include "amdgpu_dm_ism.h"
 #include "amdgpu_dm_crtc.h"
 #include "amdgpu_dm_trace.h"
+#include "amdgpu_dm_kunit_helpers.h"
+
 
 /**
  * dm_ism_next_state - Get next state based on current state and event
@@ -42,9 +44,10 @@
  * This function defines the idle state management FSM. Invalid transitions
  * are ignored and will not progress the FSM.
  */
-static bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state,
-                             enum amdgpu_dm_ism_event event,
-                             enum amdgpu_dm_ism_state *next_state)
+STATIC_IFN_KUNIT
+bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state,
+                      enum amdgpu_dm_ism_event event,
+                      enum amdgpu_dm_ism_state *next_state)
 {
        switch (STATE_EVENT(current_state, event)) {
        case STATE_EVENT(DM_ISM_STATE_FULL_POWER_RUNNING,
@@ -125,8 +128,10 @@ static bool dm_ism_next_state(enum amdgpu_dm_ism_state 
current_state,
        }
        return true;
 }
+EXPORT_IF_KUNIT(dm_ism_next_state);
 
-static uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism,
+STATIC_IFN_KUNIT
+uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism,
                                     const struct dc_stream_state *stream)
 {
        const struct amdgpu_dm_ism_config *config = &ism->config;
@@ -148,6 +153,7 @@ static uint64_t dm_ism_get_sso_delay(const struct 
amdgpu_dm_ism *ism,
 
        return sso_delay_ns;
 }
+EXPORT_IF_KUNIT(dm_ism_get_sso_delay);
 
 /**
  * dm_ism_get_idle_allow_delay - Calculate hysteresis-based idle allow delay
@@ -157,8 +163,9 @@ static uint64_t dm_ism_get_sso_delay(const struct 
amdgpu_dm_ism *ism,
  * Calculates the delay before allowing idle optimizations based on recent
  * idle history and the current stream timing.
  */
-static uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism,
-                                           const struct dc_stream_state 
*stream)
+STATIC_IFN_KUNIT
+uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism,
+                                    const struct dc_stream_state *stream)
 {
        const struct amdgpu_dm_ism_config *config = &ism->config;
        uint32_t v_total, h_total;
@@ -217,6 +224,7 @@ static uint64_t dm_ism_get_idle_allow_delay(const struct 
amdgpu_dm_ism *ism,
 
        return ret_ns;
 }
+EXPORT_IF_KUNIT(dm_ism_get_idle_allow_delay);
 
 /**
  * dm_ism_insert_record - Insert a record into the circular history buffer
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h 
b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h
index fde0ddc8d4e4..4df6a82972a8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h
@@ -148,4 +148,14 @@ void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism,
 void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm);
 void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm);
 
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state,
+                      enum amdgpu_dm_ism_event event,
+                      enum amdgpu_dm_ism_state *next_state);
+uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism,
+                             const struct dc_stream_state *stream);
+uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism,
+                                    const struct dc_stream_state *stream);
+#endif
+
 #endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile 
b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 7d9c983f4995..768f9bbc50e1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -15,3 +15,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o
 obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o
 obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o
 obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o
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
new file mode 100644
index 000000000000..e761105e1995
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c
@@ -0,0 +1,636 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_ism.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+
+#include "dc.h"
+#include "amdgpu_dm_ism.h"
+
+/*
+ * Helper: allocate and zero-initialise a dc_stream_state for timing tests.
+ * Only the timing sub-struct is accessed by the functions under test.
+ */
+static struct dc_stream_state *alloc_test_stream(struct kunit *test)
+{
+       struct dc_stream_state *stream;
+
+       stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, stream);
+
+       return stream;
+}
+
+/*
+ * Helper: allocate and zero-initialise an ISM instance.
+ */
+static struct amdgpu_dm_ism *alloc_test_ism(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism;
+
+       ism = kunit_kzalloc(test, sizeof(*ism), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, ism);
+
+       return ism;
+}
+
+/* ===== Tests for dm_ism_next_state — FULL_POWER_RUNNING transitions ===== */
+
+static void dm_test_ism_next_state_running_enter_idle(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING,
+                              DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_WAITING);
+}
+
+static void dm_test_ism_next_state_running_begin_cursor(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING,
+                              DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_BUSY);
+}
+
+static void dm_test_ism_next_state_running_invalid(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next = DM_ISM_NUM_STATES;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING,
+                              DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
+       KUNIT_EXPECT_FALSE(test, ok);
+       /* next should remain untouched on invalid transition */
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_NUM_STATES);
+}
+
+/* ===== Tests for dm_ism_next_state — FULL_POWER_BUSY transitions ===== */
+
+static void dm_test_ism_next_state_busy_enter_idle(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_BUSY,
+                              DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY);
+}
+
+static void dm_test_ism_next_state_busy_end_cursor(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_BUSY,
+                              DM_ISM_EVENT_END_CURSOR_UPDATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+}
+
+/* ===== Tests for dm_ism_next_state — HYSTERESIS_WAITING transitions ===== */
+
+static void dm_test_ism_next_state_hyst_wait_exit_idle(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING,
+                              DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_TIMER_ABORTED);
+}
+
+static void dm_test_ism_next_state_hyst_wait_begin_cursor(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING,
+                              DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY);
+}
+
+static void dm_test_ism_next_state_hyst_wait_timer(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING,
+                              DM_ISM_EVENT_TIMER_ELAPSED, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE);
+}
+
+static void dm_test_ism_next_state_hyst_wait_immediate(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING,
+                              DM_ISM_EVENT_IMMEDIATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE);
+}
+
+/* ===== Tests for dm_ism_next_state — HYSTERESIS_BUSY transitions ===== */
+
+static void dm_test_ism_next_state_hyst_busy_exit_idle(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_BUSY,
+                              DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_BUSY);
+}
+
+static void dm_test_ism_next_state_hyst_busy_end_cursor(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_BUSY,
+                              DM_ISM_EVENT_END_CURSOR_UPDATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_WAITING);
+}
+
+/* ===== Tests for dm_ism_next_state — OPTIMIZED_IDLE transitions ===== */
+
+static void dm_test_ism_next_state_opt_idle_exit(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE,
+                              DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+}
+
+static void dm_test_ism_next_state_opt_idle_begin_cursor(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE,
+                              DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY);
+}
+
+static void dm_test_ism_next_state_opt_idle_sso_timer(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE,
+                              DM_ISM_EVENT_SSO_TIMER_ELAPSED, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO);
+}
+
+static void dm_test_ism_next_state_opt_idle_immediate(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE,
+                              DM_ISM_EVENT_IMMEDIATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO);
+}
+
+/* ===== Tests for dm_ism_next_state — OPTIMIZED_IDLE_SSO transitions ===== */
+
+static void dm_test_ism_next_state_opt_idle_sso_exit(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
+                              DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+}
+
+static void dm_test_ism_next_state_opt_idle_sso_cursor(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
+                              DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY);
+}
+
+/* ===== Tests for dm_ism_next_state — TIMER_ABORTED transitions ===== */
+
+static void dm_test_ism_next_state_aborted_immediate(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_TIMER_ABORTED,
+                              DM_ISM_EVENT_IMMEDIATE, &next);
+       KUNIT_EXPECT_TRUE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+}
+
+static void dm_test_ism_next_state_aborted_invalid(struct kunit *test)
+{
+       enum amdgpu_dm_ism_state next = DM_ISM_NUM_STATES;
+       bool ok;
+
+       ok = dm_ism_next_state(DM_ISM_STATE_TIMER_ABORTED,
+                              DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next);
+       KUNIT_EXPECT_FALSE(test, ok);
+       KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_NUM_STATES);
+}
+
+/* ===== Tests for dm_ism_get_sso_delay ===== */
+
+static void dm_test_ism_sso_delay_null_stream(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+
+       ism->config.sso_num_frames = 5;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, NULL), (uint64_t)0);
+}
+
+static void dm_test_ism_sso_delay_zero_frames(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+       ism->config.sso_num_frames = 0;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), (uint64_t)0);
+}
+
+static void dm_test_ism_sso_delay_1080p60_3frames(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+       uint64_t expected_one_frame_ns, expected;
+
+       /*
+        * 1080p@60Hz: v_total=1125, h_total=2200, pix_clk=148.5MHz
+        * pix_clk_100hz = 1485000
+        * one_frame_ns = (1125 * 2200 * 10000000) / 1485000 = 16666666 ns
+        */
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+       ism->config.sso_num_frames = 3;
+
+       expected_one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
+                                         1485000);
+       expected = 3 * expected_one_frame_ns;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), expected);
+}
+
+static void dm_test_ism_sso_delay_4k60_1frame(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+       uint64_t expected_one_frame_ns;
+
+       /*
+        * 4K@60Hz: v_total=2250, h_total=4400, pix_clk=594MHz
+        * pix_clk_100hz = 5940000
+        */
+       stream->timing.v_total = 2250;
+       stream->timing.h_total = 4400;
+       stream->timing.pix_clk_100hz = 5940000;
+       ism->config.sso_num_frames = 1;
+
+       expected_one_frame_ns = div64_u64((uint64_t)2250 * 4400 * 10000000ULL,
+                                         5940000);
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream),
+                       expected_one_frame_ns);
+}
+
+/* ===== Tests for dm_ism_get_idle_allow_delay ===== */
+
+static void dm_test_ism_idle_delay_null_stream(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+
+       ism->config.filter_num_frames = 5;
+       ism->config.filter_entry_count = 3;
+       ism->config.activation_num_delay_frames = 10;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, NULL),
+                       (uint64_t)0);
+}
+
+static void dm_test_ism_idle_delay_zero_filter_frames(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+       ism->config.filter_num_frames = 0;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
+                       (uint64_t)0);
+}
+
+static void dm_test_ism_idle_delay_zero_entry_count(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+       ism->config.filter_num_frames = 5;
+       ism->config.filter_entry_count = 0;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
+                       (uint64_t)0);
+}
+
+static void dm_test_ism_idle_delay_zero_delay_frames(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+       ism->config.filter_num_frames = 5;
+       ism->config.filter_entry_count = 3;
+       ism->config.activation_num_delay_frames = 0;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
+                       (uint64_t)0);
+}
+
+static void dm_test_ism_idle_delay_no_short_idles(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+       uint64_t one_frame_ns;
+
+       /*
+        * All history records have long durations (well above the
+        * short_idle_ns threshold), so no delay should be applied.
+        */
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+
+       one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
+                                1485000);
+
+       ism->config.filter_num_frames = 5;
+       ism->config.filter_entry_count = 3;
+       ism->config.activation_num_delay_frames = 10;
+       ism->config.filter_history_size = 8;
+       ism->config.filter_old_history_threshold = 0;
+
+       /* Fill history with long idle durations */
+       for (int i = 0; i < 8; i++) {
+               ism->records[i].duration_ns = one_frame_ns * 100;
+               ism->records[i].timestamp_ns = 0;
+       }
+       ism->next_record_idx = 8;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
+                       (uint64_t)0);
+}
+
+static void dm_test_ism_idle_delay_enough_short_idles(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+       uint64_t one_frame_ns, expected;
+
+       /*
+        * Fill history with short idle durations that meet the threshold.
+        * filter_entry_count=3, so 3 short idles should trigger the delay.
+        */
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+
+       one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
+                                1485000);
+
+       ism->config.filter_num_frames = 5;
+       ism->config.filter_entry_count = 3;
+       ism->config.activation_num_delay_frames = 10;
+       ism->config.filter_history_size = 8;
+       ism->config.filter_old_history_threshold = 0;
+
+       /* Fill history with short idle durations (1 frame each) */
+       for (int i = 0; i < 8; i++) {
+               ism->records[i].duration_ns = one_frame_ns;
+               ism->records[i].timestamp_ns = 0;
+       }
+       ism->next_record_idx = 8;
+
+       expected = 10 * one_frame_ns;
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
+                       expected);
+}
+
+static void dm_test_ism_idle_delay_wraps_around_buffer(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+       uint64_t one_frame_ns, expected;
+
+       /*
+        * Test the circular buffer wraparound: next_record_idx at 2 means
+        * the most recent records are at indices 1, 0, 15, 14, ...
+        */
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+
+       one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
+                                1485000);
+
+       ism->config.filter_num_frames = 5;
+       ism->config.filter_entry_count = 3;
+       ism->config.activation_num_delay_frames = 10;
+       ism->config.filter_history_size = 8;
+       ism->config.filter_old_history_threshold = 0;
+
+       /* Fill entire buffer with short idles */
+       for (int i = 0; i < AMDGPU_DM_IDLE_HIST_LEN; i++) {
+               ism->records[i].duration_ns = one_frame_ns;
+               ism->records[i].timestamp_ns = 0;
+       }
+       /* Position next_record_idx at 2 to test wraparound */
+       ism->next_record_idx = 2;
+
+       expected = 10 * one_frame_ns;
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
+                       expected);
+}
+
+static void dm_test_ism_idle_delay_old_history_cutoff(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+       uint64_t one_frame_ns;
+
+       /*
+        * Test old_history_threshold: only recent entries within the
+        * threshold should be counted. Set up 2 recent short idles but
+        * require 3 — older entries are outside the threshold.
+        */
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+
+       one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
+                                1485000);
+
+       ism->config.filter_num_frames = 5;
+       ism->config.filter_entry_count = 3;
+       ism->config.activation_num_delay_frames = 10;
+       ism->config.filter_history_size = 8;
+       /* Threshold: entries older than 20 frames are ignored */
+       ism->config.filter_old_history_threshold = 20;
+
+       ism->last_idle_timestamp_ns = one_frame_ns * 100;
+
+       /* 2 recent short idles (within threshold) */
+       ism->records[6].duration_ns = one_frame_ns;
+       ism->records[6].timestamp_ns = one_frame_ns * 95;
+       ism->records[7].duration_ns = one_frame_ns;
+       ism->records[7].timestamp_ns = one_frame_ns * 98;
+
+       /* Older entries outside the threshold with long durations */
+       for (int i = 0; i < 6; i++) {
+               ism->records[i].duration_ns = one_frame_ns * 100;
+               ism->records[i].timestamp_ns = one_frame_ns * 10;
+       }
+       ism->next_record_idx = 8;
+
+       /*
+        * Only 2 short idles within threshold, but 3 required —
+        * should return 0 (no delay).
+        */
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
+                       (uint64_t)0);
+}
+
+static void dm_test_ism_idle_delay_mixed_durations(struct kunit *test)
+{
+       struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+       struct dc_stream_state *stream = alloc_test_stream(test);
+       uint64_t one_frame_ns;
+
+       /*
+        * Mix of short and long idle durations. Only 2 short idles
+        * in 8 entries, but filter_entry_count=3, so no delay.
+        */
+       stream->timing.v_total = 1125;
+       stream->timing.h_total = 2200;
+       stream->timing.pix_clk_100hz = 1485000;
+
+       one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
+                                1485000);
+
+       ism->config.filter_num_frames = 5;
+       ism->config.filter_entry_count = 3;
+       ism->config.activation_num_delay_frames = 10;
+       ism->config.filter_history_size = 8;
+       ism->config.filter_old_history_threshold = 0;
+
+       /* 2 short idles, 6 long idles */
+       for (int i = 0; i < 8; i++) {
+               if (i == 6 || i == 7)
+                       ism->records[i].duration_ns = one_frame_ns;
+               else
+                       ism->records[i].duration_ns = one_frame_ns * 100;
+               ism->records[i].timestamp_ns = 0;
+       }
+       ism->next_record_idx = 8;
+
+       KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
+                       (uint64_t)0);
+}
+
+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),
+       KUNIT_CASE(dm_test_ism_next_state_running_begin_cursor),
+       KUNIT_CASE(dm_test_ism_next_state_running_invalid),
+       /* dm_ism_next_state — FULL_POWER_BUSY */
+       KUNIT_CASE(dm_test_ism_next_state_busy_enter_idle),
+       KUNIT_CASE(dm_test_ism_next_state_busy_end_cursor),
+       /* dm_ism_next_state — HYSTERESIS_WAITING */
+       KUNIT_CASE(dm_test_ism_next_state_hyst_wait_exit_idle),
+       KUNIT_CASE(dm_test_ism_next_state_hyst_wait_begin_cursor),
+       KUNIT_CASE(dm_test_ism_next_state_hyst_wait_timer),
+       KUNIT_CASE(dm_test_ism_next_state_hyst_wait_immediate),
+       /* dm_ism_next_state — HYSTERESIS_BUSY */
+       KUNIT_CASE(dm_test_ism_next_state_hyst_busy_exit_idle),
+       KUNIT_CASE(dm_test_ism_next_state_hyst_busy_end_cursor),
+       /* dm_ism_next_state — OPTIMIZED_IDLE */
+       KUNIT_CASE(dm_test_ism_next_state_opt_idle_exit),
+       KUNIT_CASE(dm_test_ism_next_state_opt_idle_begin_cursor),
+       KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_timer),
+       KUNIT_CASE(dm_test_ism_next_state_opt_idle_immediate),
+       /* dm_ism_next_state — OPTIMIZED_IDLE_SSO */
+       KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_exit),
+       KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_cursor),
+       /* dm_ism_next_state — TIMER_ABORTED */
+       KUNIT_CASE(dm_test_ism_next_state_aborted_immediate),
+       KUNIT_CASE(dm_test_ism_next_state_aborted_invalid),
+       /* dm_ism_get_sso_delay */
+       KUNIT_CASE(dm_test_ism_sso_delay_null_stream),
+       KUNIT_CASE(dm_test_ism_sso_delay_zero_frames),
+       KUNIT_CASE(dm_test_ism_sso_delay_1080p60_3frames),
+       KUNIT_CASE(dm_test_ism_sso_delay_4k60_1frame),
+       /* dm_ism_get_idle_allow_delay */
+       KUNIT_CASE(dm_test_ism_idle_delay_null_stream),
+       KUNIT_CASE(dm_test_ism_idle_delay_zero_filter_frames),
+       KUNIT_CASE(dm_test_ism_idle_delay_zero_entry_count),
+       KUNIT_CASE(dm_test_ism_idle_delay_zero_delay_frames),
+       KUNIT_CASE(dm_test_ism_idle_delay_no_short_idles),
+       KUNIT_CASE(dm_test_ism_idle_delay_enough_short_idles),
+       KUNIT_CASE(dm_test_ism_idle_delay_wraps_around_buffer),
+       KUNIT_CASE(dm_test_ism_idle_delay_old_history_cutoff),
+       KUNIT_CASE(dm_test_ism_idle_delay_mixed_durations),
+       {}
+};
+
+static struct kunit_suite dm_ism_test_suite = {
+       .name = "amdgpu_dm_ism",
+       .test_cases = dm_ism_test_cases,
+};
+
+kunit_test_suite(dm_ism_test_suite);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_ism");
+MODULE_AUTHOR("AMD");
-- 
2.43.0

Reply via email to