[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