mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 11:41:29 -04:00
drm/amd/display: Add KUnit tests for ism
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 <bhawanpreet.lakha@amd.com> Signed-off-by: Alex Hung <alex.hung@amd.com> Signed-off-by: George Zhang <george.zhang@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user