(uint64_t)0);
}
+/**
+ * dm_test_ism_idle_delay_entry_count_exceeds_history_size - entry_count > history_size sets delay
+ * @test: KUnit test context
+ */
+static void dm_test_ism_idle_delay_entry_count_exceeds_history_size(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;
+
+ /*
+ * filter_entry_count (5) > filter_history_size (3), so history_size
+ * is determined by filter_entry_count via max(). All 5 records are
+ * short idles, triggering 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 = 5;
+ ism->config.filter_history_size = 3;
+ ism->config.activation_num_delay_frames = 10;
+ ism->config.filter_old_history_threshold = 0;
+
+ for (int i = 0; i < 5; i++) {
+ ism->records[i].duration_ns = one_frame_ns;
+ ism->records[i].timestamp_ns = 0;
+ }
+ ism->next_record_idx = 5;
+
+ expected = 10 * one_frame_ns;
+ KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), expected);
+}
+
+/* ===== Tests for amdgpu_dm_ism_init ===== */
+
+/**
+ * dm_test_ism_init_sets_initial_state - all ISM fields initialized to expected values
+ * @test: KUnit test context
+ */
+static void dm_test_ism_init_sets_initial_state(struct kunit *test)
+{
+ struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+ 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 = 20,
+ .sso_num_frames = 2,
+ };
+
+ amdgpu_dm_ism_init(ism, &config);
+
+ KUNIT_EXPECT_EQ(test, (int)ism->current_state,
+ (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+ KUNIT_EXPECT_EQ(test, (int)ism->previous_state,
+ (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+ KUNIT_EXPECT_EQ(test, ism->next_record_idx, 0);
+ KUNIT_EXPECT_EQ(test, ism->last_idle_timestamp_ns, (uint64_t)0);
+ KUNIT_EXPECT_EQ(test, ism->config.filter_num_frames,
+ config.filter_num_frames);
+ KUNIT_EXPECT_EQ(test, ism->config.filter_entry_count,
+ config.filter_entry_count);
+ KUNIT_EXPECT_EQ(test, ism->config.activation_num_delay_frames,
+ config.activation_num_delay_frames);
+ KUNIT_EXPECT_EQ(test, ism->config.sso_num_frames, config.sso_num_frames);
+}
+
+/* ===== Tests for amdgpu_dm_ism_fini ===== */
+
+/**
+ * dm_test_ism_fini_after_init - fini cancels never-scheduled work without error
+ * @test: KUnit test context
+ */
+static void dm_test_ism_fini_after_init(struct kunit *test)
+{
+ struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+ struct amdgpu_dm_ism_config config = {
+ .filter_num_frames = 5,
+ .filter_entry_count = 3,
+ .activation_num_delay_frames = 10,
+ .sso_num_frames = 2,
+ };
+
+ amdgpu_dm_ism_init(ism, &config);
+ /* Work was never scheduled; cancel_delayed_work_sync is a no-op. */
+ amdgpu_dm_ism_fini(ism);
+
+ /* FSM state is untouched by fini */
+ KUNIT_EXPECT_EQ(test, (int)ism->current_state,
+ (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+}
+
+/* ===== Tests for dm_ism_set_last_idle_ts ===== */
+
+/**
+ * dm_test_ism_set_last_idle_ts_updates_timestamp - last_idle_timestamp_ns updated to current time
+ * @test: KUnit test context
+ */
+static void dm_test_ism_set_last_idle_ts_updates_timestamp(struct kunit *test)
+{
+ struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+ uint64_t before;
+
+ ism->last_idle_timestamp_ns = 0;
+ before = ktime_get_ns();
+ dm_ism_set_last_idle_ts(ism);
+
+ KUNIT_EXPECT_GE(test, ism->last_idle_timestamp_ns, before);
+}
+
+/* ===== Tests for dm_ism_insert_record ===== */
+
+/**
+ * dm_test_ism_insert_record_basic - record inserted with correct index and duration
+ * @test: KUnit test context
+ */
+static void dm_test_ism_insert_record_basic(struct kunit *test)
+{
+ struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+
+ ism->last_idle_timestamp_ns = 0;
+ ism->next_record_idx = 0;
+
+ dm_ism_insert_record(ism);
+
+ KUNIT_EXPECT_EQ(test, ism->next_record_idx, 1);
+ KUNIT_EXPECT_GT(test, ism->records[0].timestamp_ns, (uint64_t)0);
+ /* duration = timestamp - last_idle_timestamp_ns (0) */
+ KUNIT_EXPECT_EQ(test, ism->records[0].duration_ns,
+ ism->records[0].timestamp_ns);
+}
+
+/**
+ * dm_test_ism_insert_record_wraps_around - out-of-bounds index wraps to slot 0
+ * @test: KUnit test context
+ */
+static void dm_test_ism_insert_record_wraps_around(struct kunit *test)
+{
+ struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+
+ ism->last_idle_timestamp_ns = 0;
+ /* Out-of-bounds index triggers reset to 0 */
+ ism->next_record_idx = AMDGPU_DM_IDLE_HIST_LEN;
+
+ dm_ism_insert_record(ism);
+
+ KUNIT_EXPECT_EQ(test, ism->next_record_idx, 1);
+ KUNIT_EXPECT_GT(test, ism->records[0].timestamp_ns, (uint64_t)0);
+}
+
+/* ===== Tests for dm_ism_trigger_event ===== */
+
+/**
+ * dm_test_ism_trigger_event_valid_transition - valid event advances current and previous state
+ * @test: KUnit test context
+ */
+static void dm_test_ism_trigger_event_valid_transition(struct kunit *test)
+{
+ struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+ bool ok;
+
+ ism->current_state = DM_ISM_STATE_FULL_POWER_RUNNING;
+ ism->previous_state = DM_ISM_STATE_FULL_POWER_RUNNING;
+
+ ok = dm_ism_trigger_event(ism, DM_ISM_EVENT_ENTER_IDLE_REQUESTED);
+
+ KUNIT_EXPECT_TRUE(test, ok);
+ KUNIT_EXPECT_EQ(test, (int)ism->current_state,
+ (int)DM_ISM_STATE_HYSTERESIS_WAITING);
+ KUNIT_EXPECT_EQ(test, (int)ism->previous_state,
+ (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+}
+
+/**
+ * dm_test_ism_trigger_event_invalid_transition - invalid event leaves state unchanged
+ * @test: KUnit test context
+ */
+static void dm_test_ism_trigger_event_invalid_transition(struct kunit *test)
+{
+ struct amdgpu_dm_ism *ism = alloc_test_ism(test);
+ bool ok;
+
+ ism->current_state = DM_ISM_STATE_FULL_POWER_RUNNING;
+ ism->previous_state = DM_ISM_STATE_FULL_POWER_RUNNING;
+
+ /* EXIT_IDLE_REQUESTED is not valid from FULL_POWER_RUNNING */
+ ok = dm_ism_trigger_event(ism, DM_ISM_EVENT_EXIT_IDLE_REQUESTED);
+
+ KUNIT_EXPECT_FALSE(test, ok);
+ /* State must remain unchanged on invalid transition */
+ KUNIT_EXPECT_EQ(test, (int)ism->current_state,
+ (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+ KUNIT_EXPECT_EQ(test, (int)ism->previous_state,
+ (int)DM_ISM_STATE_FULL_POWER_RUNNING);
+}
+
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_idle_delay_wraps_around_buffer),
KUNIT_CASE(dm_test_ism_idle_delay_old_history_cutoff),
KUNIT_CASE(dm_test_ism_idle_delay_mixed_durations),
+ KUNIT_CASE(dm_test_ism_idle_delay_entry_count_exceeds_history_size),
+ /* amdgpu_dm_ism_init */
+ KUNIT_CASE(dm_test_ism_init_sets_initial_state),
+ /* amdgpu_dm_ism_fini */
+ KUNIT_CASE(dm_test_ism_fini_after_init),
+ /* dm_ism_set_last_idle_ts */
+ KUNIT_CASE(dm_test_ism_set_last_idle_ts_updates_timestamp),
+ /* dm_ism_insert_record */
+ KUNIT_CASE(dm_test_ism_insert_record_basic),
+ KUNIT_CASE(dm_test_ism_insert_record_wraps_around),
+ /* dm_ism_trigger_event */
+ KUNIT_CASE(dm_test_ism_trigger_event_valid_transition),
+ KUNIT_CASE(dm_test_ism_trigger_event_invalid_transition),
{}
};