From: Alex Hung <[email protected]> Add KUnit coverage for the following ISM functions:
- dm_ism_next_state(): state transitions for running, busy, hysteresis-wait, optimized-idle, and aborted states - dm_ism_get_sso_delay(): null stream, zero frames, 1080p60 3-frame, and 4k60 1-frame calculations - dm_ism_get_idle_allow_delay(): null stream, zero filter/entry/delay frames, short-idle filtering, wrap-around, old history cutoff, mixed durations, and entry count exceeding history size - amdgpu_dm_ism_init(): initial state setup - amdgpu_dm_ism_fini(): cleanup after init - dm_ism_set_last_idle_ts(): timestamp update - dm_ism_insert_record(): basic insert and wrap-around - dm_ism_trigger_event(): valid and invalid transitions Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha <[email protected]> Signed-off-by: Alex Hung <[email protected]> Signed-off-by: George Zhang <[email protected]> --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c | 4 + .../amdgpu_dm/tests/amdgpu_dm_ism_test.c | 353 ++++++++++++++++++ 2 files changed, 357 insertions(+) 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 32391b56097e..a9575bf25fc2 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 @@ -502,6 +502,7 @@ void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism, } while (next_event < DM_ISM_NUM_EVENTS); } +EXPORT_IF_KUNIT(amdgpu_dm_ism_commit_event); static void dm_ism_delayed_work_func(struct work_struct *work) @@ -568,6 +569,7 @@ void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm) disable_delayed_work_sync(&ism->sso_delayed_work); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_disable); /** * amdgpu_dm_ism_force_full_power - Force every CRTC's ISM FSM to FULL_POWER @@ -603,6 +605,7 @@ void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm) DM_ISM_EVENT_EXIT_IDLE_REQUESTED); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_force_full_power); /** * amdgpu_dm_ism_enable - enable the ISM @@ -626,6 +629,7 @@ void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm) enable_delayed_work(&ism->sso_delayed_work); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_enable); void amdgpu_dm_ism_init(struct amdgpu_dm_ism *ism, struct amdgpu_dm_ism_config *config) 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 7dfb3b351d20..6394b967ac3a 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 @@ -8,6 +8,9 @@ #include <kunit/test.h> #include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_ism.h" #include "amdgpu_dm_kunit_test_helpers.h" @@ -848,6 +851,345 @@ static void dm_test_dispatch_next_event_no_action_state(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); } +/* + * Helper: allocate an amdgpu_crtc (which embeds the ISM) wired up to a + * minimally-populated amdgpu_device so that the container_of()/drm_to_adev() + * lookups inside the ISM event machinery resolve correctly. + * + * The amdgpu_device is large, so it must be heap-allocated via kunit_kzalloc. + * acrtc->base.dev points at the embedded &adev->ddev, which is what + * drm_to_adev() expects (it is a container_of of ddev). + */ +static struct amdgpu_crtc *alloc_test_acrtc(struct kunit *test, + struct amdgpu_device **adev_out) +{ + 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.ddev = &adev->ddev; + mutex_init(&adev->dm.dc_lock); + + acrtc->base.dev = &adev->ddev; + + if (adev_out) + *adev_out = adev; + + return acrtc; +} + +/* + * Helper: register an already-allocated amdgpu_crtc into the DRM device's + * crtc_list so that drm_for_each_crtc() iterates it. Only the list linkage is + * required for the ISM enable/disable/force-full-power helpers. + */ +static void register_test_acrtc(struct amdgpu_device *adev, + struct amdgpu_crtc *acrtc) +{ + INIT_LIST_HEAD(&adev->ddev.mode_config.crtc_list); + INIT_LIST_HEAD(&acrtc->base.head); + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); +} + +/* ===== Tests for amdgpu_dm_ism_commit_event ===== */ + +/** + * dm_test_ism_commit_event_no_state - commit_event returns early without a crtc state + * @test: KUnit test context + * + * When the CRTC has no atomic state (base.state == NULL) the function takes + * the NO_STATE early-return path and the FSM is left untouched. + */ +static void dm_test_ism_commit_event_no_state(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct amdgpu_dm_ism_config config = { 0 }; + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = NULL; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_commit_event_cursor_transition - cursor begin/end drive FSM without DC work + * @test: KUnit test context + * + * BEGIN_CURSOR_UPDATE then END_CURSOR_UPDATE from FULL_POWER_RUNNING traverse + * the FULL_POWER_BUSY state. Neither transition reaches the idle-optimization + * commit path, so no DC hardware access occurs and the FSM returns to + * FULL_POWER_RUNNING. + */ +static void dm_test_ism_commit_event_cursor_transition(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_BUSY); + + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_END_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_commit_event_invalid_event - invalid event leaves FSM unchanged + * @test: KUnit test context + * + * EXIT_IDLE_REQUESTED is not a valid event from FULL_POWER_RUNNING, so the + * FSM does not transition and no power-state dispatch occurs. + */ +static void dm_test_ism_commit_event_invalid_event(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + + guard(mutex)(&adev->dm.dc_lock); + 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); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for amdgpu_dm_ism_force_full_power ===== */ + +/** + * dm_test_ism_force_full_power - force-full-power sends EXIT_IDLE to every CRTC + * @test: KUnit test context + * + * From the initial FULL_POWER_RUNNING state EXIT_IDLE_REQUESTED is a no-op, so + * the FSM remains in FULL_POWER_RUNNING and no DC work is scheduled. + */ +static void dm_test_ism_force_full_power(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + register_test_acrtc(adev, acrtc); + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_force_full_power(&adev->dm); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for amdgpu_dm_ism_disable / amdgpu_dm_ism_enable ===== */ + +/** + * dm_test_ism_disable_enable_cycle - disable then enable quiesces and re-arms work + * @test: KUnit test context + * + * Walks every CRTC's ISM, disabling its delayed work (synchronously) and then + * re-enabling it. With no work ever scheduled both calls complete without + * touching the FSM state. disable must be called without dc_lock held. + */ +static void dm_test_ism_disable_enable_cycle(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct amdgpu_dm_ism_config config = { 0 }; + + amdgpu_dm_ism_init(&acrtc->ism, &config); + register_test_acrtc(adev, acrtc); + + amdgpu_dm_ism_disable(&adev->dm); + amdgpu_dm_ism_enable(&adev->dm); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for dm_ism_dispatch_power_state (via commit_event) ===== */ + +/* + * Build a config + history that makes dm_ism_get_idle_allow_delay() return a + * non-zero hysteresis delay. A non-zero delay keeps the FSM parked in + * HYSTERESIS_WAITING (the dispatcher returns DM_ISM_NUM_EVENTS instead of an + * immediate follow-up event), preventing the cascade into the DC-dependent + * OPTIMIZED_IDLE / *_SSO states. + */ +static void setup_idle_delay_history(struct amdgpu_dm_ism *ism, + struct dc_stream_state *stream) +{ + uint64_t one_frame_ns; + + 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); + + 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; +} + +/** + * dm_test_ism_dispatch_hysteresis_schedule_and_cancel - cover the HYSTERESIS_WAITING dispatch arms + * @test: KUnit test context + * + * ENTER_IDLE_REQUESTED moves FULL_POWER_RUNNING -> HYSTERESIS_WAITING. The + * current-state arm of dm_ism_dispatch_power_state() then records the idle + * timestamp, computes a (non-zero) idle-allow delay and schedules the delayed + * worker. A subsequent BEGIN_CURSOR_UPDATE (-> HYSTERESIS_BUSY) exercises the + * previous-state arm that cancels that pending worker. Neither arm reaches the + * DC-dependent idle-optimization commit path. + */ +static void dm_test_ism_dispatch_hysteresis_schedule_and_cancel(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + 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, + }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + stream = dm_kunit_alloc_stream(test, NULL); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + setup_idle_delay_history(&acrtc->ism, stream); + dm_state->stream = stream; + acrtc->base.state = &dm_state->base; + + scoped_guard(mutex, &adev->dm.dc_lock) { + /* Enter HYSTERESIS_WAITING: schedules the idle-allow worker. */ + 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); + + /* Cursor update cancels the pending worker (prev-state arm). */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_BUSY); + } + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_dispatch_optimized_idle_defers_sso - cover OPTIMIZED_IDLE dispatch without DC + * @test: KUnit test context + * + * With sso_num_frames < filter_num_frames the OPTIMIZED_IDLE current-state arm + * skips the idle-optimization commit and only schedules the SSO worker. Driving + * HYSTERESIS_WAITING -> OPTIMIZED_IDLE via TIMER_ELAPSED exercises that arm + * (get_sso_delay + the skip branch + mod_delayed_work) without any DC access. + */ +static void dm_test_ism_dispatch_optimized_idle_defers_sso(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + 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 = 2, + }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + stream = dm_kunit_alloc_stream(test, NULL); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + setup_idle_delay_history(&acrtc->ism, stream); + dm_state->stream = stream; + acrtc->base.state = &dm_state->base; + + 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_HYSTERESIS_WAITING); + + /* + * Timer fires: HYSTERESIS_WAITING -> OPTIMIZED_IDLE. sso_delay + * is non-zero and sso_num_frames < filter_num_frames, so the + * commit is deferred to the SSO worker and the FSM parks here. + */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_TIMER_ELAPSED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_OPTIMIZED_IDLE); + + /* Cancel the scheduled SSO worker while still holding dc_lock. */ + cancel_delayed_work(&acrtc->ism.sso_delayed_work); + } + + 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), @@ -910,6 +1252,17 @@ static struct kunit_case dm_ism_test_cases[] = { 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), + /* amdgpu_dm_ism_commit_event */ + KUNIT_CASE(dm_test_ism_commit_event_no_state), + KUNIT_CASE(dm_test_ism_commit_event_cursor_transition), + KUNIT_CASE(dm_test_ism_commit_event_invalid_event), + /* amdgpu_dm_ism_force_full_power */ + KUNIT_CASE(dm_test_ism_force_full_power), + /* amdgpu_dm_ism_disable / amdgpu_dm_ism_enable */ + KUNIT_CASE(dm_test_ism_disable_enable_cycle), + /* 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), {} }; -- 2.55.0
