// SPDX-License-Identifier: GPL-2.0 OR MIT /* * KUnit tests for amdgpu_dm_ism.c * * Copyright 2026 Advanced Micro Devices, Inc. */ #include #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); } /** * 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); } /* ===== Tests for dm_ism_dispatch_next_event ===== */ /** * dm_test_dispatch_next_event_hyst_wait_no_delay - zero delay_ns: IMMEDIATE in HYSTERESIS_WAITING * @test: KUnit test context */ static void dm_test_dispatch_next_event_hyst_wait_no_delay(struct kunit *test) { enum amdgpu_dm_ism_event result; result = dm_ism_dispatch_next_event(DM_ISM_STATE_HYSTERESIS_WAITING, 0, 0); KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE); } /** * dm_test_dispatch_next_event_hyst_wait_with_delay - delay_ns > 0, no IMMEDIATE event returned * @test: KUnit test context */ static void dm_test_dispatch_next_event_hyst_wait_with_delay(struct kunit *test) { enum amdgpu_dm_ism_event result; result = dm_ism_dispatch_next_event(DM_ISM_STATE_HYSTERESIS_WAITING, 1000000, 0); KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); } /** * dm_test_dispatch_next_event_opt_idle_no_sso_delay - sso_delay_ns == 0 triggers IMMEDIATE event * @test: KUnit test context */ static void dm_test_dispatch_next_event_opt_idle_no_sso_delay(struct kunit *test) { enum amdgpu_dm_ism_event result; result = dm_ism_dispatch_next_event(DM_ISM_STATE_OPTIMIZED_IDLE, 0, 0); KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE); } /** * dm_test_dispatch_next_event_opt_idle_with_sso_delay - sso_delay_ns > 0, SSO timer, no IMMEDIATE * @test: KUnit test context */ static void dm_test_dispatch_next_event_opt_idle_with_sso_delay(struct kunit *test) { enum amdgpu_dm_ism_event result; result = dm_ism_dispatch_next_event(DM_ISM_STATE_OPTIMIZED_IDLE, 0, 1000000); KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); } /** * dm_test_dispatch_next_event_timer_aborted - TIMER_ABORTED always returns IMMEDIATE * @test: KUnit test context */ static void dm_test_dispatch_next_event_timer_aborted(struct kunit *test) { enum amdgpu_dm_ism_event result; result = dm_ism_dispatch_next_event(DM_ISM_STATE_TIMER_ABORTED, 0, 0); KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE); } /** * dm_test_dispatch_next_event_no_action_state - other states return DM_ISM_NUM_EVENTS * @test: KUnit test context */ static void dm_test_dispatch_next_event_no_action_state(struct kunit *test) { enum amdgpu_dm_ism_event result; result = dm_ism_dispatch_next_event(DM_ISM_STATE_FULL_POWER_RUNNING, 0, 0); KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); } 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), 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), /* dm_ism_dispatch_next_event */ KUNIT_CASE(dm_test_dispatch_next_event_hyst_wait_no_delay), KUNIT_CASE(dm_test_dispatch_next_event_hyst_wait_with_delay), KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_no_sso_delay), KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_with_sso_delay), KUNIT_CASE(dm_test_dispatch_next_event_timer_aborted), KUNIT_CASE(dm_test_dispatch_next_event_no_action_state), {} }; 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");