summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Hung <alex.hung@amd.com>2026-06-18 21:12:21 -0600
committerAlex Deucher <alexander.deucher@amd.com>2026-07-14 19:12:35 -0400
commit3b1fdd64133268aeccf8be488f52ff24998bab52 (patch)
treeb7d6e19e4cb2a412af499ea65c3c11ebb4ec2fdb
parent5a22cc7c724dc4ced818b2eae1db7433f467a761 (diff)
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>
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c4
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c353
2 files changed, 357 insertions, 0 deletions
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),
{}
};