| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Test cases for the drm_plane_helper functions | |
| * | |
| * Copyright (c) 2022 MaĆra Canal <mairacanal@riseup.net> | |
| */ | |
| #include <kunit/test.h> | |
| #include <drm/drm_atomic_helper.h> | |
| #include <drm/drm_framebuffer.h> | |
| #include <drm/drm_modes.h> | |
| #include <drm/drm_rect.h> | |
| static const struct drm_crtc_state crtc_state = { | |
| .crtc = ZERO_SIZE_PTR, | |
| .enable = true, | |
| .active = true, | |
| .mode = { | |
| DRM_MODE("1024x768", 0, 65000, 1024, 1048, | |
| 1184, 1344, 0, 768, 771, 777, 806, 0, | |
| DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) | |
| }, | |
| }; | |
| struct drm_check_plane_state_test { | |
| const char *name; | |
| const char *msg; | |
| struct { | |
| unsigned int x; | |
| unsigned int y; | |
| unsigned int w; | |
| unsigned int h; | |
| } src, src_expected; | |
| struct { | |
| int x; | |
| int y; | |
| unsigned int w; | |
| unsigned int h; | |
| } crtc, crtc_expected; | |
| unsigned int rotation; | |
| int min_scale; | |
| int max_scale; | |
| bool can_position; | |
| }; | |
| static int drm_plane_helper_init(struct kunit *test) | |
| { | |
| const struct drm_check_plane_state_test *params = test->param_value; | |
| struct drm_plane *plane; | |
| struct drm_framebuffer *fb; | |
| struct drm_plane_state *mock; | |
| plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); | |
| KUNIT_ASSERT_NOT_NULL(test, plane); | |
| fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); | |
| KUNIT_ASSERT_NOT_NULL(test, fb); | |
| fb->width = 2048; | |
| fb->height = 2048; | |
| mock = kunit_kzalloc(test, sizeof(*mock), GFP_KERNEL); | |
| KUNIT_ASSERT_NOT_NULL(test, mock); | |
| mock->plane = plane; | |
| mock->crtc = ZERO_SIZE_PTR; | |
| mock->fb = fb; | |
| mock->rotation = params->rotation; | |
| mock->src_x = params->src.x; | |
| mock->src_y = params->src.y; | |
| mock->src_w = params->src.w; | |
| mock->src_h = params->src.h; | |
| mock->crtc_x = params->crtc.x; | |
| mock->crtc_y = params->crtc.y; | |
| mock->crtc_w = params->crtc.w; | |
| mock->crtc_h = params->crtc.h; | |
| test->priv = mock; | |
| return 0; | |
| } | |
| static void check_src_eq(struct kunit *test, struct drm_plane_state *plane_state, | |
| unsigned int src_x, unsigned int src_y, | |
| unsigned int src_w, unsigned int src_h) | |
| { | |
| struct drm_rect expected = DRM_RECT_INIT(src_x, src_y, src_w, src_h); | |
| KUNIT_ASSERT_GE_MSG(test, plane_state->src.x1, 0, | |
| "src x coordinate %x should never be below 0, src: " DRM_RECT_FP_FMT, | |
| plane_state->src.x1, DRM_RECT_FP_ARG(&plane_state->src)); | |
| KUNIT_ASSERT_GE_MSG(test, plane_state->src.y1, 0, | |
| "src y coordinate %x should never be below 0, src: " DRM_RECT_FP_FMT, | |
| plane_state->src.y1, DRM_RECT_FP_ARG(&plane_state->src)); | |
| KUNIT_EXPECT_TRUE_MSG(test, drm_rect_equals(&plane_state->src, &expected), | |
| "dst: " DRM_RECT_FP_FMT ", expected: " DRM_RECT_FP_FMT, | |
| DRM_RECT_FP_ARG(&plane_state->src), DRM_RECT_FP_ARG(&expected)); | |
| } | |
| static void check_crtc_eq(struct kunit *test, struct drm_plane_state *plane_state, | |
| int crtc_x, int crtc_y, | |
| unsigned int crtc_w, unsigned int crtc_h) | |
| { | |
| struct drm_rect expected = DRM_RECT_INIT(crtc_x, crtc_y, crtc_w, crtc_h); | |
| KUNIT_EXPECT_TRUE_MSG(test, drm_rect_equals(&plane_state->dst, &expected), | |
| "dst: " DRM_RECT_FMT ", expected: " DRM_RECT_FMT, | |
| DRM_RECT_ARG(&plane_state->dst), DRM_RECT_ARG(&expected)); | |
| } | |
| static void drm_test_check_plane_state(struct kunit *test) | |
| { | |
| const struct drm_check_plane_state_test *params = test->param_value; | |
| struct drm_plane_state *plane_state = test->priv; | |
| KUNIT_ASSERT_EQ_MSG(test, | |
| drm_atomic_helper_check_plane_state(plane_state, &crtc_state, | |
| params->min_scale, | |
| params->max_scale, | |
| params->can_position, false), | |
| 0, params->msg); | |
| KUNIT_EXPECT_TRUE(test, plane_state->visible); | |
| check_src_eq(test, plane_state, params->src_expected.x, params->src_expected.y, | |
| params->src_expected.w, params->src_expected.h); | |
| check_crtc_eq(test, plane_state, params->crtc_expected.x, params->crtc_expected.y, | |
| params->crtc_expected.w, params->crtc_expected.h); | |
| } | |
| static void drm_check_plane_state_desc(const struct drm_check_plane_state_test *t, | |
| char *desc) | |
| { | |
| sprintf(desc, "%s", t->name); | |
| } | |
| static const struct drm_check_plane_state_test drm_check_plane_state_tests[] = { | |
| { | |
| .name = "clipping_simple", | |
| .msg = "Simple clipping check should pass", | |
| .src = { 0, 0, | |
| 2048 << 16, | |
| 2048 << 16 }, | |
| .crtc = { 0, 0, 2048, 2048 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = DRM_PLANE_NO_SCALING, | |
| .max_scale = DRM_PLANE_NO_SCALING, | |
| .can_position = false, | |
| .src_expected = { 0, 0, 1024 << 16, 768 << 16 }, | |
| .crtc_expected = { 0, 0, 1024, 768 }, | |
| }, | |
| { | |
| .name = "clipping_rotate_reflect", | |
| .msg = "Rotated clipping check should pass", | |
| .src = { 0, 0, | |
| 2048 << 16, | |
| 2048 << 16 }, | |
| .crtc = { 0, 0, 2048, 2048 }, | |
| .rotation = DRM_MODE_ROTATE_90 | DRM_MODE_REFLECT_X, | |
| .min_scale = DRM_PLANE_NO_SCALING, | |
| .max_scale = DRM_PLANE_NO_SCALING, | |
| .can_position = false, | |
| .src_expected = { 0, 0, 768 << 16, 1024 << 16 }, | |
| .crtc_expected = { 0, 0, 1024, 768 }, | |
| }, | |
| { | |
| .name = "positioning_simple", | |
| .msg = "Simple positioning should work", | |
| .src = { 0, 0, 1023 << 16, 767 << 16 }, | |
| .crtc = { 0, 0, 1023, 767 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = DRM_PLANE_NO_SCALING, | |
| .max_scale = DRM_PLANE_NO_SCALING, | |
| .can_position = true, | |
| .src_expected = { 0, 0, 1023 << 16, 767 << 16 }, | |
| .crtc_expected = { 0, 0, 1023, 767 }, | |
| }, | |
| { | |
| .name = "upscaling", | |
| .msg = "Upscaling exactly 2x should work", | |
| .src = { 0, 0, 512 << 16, 384 << 16 }, | |
| .crtc = { 0, 0, 1024, 768 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = 0x8000, | |
| .max_scale = DRM_PLANE_NO_SCALING, | |
| .can_position = false, | |
| .src_expected = { 0, 0, 512 << 16, 384 << 16 }, | |
| .crtc_expected = { 0, 0, 1024, 768 }, | |
| }, | |
| { | |
| .name = "downscaling", | |
| .msg = "Should succeed with exact scaling limit", | |
| .src = { 0, 0, 2048 << 16, 1536 << 16 }, | |
| .crtc = { 0, 0, 1024, 768 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = DRM_PLANE_NO_SCALING, | |
| .max_scale = 0x20000, | |
| .can_position = false, | |
| .src_expected = { 0, 0, 2048 << 16, 1536 << 16 }, | |
| .crtc_expected = { 0, 0, 1024, 768 }, | |
| }, | |
| { | |
| .name = "rounding1", | |
| .msg = "Should succeed by clipping to exact multiple", | |
| .src = { 0, 0, 0x40001, 0x40001 }, | |
| .crtc = { 1022, 766, 4, 4 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = DRM_PLANE_NO_SCALING, | |
| .max_scale = 0x10001, | |
| .can_position = true, | |
| .src_expected = { 0, 0, 2 << 16, 2 << 16 }, | |
| .crtc_expected = { 1022, 766, 2, 2 }, | |
| }, | |
| { | |
| .name = "rounding2", | |
| .msg = "Should succeed by clipping to exact multiple", | |
| .src = { 0x20001, 0x20001, 0x4040001, 0x3040001 }, | |
| .crtc = { -2, -2, 1028, 772 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = DRM_PLANE_NO_SCALING, | |
| .max_scale = 0x10001, | |
| .can_position = false, | |
| .src_expected = { 0x40002, 0x40002, 1024 << 16, 768 << 16 }, | |
| .crtc_expected = { 0, 0, 1024, 768 }, | |
| }, | |
| { | |
| .name = "rounding3", | |
| .msg = "Should succeed by clipping to exact multiple", | |
| .src = { 0, 0, 0x3ffff, 0x3ffff }, | |
| .crtc = { 1022, 766, 4, 4 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = 0xffff, | |
| .max_scale = DRM_PLANE_NO_SCALING, | |
| .can_position = true, | |
| /* Should not be rounded to 0x20001, which would be upscaling. */ | |
| .src_expected = { 0, 0, 2 << 16, 2 << 16 }, | |
| .crtc_expected = { 1022, 766, 2, 2 }, | |
| }, | |
| { | |
| .name = "rounding4", | |
| .msg = "Should succeed by clipping to exact multiple", | |
| .src = { 0x1ffff, 0x1ffff, 0x403ffff, 0x303ffff }, | |
| .crtc = { -2, -2, 1028, 772 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = 0xffff, | |
| .max_scale = DRM_PLANE_NO_SCALING, | |
| .can_position = false, | |
| .src_expected = { 0x3fffe, 0x3fffe, 1024 << 16, 768 << 16 }, | |
| .crtc_expected = { 0, 0, 1024, 768 }, | |
| }, | |
| }; | |
| KUNIT_ARRAY_PARAM(drm_check_plane_state, drm_check_plane_state_tests, drm_check_plane_state_desc); | |
| static void drm_test_check_invalid_plane_state(struct kunit *test) | |
| { | |
| const struct drm_check_plane_state_test *params = test->param_value; | |
| struct drm_plane_state *plane_state = test->priv; | |
| KUNIT_ASSERT_LT_MSG(test, | |
| drm_atomic_helper_check_plane_state(plane_state, &crtc_state, | |
| params->min_scale, | |
| params->max_scale, | |
| params->can_position, false), | |
| 0, params->msg); | |
| } | |
| static const struct drm_check_plane_state_test drm_check_invalid_plane_state_tests[] = { | |
| { | |
| .name = "positioning_invalid", | |
| .msg = "Should not be able to position on the crtc with can_position=false", | |
| .src = { 0, 0, 1023 << 16, 767 << 16 }, | |
| .crtc = { 0, 0, 1023, 767 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = DRM_PLANE_NO_SCALING, | |
| .max_scale = DRM_PLANE_NO_SCALING, | |
| .can_position = false, | |
| }, | |
| { | |
| .name = "upscaling_invalid", | |
| .msg = "Upscaling out of range should fail", | |
| .src = { 0, 0, 512 << 16, 384 << 16 }, | |
| .crtc = { 0, 0, 1024, 768 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = 0x8001, | |
| .max_scale = DRM_PLANE_NO_SCALING, | |
| .can_position = false, | |
| }, | |
| { | |
| .name = "downscaling_invalid", | |
| .msg = "Downscaling out of range should fail", | |
| .src = { 0, 0, 2048 << 16, 1536 << 16 }, | |
| .crtc = { 0, 0, 1024, 768 }, | |
| .rotation = DRM_MODE_ROTATE_0, | |
| .min_scale = DRM_PLANE_NO_SCALING, | |
| .max_scale = 0x1ffff, | |
| .can_position = false, | |
| }, | |
| }; | |
| KUNIT_ARRAY_PARAM(drm_check_invalid_plane_state, drm_check_invalid_plane_state_tests, | |
| drm_check_plane_state_desc); | |
| static struct kunit_case drm_plane_helper_test[] = { | |
| KUNIT_CASE_PARAM(drm_test_check_plane_state, drm_check_plane_state_gen_params), | |
| KUNIT_CASE_PARAM(drm_test_check_invalid_plane_state, | |
| drm_check_invalid_plane_state_gen_params), | |
| {} | |
| }; | |
| static struct kunit_suite drm_plane_helper_test_suite = { | |
| .name = "drm_plane_helper", | |
| .init = drm_plane_helper_init, | |
| .test_cases = drm_plane_helper_test, | |
| }; | |
| kunit_test_suite(drm_plane_helper_test_suite); | |
| MODULE_DESCRIPTION("Test cases for the drm_plane_helper functions"); | |
| MODULE_LICENSE("GPL"); |