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), {} };