[PATCH 14/41] drm/amd/display: Add KUnit tests for more crtc functions

<[email protected]> Fri, 31 Jul 2026 17:12:35 -0400
Newsgroups org.freedesktop.lists.amd-gfx
Message-ID <[email protected]>
From: Bhawanpreet Lakha <[email protected]>

Expand KUnit coverage for amdgpu_dm_crtc.c with tests for functions that
are easy to exercise in isolation:

- amdgpu_dm_crtc_set_static_screen_optimze(): the !allow_sr_entry early
  return.
- amdgpu_dm_crtc_enable_vblank(): rejection with -EINVAL when enabling
  vblank on an unconfigured CRTC.
- amdgpu_dm_crtc_update_crtc_active_planes(): the no-stream branch that
  resets active_planes to zero.
- amdgpu_dm_crtc_duplicate_state(): DM-specific fields are carried over.
- amdgpu_dm_crtc_reset_state(): a fresh state is allocated and installed.
- amdgpu_dm_crtc_destroy_state(): a stream-less state is freed cleanly.

Expose amdgpu_dm_crtc_destroy_state(), amdgpu_dm_crtc_duplicate_state(),
amdgpu_dm_crtc_reset_state() and amdgpu_dm_crtc_update_crtc_active_planes()
to the tests via STATIC_IFN_KUNIT/EXPORT_IF_KUNIT.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Alex Hung <[email protected]>
Signed-off-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Roman Li <[email protected]>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c    |  14 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_crtc.h    |   6 +
 .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c     | 177 ++++++++++++++++++
 3 files changed, 193 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
index 079d4ccc88da..ce42102109bb 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
@@ -134,6 +134,7 @@ void amdgpu_dm_crtc_set_static_screen_optimze(
 		amdgpu_dm_psr_set_event(dm, stream,
 			set_vsync_event, psr_event_vsync, set_vsync_event);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_set_static_screen_optimze);
 
 bool amdgpu_dm_is_headless(struct amdgpu_device *adev)
 {
@@ -393,13 +394,14 @@ int amdgpu_dm_crtc_enable_vblank(struct drm_crtc *crtc)
 {
 	return amdgpu_dm_crtc_set_vblank(crtc, true);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_enable_vblank);
 
 void amdgpu_dm_crtc_disable_vblank(struct drm_crtc *crtc)
 {
 	amdgpu_dm_crtc_set_vblank(crtc, false);
 }
 
-static void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc,
+STATIC_IFN_KUNIT void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc,
 				  struct drm_crtc_state *state)
 {
 	struct dm_crtc_state *cur = to_dm_crtc_state(state);
@@ -414,8 +416,9 @@ static void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc,
 
 	kfree(state);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_destroy_state);
 
-static struct drm_crtc_state *amdgpu_dm_crtc_duplicate_state(struct drm_crtc *crtc)
+STATIC_IFN_KUNIT struct drm_crtc_state *amdgpu_dm_crtc_duplicate_state(struct drm_crtc *crtc)
 {
 	struct dm_crtc_state *state, *cur;
 
@@ -450,6 +453,7 @@ static struct drm_crtc_state *amdgpu_dm_crtc_duplicate_state(struct drm_crtc *cr
 
 	return &state->base;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_duplicate_state);
 
 static void amdgpu_dm_crtc_destroy(struct drm_crtc *crtc)
 {
@@ -463,7 +467,7 @@ static void amdgpu_dm_crtc_destroy(struct drm_crtc *crtc)
 	kfree(crtc);
 }
 
-static void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc)
+STATIC_IFN_KUNIT void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc)
 {
 	struct dm_crtc_state *state;
 
@@ -476,6 +480,7 @@ static void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc)
 
 	__drm_atomic_helper_crtc_reset(crtc, &state->base);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_reset_state);
 
 #ifdef CONFIG_DEBUG_FS
 static int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc)
@@ -610,7 +615,7 @@ static int amdgpu_dm_crtc_count_crtc_active_planes(struct drm_crtc_state *new_cr
 	return num_active;
 }
 
-static void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc,
+STATIC_IFN_KUNIT void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc,
 						     struct drm_crtc_state *new_crtc_state)
 {
 	struct dm_crtc_state *dm_new_crtc_state =
@@ -624,6 +629,7 @@ static void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc,
 	dm_new_crtc_state->active_planes =
 		amdgpu_dm_crtc_count_crtc_active_planes(new_crtc_state);
 }
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_update_crtc_active_planes);
 
 STATIC_IFN_KUNIT bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc,
 					       const struct drm_display_mode *mode,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
index d8b004f613ab..ed6e8021d69e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
@@ -46,6 +46,12 @@ bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc);
 bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc,
 				      const struct drm_display_mode *mode,
 				      struct drm_display_mode *adjusted_mode);
+void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc,
+				  struct drm_crtc_state *state);
+struct drm_crtc_state *amdgpu_dm_crtc_duplicate_state(struct drm_crtc *crtc);
+void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc);
+void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc,
+					      struct drm_crtc_state *new_crtc_state);
 #endif
 
 bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
index ac21bbec92ab..ce20692959d0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
@@ -456,6 +456,171 @@ static void dm_test_idle_create_workqueue(struct kunit *test)
 	kfree(idle_work);
 }
 
+/* Tests for amdgpu_dm_crtc_set_static_screen_optimze() */
+
+/**
+ * dm_test_crtc_set_static_screen_optimze_no_sr_entry - Test early return when SR entry disallowed
+ * @test: The KUnit test context
+ *
+ * When self-refresh entry is not allowed the function must return immediately
+ * without touching replay or PSR events, regardless of the requested SSO state.
+ */
+static void dm_test_crtc_set_static_screen_optimze_no_sr_entry(struct kunit *test)
+{
+	struct amdgpu_display_manager *dm;
+	struct dc_link *link;
+	struct dc_stream_state *stream;
+
+	dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm);
+
+	link = dm_kunit_alloc_link(test);
+	stream = dm_kunit_alloc_stream(test, link);
+
+	/* allow_sr_entry == false -> returns before any event is set. */
+	amdgpu_dm_crtc_set_static_screen_optimze(dm, stream, true, false);
+	amdgpu_dm_crtc_set_static_screen_optimze(dm, stream, false, false);
+}
+
+/* Tests for amdgpu_dm_crtc_enable_vblank() */
+
+/**
+ * dm_test_crtc_enable_vblank_rejects_unconfigured - Test vblank enable on disabled CRTC
+ * @test: The KUnit test context
+ *
+ * Enabling vblank on a CRTC that is not enabled must be rejected with -EINVAL
+ * before any interrupt state is touched.
+ */
+static void dm_test_crtc_enable_vblank_rejects_unconfigured(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+	struct amdgpu_crtc *acrtc;
+
+	acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+	acrtc->base.dev = &adev->ddev;
+	acrtc->base.enabled = false;
+
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_enable_vblank(&acrtc->base), -EINVAL);
+}
+
+/* Tests for amdgpu_dm_crtc_update_crtc_active_planes() */
+
+/**
+ * dm_test_crtc_update_active_planes_no_stream - Test active plane reset without a stream
+ * @test: The KUnit test context
+ *
+ * Without a DC stream attached the active plane count must be reset to zero
+ * and the plane-counting path must be skipped.
+ */
+static void dm_test_crtc_update_active_planes_no_stream(struct kunit *test)
+{
+	struct dm_crtc_state *dm_state;
+
+	dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state);
+
+	dm_state->stream = NULL;
+	dm_state->active_planes = 5;
+
+	amdgpu_dm_crtc_update_crtc_active_planes(NULL, &dm_state->base);
+
+	KUNIT_EXPECT_EQ(test, dm_state->active_planes, 0);
+}
+
+/* Tests for amdgpu_dm_crtc_duplicate_state() */
+
+/**
+ * dm_test_crtc_duplicate_state_copies_fields - Test duplicated state carries DM fields
+ * @test: The KUnit test context
+ *
+ * Duplicating a CRTC state without a stream must produce a new state that
+ * carries over the DM-specific fields from the current state.
+ */
+static void dm_test_crtc_duplicate_state_copies_fields(struct kunit *test)
+{
+	struct drm_crtc *crtc;
+	struct dm_crtc_state *cur;
+	struct drm_crtc_state *dup;
+	struct dm_crtc_state *dm_dup;
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+	cur = kunit_kzalloc(test, sizeof(*cur), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cur);
+
+	cur->abm_level = 3;
+	cur->active_planes = 2;
+	cur->vrr_supported = true;
+	cur->cm_has_degamma = true;
+	cur->cm_is_degamma_srgb = true;
+	cur->crc_skip_count = 7;
+	cur->mpo_requested = true;
+	crtc->state = &cur->base;
+
+	dup = amdgpu_dm_crtc_duplicate_state(crtc);
+	KUNIT_ASSERT_NOT_NULL(test, dup);
+
+	dm_dup = to_dm_crtc_state(dup);
+	KUNIT_EXPECT_EQ(test, dm_dup->abm_level, 3);
+	KUNIT_EXPECT_EQ(test, dm_dup->active_planes, 2);
+	KUNIT_EXPECT_TRUE(test, dm_dup->vrr_supported);
+	KUNIT_EXPECT_TRUE(test, dm_dup->cm_has_degamma);
+	KUNIT_EXPECT_TRUE(test, dm_dup->cm_is_degamma_srgb);
+	KUNIT_EXPECT_EQ(test, dm_dup->crc_skip_count, 7);
+	KUNIT_EXPECT_TRUE(test, dm_dup->mpo_requested);
+
+	amdgpu_dm_crtc_destroy_state(crtc, dup);
+}
+
+/* Tests for amdgpu_dm_crtc_reset_state() */
+
+/**
+ * dm_test_crtc_reset_state_allocates_state - Test reset installs a fresh state
+ * @test: The KUnit test context
+ *
+ * Resetting a CRTC with no existing state must allocate and install a new
+ * drm_crtc_state.
+ */
+static void dm_test_crtc_reset_state_allocates_state(struct kunit *test)
+{
+	struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+	struct drm_crtc *crtc;
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+	crtc->dev = &adev->ddev;
+	crtc->state = NULL;
+
+	amdgpu_dm_crtc_reset_state(crtc);
+
+	KUNIT_EXPECT_NOT_NULL(test, crtc->state);
+
+	if (crtc->state)
+		amdgpu_dm_crtc_destroy_state(crtc, crtc->state);
+}
+
+/* Tests for amdgpu_dm_crtc_destroy_state() */
+
+/**
+ * dm_test_crtc_destroy_state_no_stream - Test destroy frees a stream-less state
+ * @test: The KUnit test context
+ *
+ * Destroying a CRTC state with no stream attached must free the state without
+ * attempting to release a DC stream.
+ */
+static void dm_test_crtc_destroy_state_no_stream(struct kunit *test)
+{
+	struct dm_crtc_state *dm_state;
+
+	/* destroy_state kfree()s the state, so use a plain (unmanaged) alloc. */
+	dm_state = kzalloc_obj(*dm_state, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state);
+
+	amdgpu_dm_crtc_destroy_state(NULL, &dm_state->base);
+}
+
 static struct kunit_case amdgpu_dm_crtc_tests[] = {
 	/* amdgpu_dm_crtc_modeset_required */
 	KUNIT_CASE(dm_test_crtc_modeset_required_active_mode_changed),
@@ -488,6 +653,18 @@ static struct kunit_case amdgpu_dm_crtc_tests[] = {
 	KUNIT_CASE(dm_test_crtc_set_vupdate_irq_no_otg),
 	/* idle_create_workqueue */
 	KUNIT_CASE(dm_test_idle_create_workqueue),
+	/* amdgpu_dm_crtc_set_static_screen_optimze */
+	KUNIT_CASE(dm_test_crtc_set_static_screen_optimze_no_sr_entry),
+	/* amdgpu_dm_crtc_enable_vblank */
+	KUNIT_CASE(dm_test_crtc_enable_vblank_rejects_unconfigured),
+	/* amdgpu_dm_crtc_update_crtc_active_planes */
+	KUNIT_CASE(dm_test_crtc_update_active_planes_no_stream),
+	/* amdgpu_dm_crtc_duplicate_state */
+	KUNIT_CASE(dm_test_crtc_duplicate_state_copies_fields),
+	/* amdgpu_dm_crtc_reset_state */
+	KUNIT_CASE(dm_test_crtc_reset_state_allocates_state),
+	/* amdgpu_dm_crtc_destroy_state */
+	KUNIT_CASE(dm_test_crtc_destroy_state_no_stream),
 	{}
 };
 
-- 
2.34.1