[PATCH 36/59] drm/amd/display: Test PSR dirty rectangle filling

Alex Hung <[email protected]>
Newsgroups org.freedesktop.lists.amd-gfx
Message-ID <[email protected]>
[WHAT]
Add KUnit tests for fill_dc_dirty_rects() covering the cursor plane
bypass, the full frame update fallbacks taken on rotation, on a
damage-unaware client, on ignored damage clips, on exceeding
DC_MAX_DIRTY_RECTS and on PSR SU in auto damage mode, the verbatim copy of
damage clips, and the MPO paths for a moved plane, a flipped plane, stable
clips and clips that overflow together with the bounding boxes.

Assisted-by: Copilot:Claude-Opus-5 GPT-5.6-Sol
Reviewed-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Alex Hung <[email protected]>
---
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c |  15 +-
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h |   8 +
 .../display/amdgpu_dm/tests/amdgpu_dm_test.c  | 349 ++++++++++++++++++
 3 files changed, 365 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 70f50fa17366..17425791e0ee 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -3147,13 +3147,13 @@ static inline void fill_dc_dirty_rect(struct drm_plane *plane,
  * implicitly provide damage clips without any client support via the plane
  * bounds.
  */
-static void fill_dc_dirty_rects(struct drm_plane *plane,
-				struct drm_plane_state *old_plane_state,
-				struct drm_plane_state *new_plane_state,
-				struct drm_crtc_state *crtc_state,
-				struct dc_flip_addrs *flip_addrs,
-				bool is_psr_su,
-				bool *dirty_regions_changed)
+STATIC_IFN_KUNIT void fill_dc_dirty_rects(struct drm_plane *plane,
+					  struct drm_plane_state *old_plane_state,
+					  struct drm_plane_state *new_plane_state,
+					  struct drm_crtc_state *crtc_state,
+					  struct dc_flip_addrs *flip_addrs,
+					  bool is_psr_su,
+					  bool *dirty_regions_changed)
 {
 	struct dm_crtc_state *dm_crtc_state = to_dm_crtc_state(crtc_state);
 	struct rect *dirty_rects = flip_addrs->dirty_rects;
@@ -3259,6 +3259,7 @@ static void fill_dc_dirty_rects(struct drm_plane *plane,
 			   dm_crtc_state->base.mode.crtc_vdisplay,
 			   &flip_addrs->dirty_rect_count, true);
 }
+EXPORT_IF_KUNIT(fill_dc_dirty_rects);
 
 static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state,
 					    struct dc_state *dc_state,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index ca19977cf788..2e3ad13b773a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1172,6 +1172,14 @@ struct dm_atomic_state *dm_atomic_get_new_state(struct drm_atomic_commit *state)
 struct drm_private_state *dm_atomic_duplicate_state(struct drm_private_obj *obj);
 void dm_atomic_destroy_state(struct drm_private_obj *obj,
 			     struct drm_private_state *state);
+struct dc_flip_addrs;
+void fill_dc_dirty_rects(struct drm_plane *plane,
+			 struct drm_plane_state *old_plane_state,
+			 struct drm_plane_state *new_plane_state,
+			 struct drm_crtc_state *crtc_state,
+			 struct dc_flip_addrs *flip_addrs,
+			 bool is_psr_su,
+			 bool *dirty_regions_changed);
 void dm_update_pflip_irq_state(struct amdgpu_device *adev,
 			       struct amdgpu_crtc *acrtc);
 void amdgpu_dm_commit_cursors(struct drm_atomic_commit *state);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index 0c57b3cc47fe..f5f2b063b355 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -1937,6 +1937,343 @@ static void dm_test_mem_type_changed_different_domain(struct kunit *test)
 							       ctx->crtc_state));
 }
 
+/* Tests for fill_dc_dirty_rects() */
+
+struct dm_test_dirty_ctx {
+	struct amdgpu_device *adev;
+	struct drm_plane *plane;
+	struct drm_plane_state *old_plane_state;
+	struct drm_plane_state *new_plane_state;
+	struct dm_crtc_state *crtc_state;
+	struct dc_flip_addrs *flip_addrs;
+	bool dirty_regions_changed;
+};
+
+static struct dm_test_dirty_ctx *dm_test_dirty_ctx_alloc(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	ctx->adev = dm_kunit_alloc_adev(test);
+	ctx->plane = drm_kunit_helper_create_primary_plane(test, &ctx->adev->ddev,
+							   NULL, NULL, NULL, 0, NULL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->plane);
+	drm_plane_enable_fb_damage_clips(ctx->plane);
+	ctx->old_plane_state = kunit_kzalloc(test, sizeof(*ctx->old_plane_state),
+					     GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->old_plane_state);
+	ctx->new_plane_state = kunit_kzalloc(test, sizeof(*ctx->new_plane_state),
+					     GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->new_plane_state);
+	ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state);
+	ctx->flip_addrs = kunit_kzalloc(test, sizeof(*ctx->flip_addrs), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx->flip_addrs);
+
+	ctx->plane->type = DRM_PLANE_TYPE_PRIMARY;
+	ctx->new_plane_state->plane = ctx->plane;
+	ctx->new_plane_state->rotation = DRM_MODE_ROTATE_0;
+	ctx->old_plane_state->plane = ctx->plane;
+	ctx->crtc_state->base.mode.crtc_hdisplay = 1920;
+	ctx->crtc_state->base.mode.crtc_vdisplay = 1080;
+
+	return ctx;
+}
+
+static void dm_test_fill_dirty_rects(struct dm_test_dirty_ctx *ctx, bool is_psr_su)
+{
+	fill_dc_dirty_rects(ctx->plane, ctx->old_plane_state,
+			    ctx->new_plane_state, &ctx->crtc_state->base,
+			    ctx->flip_addrs, is_psr_su,
+			    &ctx->dirty_regions_changed);
+}
+
+/*
+ * Attach @count damage clips to the new plane state. The blob is only ever
+ * read through drm_plane_get_damage_clips(), so a bare blob is enough.
+ */
+static struct drm_mode_rect *dm_test_add_damage_clips(struct kunit *test,
+						      struct drm_plane_state *state,
+						      unsigned int count)
+{
+	struct drm_property_blob *blob;
+	struct drm_mode_rect *clips;
+
+	blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, blob);
+	clips = kunit_kcalloc(test, count, sizeof(*clips), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, clips);
+
+	blob->length = count * sizeof(*clips);
+	blob->data = clips;
+	state->fb_damage_clips = blob;
+
+	return clips;
+}
+
+/**
+ * dm_test_dirty_rects_cursor_plane - Test cursor planes are left to their own path
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_cursor_plane(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+
+	ctx->plane->type = DRM_PLANE_TYPE_CURSOR;
+
+	dm_test_fill_dirty_rects(ctx, true);
+
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 0U);
+}
+
+/**
+ * dm_test_dirty_rects_rotation_ffu - Test a rotated plane falls back to full frame update
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_rotation_ffu(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+
+	ctx->new_plane_state->rotation = DRM_MODE_ROTATE_90;
+
+	dm_test_fill_dirty_rects(ctx, true);
+
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].x, 0);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].y, 0);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 1920);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].height, 1080);
+}
+
+/**
+ * dm_test_dirty_rects_no_clips_ffu - Test a damage-unaware client gets a full frame update
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_no_clips_ffu(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+
+	dm_test_fill_dirty_rects(ctx, true);
+
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 1920);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].height, 1080);
+}
+
+/**
+ * dm_test_dirty_rects_ignored_damage_clips - Test ignored damage clips force a full update
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_ignored_damage_clips(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+
+	dm_test_add_damage_clips(test, ctx->new_plane_state, 1);
+	ctx->new_plane_state->ignore_damage_clips = true;
+
+	dm_test_fill_dirty_rects(ctx, true);
+
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 1920);
+}
+
+/**
+ * dm_test_dirty_rects_too_many_clips_ffu - Test exceeding DC_MAX_DIRTY_RECTS falls back
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_too_many_clips_ffu(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+
+	dm_test_add_damage_clips(test, ctx->new_plane_state,
+				 DC_MAX_DIRTY_RECTS + 1);
+
+	dm_test_fill_dirty_rects(ctx, false);
+
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 1920);
+}
+
+/**
+ * dm_test_dirty_rects_damage_clips - Test damage clips are copied verbatim
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_damage_clips(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+	struct drm_mode_rect *clips;
+
+	clips = dm_test_add_damage_clips(test, ctx->new_plane_state, 2);
+	clips[0].x1 = 10;
+	clips[0].y1 = 20;
+	clips[0].x2 = 40;
+	clips[0].y2 = 60;
+	clips[1].x1 = 100;
+	clips[1].y1 = 200;
+	clips[1].x2 = 150;
+	clips[1].y2 = 260;
+
+	dm_test_fill_dirty_rects(ctx, false);
+
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 2U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].x, 10);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].y, 20);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 30);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].height, 40);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[1].x, 100);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[1].width, 50);
+	KUNIT_EXPECT_FALSE(test, ctx->dirty_regions_changed);
+}
+
+/**
+ * dm_test_dirty_rects_mpo_bb_changed - Test MPO adds both plane bounding boxes
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_mpo_bb_changed(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+	struct drm_framebuffer *fb;
+
+	fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, fb);
+
+	ctx->crtc_state->mpo_requested = true;
+	ctx->old_plane_state->fb = fb;
+	ctx->new_plane_state->fb = fb;
+	ctx->old_plane_state->crtc_x = 0;
+	ctx->old_plane_state->crtc_y = 0;
+	ctx->old_plane_state->crtc_w = 640;
+	ctx->old_plane_state->crtc_h = 480;
+	ctx->new_plane_state->crtc_x = 100;
+	ctx->new_plane_state->crtc_y = 50;
+	ctx->new_plane_state->crtc_w = 800;
+	ctx->new_plane_state->crtc_h = 600;
+
+	dm_test_fill_dirty_rects(ctx, true);
+
+	KUNIT_EXPECT_TRUE(test, ctx->dirty_regions_changed);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 2U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].x, 100);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 800);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[1].x, 0);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[1].width, 640);
+}
+
+/**
+ * dm_test_dirty_rects_mpo_fb_changed - Test MPO flips add the new plane bounding box
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_mpo_fb_changed(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+	struct drm_framebuffer *old_fb;
+	struct drm_framebuffer *new_fb;
+
+	old_fb = kunit_kzalloc(test, sizeof(*old_fb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, old_fb);
+	new_fb = kunit_kzalloc(test, sizeof(*new_fb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, new_fb);
+
+	old_fb->base.id = 1;
+	new_fb->base.id = 2;
+	ctx->crtc_state->mpo_requested = true;
+	ctx->old_plane_state->fb = old_fb;
+	ctx->new_plane_state->fb = new_fb;
+	ctx->new_plane_state->crtc_w = 800;
+	ctx->new_plane_state->crtc_h = 600;
+	ctx->old_plane_state->crtc_w = 800;
+	ctx->old_plane_state->crtc_h = 600;
+
+	dm_test_fill_dirty_rects(ctx, true);
+
+	KUNIT_EXPECT_FALSE(test, ctx->dirty_regions_changed);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 800);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].height, 600);
+}
+
+/**
+ * dm_test_dirty_rects_psr_su_ffu - Test PSR SU ignores clips in auto damage mode
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_psr_su_ffu(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+
+	dm_test_add_damage_clips(test, ctx->new_plane_state, 1);
+
+	dm_test_fill_dirty_rects(ctx, true);
+
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 1920);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].height, 1080);
+}
+
+/**
+ * dm_test_dirty_rects_mpo_clips - Test MPO copies damage clips when the box is stable
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_mpo_clips(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+	struct drm_mode_rect *clips;
+	struct drm_framebuffer *fb;
+
+	fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, fb);
+
+	clips = dm_test_add_damage_clips(test, ctx->new_plane_state, 2);
+	clips[0].x1 = 5;
+	clips[0].y1 = 6;
+	clips[0].x2 = 25;
+	clips[0].y2 = 36;
+	clips[1].x1 = 50;
+	clips[1].y1 = 60;
+	clips[1].x2 = 90;
+	clips[1].y2 = 110;
+
+	ctx->crtc_state->mpo_requested = true;
+	ctx->old_plane_state->fb = fb;
+	ctx->new_plane_state->fb = fb;
+
+	dm_test_fill_dirty_rects(ctx, false);
+
+	KUNIT_EXPECT_FALSE(test, ctx->dirty_regions_changed);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 2U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].x, 5);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 20);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[1].x, 50);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[1].height, 50);
+}
+
+/**
+ * dm_test_dirty_rects_mpo_overflow_ffu - Test MPO falls back when clips plus boxes overflow
+ * @test: The KUnit test context
+ */
+static void dm_test_dirty_rects_mpo_overflow_ffu(struct kunit *test)
+{
+	struct dm_test_dirty_ctx *ctx = dm_test_dirty_ctx_alloc(test);
+	struct drm_framebuffer *fb;
+
+	fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, fb);
+
+	dm_test_add_damage_clips(test, ctx->new_plane_state, 2);
+
+	ctx->crtc_state->mpo_requested = true;
+	ctx->old_plane_state->fb = fb;
+	ctx->new_plane_state->fb = fb;
+	ctx->new_plane_state->crtc_x = 100;
+
+	dm_test_fill_dirty_rects(ctx, false);
+
+	KUNIT_EXPECT_TRUE(test, ctx->dirty_regions_changed);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rect_count, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->flip_addrs->dirty_rects[0].width, 1920);
+}
+
 static struct kunit_case amdgpu_dm_tests[] = {
 	/* Simple DM callbacks */
 	KUNIT_CASE(dm_test_wait_for_idle),
@@ -2038,6 +2375,18 @@ static struct kunit_case amdgpu_dm_tests[] = {
 	KUNIT_CASE(dm_test_mem_type_changed_missing_state),
 	KUNIT_CASE(dm_test_mem_type_changed_same_domain),
 	KUNIT_CASE(dm_test_mem_type_changed_different_domain),
+	/* fill_dc_dirty_rects */
+	KUNIT_CASE(dm_test_dirty_rects_cursor_plane),
+	KUNIT_CASE(dm_test_dirty_rects_rotation_ffu),
+	KUNIT_CASE(dm_test_dirty_rects_no_clips_ffu),
+	KUNIT_CASE(dm_test_dirty_rects_ignored_damage_clips),
+	KUNIT_CASE(dm_test_dirty_rects_too_many_clips_ffu),
+	KUNIT_CASE(dm_test_dirty_rects_damage_clips),
+	KUNIT_CASE(dm_test_dirty_rects_mpo_bb_changed),
+	KUNIT_CASE(dm_test_dirty_rects_mpo_fb_changed),
+	KUNIT_CASE(dm_test_dirty_rects_psr_su_ffu),
+	KUNIT_CASE(dm_test_dirty_rects_mpo_clips),
+	KUNIT_CASE(dm_test_dirty_rects_mpo_overflow_ffu),
 	{}
 };
 
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.