[PATCH 09/34] drm/amd/display: Add KUnit tests for amdgpu_dm_crtc_helper_atomic_check

Tom Chung <[email protected]> Wed, 5 Aug 2026 14:36:21 +0800
Newsgroups org.freedesktop.lists.amd-gfx
Message-ID <[email protected]>
From: Bhawanpreet Lakha <[email protected]>

why:
Expand CRTC KUnit coverage to the atomic_check helper so its branch
behaviour is exercised and protected against regressions.

how:
Expose amdgpu_dm_crtc_helper_atomic_check for KUnit (STATIC_IFN_KUNIT +
EXPORT_IF_KUNIT + header declaration). Add tests covering the no-stream
(reset) pass, enabled-CRTC-without-primary rejection, async-flip non-fast
rejection, the VRR primary-plane pull path, and DC stream validation
pass/fail using timing_generator and link_service stubs.

Reviewed-by: Alex Hung <[email protected]>
Signed-off-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Tom Chung <[email protected]>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c    |   3 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_crtc.h    |   2 +
 .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c     | 345 ++++++++++++++++++
 3 files changed, 349 insertions(+), 1 deletion(-)

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 b640e4cc37c3..261bf05c2084 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
@@ -646,7 +646,7 @@ STATIC_IFN_KUNIT bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc,
 }
 EXPORT_IF_KUNIT(amdgpu_dm_crtc_helper_mode_fixup);
 
-static int amdgpu_dm_crtc_helper_atomic_check(struct drm_crtc *crtc,
+STATIC_IFN_KUNIT int amdgpu_dm_crtc_helper_atomic_check(struct drm_crtc *crtc,
 					      struct drm_atomic_commit *state)
 {
 	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
@@ -708,6 +708,7 @@ static int amdgpu_dm_crtc_helper_atomic_check(struct drm_crtc *crtc,
 	DRM_DEBUG_ATOMIC("Failed DC stream validation\n");
 	return ret;
 }
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_helper_atomic_check);
 
 static const struct drm_crtc_helper_funcs amdgpu_dm_crtc_helper_funcs = {
 	.disable = amdgpu_dm_crtc_helper_disable,
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 a2b105a496e0..7de79796a56d 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
@@ -56,6 +56,8 @@ void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc,
 					      struct drm_crtc_state *new_crtc_state);
 void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work);
 void amdgpu_dm_idle_worker(struct work_struct *work);
+int amdgpu_dm_crtc_helper_atomic_check(struct drm_crtc *crtc,
+				       struct drm_atomic_commit *state);
 #ifdef CONFIG_DEBUG_FS
 int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc);
 #endif
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 1a48c6fea8d8..a88175d99092 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
@@ -15,6 +15,8 @@
 #include "dc.h"
 #include "inc/core_types.h"
 #include "irq/irq_service.h"
+#include "inc/hw/timing_generator.h"
+#include "inc/link_service.h"
 #include "amdgpu.h"
 #include "amdgpu_mode.h"
 #include "amdgpu_dm.h"
@@ -2136,6 +2138,342 @@ static void dm_test_crtc_init_enables_degamma(struct kunit *test)
 		cursor->funcs->destroy(cursor);
 }
 
+/* Tests for amdgpu_dm_crtc_helper_atomic_check() */
+
+/**
+ * dm_test_crtc_atomic_check_no_stream_passes - Test atomic_check with no stream
+ * @test: The KUnit test context
+ *
+ * A CRTC state without a stream (as during reset) and with no modeset
+ * requirement must pass atomic_check and return 0 before reaching DC stream
+ * validation.
+ */
+static void dm_test_crtc_atomic_check_no_stream_passes(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct dm_crtc_state *dm_crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_crtc *crtc;
+	int ret;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+	crtc->dev = &adev->ddev;
+
+	dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_crtc_state);
+	dm_crtc_state->base.crtc = crtc;
+
+	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+	state->dev = &adev->ddev;
+	state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state->crtcs);
+	state->crtcs[0].new_state = &dm_crtc_state->base;
+	dm_crtc_state->base.state = state;
+
+	ret = amdgpu_dm_crtc_helper_atomic_check(crtc, state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+/* Stubs for the DC stream validation branches of atomic_check. */
+static bool dm_test_atomic_check_validate_timing_ok(struct timing_generator *tg,
+						    const struct dc_crtc_timing *timing)
+{
+	return true;
+}
+
+static bool dm_test_atomic_check_validate_timing_fail(struct timing_generator *tg,
+						      const struct dc_crtc_timing *timing)
+{
+	return false;
+}
+
+static enum dc_status dm_test_atomic_check_validate_mode_timing_ok(const struct dc_stream_state *stream,
+								  struct dc_link *link,
+								  const struct dc_crtc_timing *timing)
+{
+	return DC_OK;
+}
+
+/**
+ * dm_test_crtc_atomic_check_enable_without_primary_fails - Enabled CRTC needs primary
+ * @test: The KUnit test context
+ *
+ * An enabled CRTC whose primary plane is not part of the plane mask must be
+ * rejected with -EINVAL.
+ */
+static void dm_test_crtc_atomic_check_enable_without_primary_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct dm_crtc_state *dm_crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_plane *primary;
+	struct drm_crtc *crtc;
+	int ret;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+	primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary);
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+	crtc->dev = &adev->ddev;
+	crtc->primary = primary;
+
+	dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_crtc_state);
+	dm_crtc_state->base.crtc = crtc;
+	dm_crtc_state->base.enable = true;
+	dm_crtc_state->base.plane_mask = 0;
+
+	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+	state->dev = &adev->ddev;
+	state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state->crtcs);
+	state->crtcs[0].new_state = &dm_crtc_state->base;
+	dm_crtc_state->base.state = state;
+
+	ret = amdgpu_dm_crtc_helper_atomic_check(crtc, state);
+
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+/**
+ * dm_test_crtc_atomic_check_async_flip_non_fast_fails - Async flip needs fast update
+ * @test: The KUnit test context
+ *
+ * An async flip is only permitted for fast updates; a non-fast update type must
+ * be rejected with -EINVAL.
+ */
+static void dm_test_crtc_atomic_check_async_flip_non_fast_fails(struct kunit *test)
+{
+	struct amdgpu_device *adev;
+	struct dm_crtc_state *dm_crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_crtc *crtc;
+	int ret;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+	crtc->dev = &adev->ddev;
+
+	dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_crtc_state);
+	dm_crtc_state->base.crtc = crtc;
+	dm_crtc_state->base.async_flip = true;
+	dm_crtc_state->update_type = UPDATE_TYPE_FULL;
+
+	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+	state->dev = &adev->ddev;
+	state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state->crtcs);
+	state->crtcs[0].new_state = &dm_crtc_state->base;
+	dm_crtc_state->base.state = state;
+
+	ret = amdgpu_dm_crtc_helper_atomic_check(crtc, state);
+
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+/**
+ * dm_test_crtc_atomic_check_vrr_pulls_primary - VRR path pulls in primary plane
+ * @test: The KUnit test context
+ *
+ * When VRR is active and the update is not a legacy cursor update, atomic_check
+ * pulls in the primary plane state. With an existing primary plane state already
+ * present and no stream attached, the check returns 0.
+ */
+static void dm_test_crtc_atomic_check_vrr_pulls_primary(struct kunit *test)
+{
+	struct drm_plane_state *primary_plane_state;
+	struct amdgpu_device *adev;
+	struct dm_crtc_state *dm_crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_plane *primary;
+	struct drm_crtc *crtc;
+	int ret;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+	primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary);
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+	crtc->dev = &adev->ddev;
+	crtc->primary = primary;
+
+	dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_crtc_state);
+	dm_crtc_state->base.crtc = crtc;
+	dm_crtc_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
+
+	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+	state->dev = &adev->ddev;
+	state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state->crtcs);
+	state->crtcs[0].new_state = &dm_crtc_state->base;
+	dm_crtc_state->base.state = state;
+
+	/* acquire ctx must be set; existing plane state avoids taking a lock. */
+	state->acquire_ctx = kunit_kzalloc(test, sizeof(*state->acquire_ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state->acquire_ctx);
+	state->planes = kunit_kzalloc(test, sizeof(*state->planes), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state->planes);
+	primary_plane_state = kunit_kzalloc(test, sizeof(*primary_plane_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary_plane_state);
+	state->planes[0].new_state = primary_plane_state;
+
+	ret = amdgpu_dm_crtc_helper_atomic_check(crtc, state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+/**
+ * dm_test_crtc_atomic_check_valid_stream_passes - Valid DC stream passes
+ * @test: The KUnit test context
+ *
+ * With a stream attached and DC stream validation returning DC_OK, atomic_check
+ * must return 0.
+ */
+static void dm_test_crtc_atomic_check_valid_stream_passes(struct kunit *test)
+{
+	struct timing_generator_funcs *tg_funcs;
+	struct timing_generator *tg;
+	struct resource_pool *res_pool;
+	struct link_service *link_srv;
+	struct amdgpu_device *adev;
+	struct dm_crtc_state *dm_crtc_state;
+	struct drm_atomic_commit *state;
+	struct dc_stream_state *stream;
+	struct dc_link *link;
+	struct drm_crtc *crtc;
+	struct dc *dc;
+	int ret;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+	dc = dm_kunit_alloc_dc_with_ctx(test);
+	res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool);
+	tg = kunit_kzalloc(test, sizeof(*tg), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tg);
+	tg_funcs = kunit_kzalloc(test, sizeof(*tg_funcs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tg_funcs);
+	tg_funcs->validate_timing = dm_test_atomic_check_validate_timing_ok;
+	tg->funcs = tg_funcs;
+	res_pool->timing_generators[0] = tg;
+	dc->res_pool = res_pool;
+
+	link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv);
+	link_srv->validate_mode_timing = dm_test_atomic_check_validate_mode_timing_ok;
+	dc->link_srv = link_srv;
+	adev->dm.dc = dc;
+
+	link = dm_kunit_alloc_link(test);
+	link->ep_type = DISPLAY_ENDPOINT_UNKNOWN;
+	stream = dm_kunit_alloc_stream(test, link);
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+	crtc->dev = &adev->ddev;
+
+	dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_crtc_state);
+	dm_crtc_state->base.crtc = crtc;
+	dm_crtc_state->stream = stream;
+
+	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+	state->dev = &adev->ddev;
+	state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state->crtcs);
+	state->crtcs[0].new_state = &dm_crtc_state->base;
+	dm_crtc_state->base.state = state;
+
+	ret = amdgpu_dm_crtc_helper_atomic_check(crtc, state);
+
+	KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+/**
+ * dm_test_crtc_atomic_check_invalid_stream_fails - Invalid DC stream fails
+ * @test: The KUnit test context
+ *
+ * With a stream attached but DC stream validation failing (timing rejected),
+ * atomic_check must return -EINVAL.
+ */
+static void dm_test_crtc_atomic_check_invalid_stream_fails(struct kunit *test)
+{
+	struct timing_generator_funcs *tg_funcs;
+	struct timing_generator *tg;
+	struct resource_pool *res_pool;
+	struct amdgpu_device *adev;
+	struct dm_crtc_state *dm_crtc_state;
+	struct drm_atomic_commit *state;
+	struct dc_stream_state *stream;
+	struct dc_link *link;
+	struct drm_crtc *crtc;
+	struct dc *dc;
+	int ret;
+
+	adev = dm_kunit_alloc_adev(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+	dc = dm_kunit_alloc_dc_with_ctx(test);
+	res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool);
+	tg = kunit_kzalloc(test, sizeof(*tg), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tg);
+	tg_funcs = kunit_kzalloc(test, sizeof(*tg_funcs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tg_funcs);
+	tg_funcs->validate_timing = dm_test_atomic_check_validate_timing_fail;
+	tg->funcs = tg_funcs;
+	res_pool->timing_generators[0] = tg;
+	dc->res_pool = res_pool;
+	adev->dm.dc = dc;
+
+	link = dm_kunit_alloc_link(test);
+	stream = dm_kunit_alloc_stream(test, link);
+
+	crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+	crtc->dev = &adev->ddev;
+
+	dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_crtc_state);
+	dm_crtc_state->base.crtc = crtc;
+	dm_crtc_state->stream = stream;
+
+	state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+	state->dev = &adev->ddev;
+	state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state->crtcs);
+	state->crtcs[0].new_state = &dm_crtc_state->base;
+	dm_crtc_state->base.state = state;
+
+	ret = amdgpu_dm_crtc_helper_atomic_check(crtc, state);
+
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
 static struct kunit_case amdgpu_dm_crtc_tests[] = {
 	/* amdgpu_dm_crtc_modeset_required */
 	KUNIT_CASE(dm_test_crtc_modeset_required_active_mode_changed),
@@ -2224,6 +2562,13 @@ static struct kunit_case amdgpu_dm_crtc_tests[] = {
 	/* amdgpu_dm_crtc_init */
 	KUNIT_CASE(dm_test_crtc_init_registers_crtc),
 	KUNIT_CASE(dm_test_crtc_init_enables_degamma),
+	/* amdgpu_dm_crtc_helper_atomic_check */
+	KUNIT_CASE(dm_test_crtc_atomic_check_no_stream_passes),
+	KUNIT_CASE(dm_test_crtc_atomic_check_enable_without_primary_fails),
+	KUNIT_CASE(dm_test_crtc_atomic_check_async_flip_non_fast_fails),
+	KUNIT_CASE(dm_test_crtc_atomic_check_vrr_pulls_primary),
+	KUNIT_CASE(dm_test_crtc_atomic_check_valid_stream_passes),
+	KUNIT_CASE(dm_test_crtc_atomic_check_invalid_stream_fails),
 	{}
 };
 
-- 
2.43.0