| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Test cases for the drm_atomic_state helpers |
| * |
| * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. |
| */ |
| |
| #include <drm/drm_atomic.h> |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_atomic_uapi.h> |
| #include <drm/drm_kunit_helpers.h> |
| #include <drm/drm_probe_helper.h> |
| |
| #define DRM_TEST_ENC_0 BIT(0) |
| #define DRM_TEST_ENC_1 BIT(1) |
| #define DRM_TEST_ENC_2 BIT(2) |
| |
| #define DRM_TEST_CONN_0 BIT(0) |
| |
| struct drm_clone_mode_test { |
| const char *name; |
| u32 encoder_mask; |
| int expected_result; |
| }; |
| |
| static const struct drm_display_mode drm_atomic_test_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_atomic_test_priv { |
| struct drm_device drm; |
| struct drm_plane *plane; |
| struct drm_crtc *crtc; |
| struct drm_encoder encoders[3]; |
| struct drm_connector connectors[2]; |
| }; |
| |
| static int modeset_counter; |
| |
| static void drm_test_encoder_mode_set(struct drm_encoder *encoder, |
| struct drm_crtc_state *crtc_state, |
| struct drm_connector_state *conn_state) |
| { |
| modeset_counter++; |
| } |
| |
| static const struct drm_encoder_helper_funcs drm_atomic_test_encoder_funcs = { |
| .atomic_mode_set = drm_test_encoder_mode_set, |
| }; |
| |
| static const struct drm_connector_funcs dummy_connector_funcs = { |
| .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
| .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
| .reset = drm_atomic_helper_connector_reset, |
| }; |
| |
| static int drm_atomic_test_dummy_get_modes(struct drm_connector *connector) |
| { |
| return drm_connector_helper_get_modes_fixed(connector, |
| &drm_atomic_test_mode); |
| } |
| |
| static const struct drm_connector_helper_funcs dummy_connector_helper_funcs = { |
| .get_modes = drm_atomic_test_dummy_get_modes, |
| }; |
| |
| static struct drm_atomic_test_priv * |
| drm_atomic_test_init_drm_components(struct kunit *test, bool has_connectors) |
| { |
| struct drm_atomic_test_priv *priv; |
| struct drm_encoder *enc; |
| struct drm_connector *conn; |
| struct drm_device *drm; |
| struct device *dev; |
| int ret; |
| |
| dev = drm_kunit_helper_alloc_device(test); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); |
| |
| priv = drm_kunit_helper_alloc_drm_device(test, dev, |
| struct drm_atomic_test_priv, |
| drm, |
| DRIVER_MODESET | DRIVER_ATOMIC); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); |
| test->priv = priv; |
| |
| drm = &priv->drm; |
| priv->plane = drm_kunit_helper_create_primary_plane(test, drm, |
| NULL, |
| NULL, |
| NULL, 0, |
| NULL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->plane); |
| |
| priv->crtc = drm_kunit_helper_create_crtc(test, drm, |
| priv->plane, NULL, |
| NULL, |
| NULL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->crtc); |
| |
| for (int i = 0; i < ARRAY_SIZE(priv->encoders); i++) { |
| enc = &priv->encoders[i]; |
| |
| ret = drmm_encoder_init(drm, enc, NULL, |
| DRM_MODE_ENCODER_DSI, NULL); |
| KUNIT_ASSERT_EQ(test, ret, 0); |
| |
| enc->possible_crtcs = drm_crtc_mask(priv->crtc); |
| } |
| |
| priv->encoders[0].possible_clones = DRM_TEST_ENC_0 | DRM_TEST_ENC_1; |
| priv->encoders[1].possible_clones = DRM_TEST_ENC_0 | DRM_TEST_ENC_1; |
| priv->encoders[2].possible_clones = DRM_TEST_ENC_2; |
| |
| if (!has_connectors) |
| goto done; |
| |
| BUILD_BUG_ON(ARRAY_SIZE(priv->connectors) > ARRAY_SIZE(priv->encoders)); |
| |
| for (int i = 0; i < ARRAY_SIZE(priv->connectors); i++) { |
| conn = &priv->connectors[i]; |
| |
| ret = drmm_connector_init(drm, conn, &dummy_connector_funcs, |
| DRM_MODE_CONNECTOR_DSI, NULL); |
| KUNIT_ASSERT_EQ(test, ret, 0); |
| |
| drm_connector_helper_add(conn, &dummy_connector_helper_funcs); |
| drm_encoder_helper_add(&priv->encoders[i], |
| &drm_atomic_test_encoder_funcs); |
| |
| drm_connector_attach_encoder(conn, &priv->encoders[i]); |
| } |
| |
| done: |
| drm_mode_config_reset(drm); |
| |
| return priv; |
| } |
| |
| static int set_up_atomic_state(struct kunit *test, |
| struct drm_atomic_test_priv *priv, |
| struct drm_connector *connector, |
| struct drm_modeset_acquire_ctx *ctx) |
| { |
| struct drm_device *drm = &priv->drm; |
| struct drm_crtc *crtc = priv->crtc; |
| struct drm_atomic_state *state; |
| struct drm_connector_state *conn_state; |
| struct drm_crtc_state *crtc_state; |
| int ret; |
| |
| state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); |
| |
| if (connector) { |
| conn_state = drm_atomic_get_connector_state(state, connector); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); |
| |
| ret = drm_atomic_set_crtc_for_connector(conn_state, crtc); |
| KUNIT_EXPECT_EQ(test, ret, 0); |
| } |
| |
| crtc_state = drm_atomic_get_crtc_state(state, crtc); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); |
| |
| ret = drm_atomic_set_mode_for_crtc(crtc_state, &drm_atomic_test_mode); |
| KUNIT_EXPECT_EQ(test, ret, 0); |
| |
| crtc_state->enable = true; |
| crtc_state->active = true; |
| |
| if (connector) { |
| ret = drm_atomic_commit(state); |
| KUNIT_ASSERT_EQ(test, ret, 0); |
| } else { |
| // dummy connector mask |
| crtc_state->connector_mask = DRM_TEST_CONN_0; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Test that the DRM encoder mode_set() is called when the atomic state |
| * connectors are changed but the CRTC mode is not. |
| */ |
| static void drm_test_check_connector_changed_modeset(struct kunit *test) |
| { |
| struct drm_atomic_test_priv *priv; |
| struct drm_modeset_acquire_ctx ctx; |
| struct drm_connector *old_conn, *new_conn; |
| struct drm_atomic_state *state; |
| struct drm_device *drm; |
| struct drm_connector_state *new_conn_state, *old_conn_state; |
| int ret, initial_modeset_count; |
| |
| priv = drm_atomic_test_init_drm_components(test, true); |
| KUNIT_ASSERT_NOT_NULL(test, priv); |
| |
| drm = &priv->drm; |
| old_conn = &priv->connectors[0]; |
| new_conn = &priv->connectors[1]; |
| |
| drm_modeset_acquire_init(&ctx, 0); |
| |
| // first modeset to enable |
| ret = set_up_atomic_state(test, priv, old_conn, &ctx); |
| KUNIT_ASSERT_EQ(test, ret, 0); |
| |
| state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); |
| |
| new_conn_state = drm_atomic_get_connector_state(state, new_conn); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); |
| |
| old_conn_state = drm_atomic_get_connector_state(state, old_conn); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state); |
| |
| ret = drm_atomic_set_crtc_for_connector(old_conn_state, NULL); |
| KUNIT_EXPECT_EQ(test, ret, 0); |
| |
| ret = drm_atomic_set_crtc_for_connector(new_conn_state, priv->crtc); |
| KUNIT_EXPECT_EQ(test, ret, 0); |
| |
| initial_modeset_count = modeset_counter; |
| |
| // modeset_disables is called as part of the atomic commit tail |
| ret = drm_atomic_commit(state); |
| KUNIT_ASSERT_EQ(test, ret, 0); |
| KUNIT_ASSERT_EQ(test, modeset_counter, initial_modeset_count + 1); |
| |
| drm_modeset_drop_locks(&ctx); |
| drm_modeset_acquire_fini(&ctx); |
| } |
| |
| /* |
| * Test that the drm_crtc_in_clone_mode() helper can detect if a given CRTC |
| * state is in clone mode |
| */ |
| static void drm_test_check_in_clone_mode(struct kunit *test) |
| { |
| bool ret; |
| const struct drm_clone_mode_test *param = test->param_value; |
| struct drm_crtc_state *crtc_state; |
| |
| crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL); |
| KUNIT_ASSERT_NOT_NULL(test, crtc_state); |
| |
| crtc_state->encoder_mask = param->encoder_mask; |
| |
| ret = drm_crtc_in_clone_mode(crtc_state); |
| |
| KUNIT_ASSERT_EQ(test, ret, param->expected_result); |
| } |
| |
| /* |
| * Test that the atomic commit path will succeed for valid clones (or non-cloned |
| * states) and fail for states where the cloned encoders are not possible_clones |
| * of each other. |
| */ |
| static void drm_test_check_valid_clones(struct kunit *test) |
| { |
| int ret; |
| const struct drm_clone_mode_test *param = test->param_value; |
| struct drm_atomic_test_priv *priv; |
| struct drm_modeset_acquire_ctx ctx; |
| struct drm_device *drm; |
| struct drm_atomic_state *state; |
| struct drm_crtc_state *crtc_state; |
| |
| priv = drm_atomic_test_init_drm_components(test, false); |
| KUNIT_ASSERT_NOT_NULL(test, priv); |
| |
| drm = &priv->drm; |
| |
| drm_modeset_acquire_init(&ctx, 0); |
| |
| ret = set_up_atomic_state(test, priv, NULL, &ctx); |
| KUNIT_ASSERT_EQ(test, ret, 0); |
| |
| state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); |
| |
| crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); |
| |
| crtc_state->encoder_mask = param->encoder_mask; |
| |
| // force modeset |
| crtc_state->mode_changed = true; |
| |
| ret = drm_atomic_helper_check_modeset(drm, state); |
| KUNIT_ASSERT_EQ(test, ret, param->expected_result); |
| |
| drm_modeset_drop_locks(&ctx); |
| drm_modeset_acquire_fini(&ctx); |
| } |
| |
| static void drm_check_in_clone_mode_desc(const struct drm_clone_mode_test *t, |
| char *desc) |
| { |
| sprintf(desc, "%s", t->name); |
| } |
| |
| static void drm_check_valid_clones_desc(const struct drm_clone_mode_test *t, |
| char *desc) |
| { |
| sprintf(desc, "%s", t->name); |
| } |
| |
| static const struct drm_clone_mode_test drm_clone_mode_tests[] = { |
| { |
| .name = "in_clone_mode", |
| .encoder_mask = DRM_TEST_ENC_0 | DRM_TEST_ENC_1, |
| .expected_result = true, |
| }, |
| { |
| .name = "not_in_clone_mode", |
| .encoder_mask = DRM_TEST_ENC_0, |
| .expected_result = false, |
| }, |
| }; |
| |
| static const struct drm_clone_mode_test drm_valid_clone_mode_tests[] = { |
| { |
| .name = "not_in_clone_mode", |
| .encoder_mask = DRM_TEST_ENC_0, |
| .expected_result = 0, |
| }, |
| |
| { |
| .name = "valid_clone", |
| .encoder_mask = DRM_TEST_ENC_0 | DRM_TEST_ENC_1, |
| .expected_result = 0, |
| }, |
| { |
| .name = "invalid_clone", |
| .encoder_mask = DRM_TEST_ENC_0 | DRM_TEST_ENC_2, |
| .expected_result = -EINVAL, |
| }, |
| }; |
| |
| KUNIT_ARRAY_PARAM(drm_check_in_clone_mode, drm_clone_mode_tests, |
| drm_check_in_clone_mode_desc); |
| |
| KUNIT_ARRAY_PARAM(drm_check_valid_clones, drm_valid_clone_mode_tests, |
| drm_check_valid_clones_desc); |
| |
| static struct kunit_case drm_test_check_modeset_test[] = { |
| KUNIT_CASE(drm_test_check_connector_changed_modeset), |
| {} |
| }; |
| |
| static struct kunit_case drm_in_clone_mode_check_test[] = { |
| KUNIT_CASE_PARAM(drm_test_check_in_clone_mode, |
| drm_check_in_clone_mode_gen_params), |
| KUNIT_CASE_PARAM(drm_test_check_valid_clones, |
| drm_check_valid_clones_gen_params), |
| {} |
| }; |
| |
| static struct kunit_suite drm_test_check_modeset_test_suite = { |
| .name = "drm_validate_modeset", |
| .test_cases = drm_test_check_modeset_test, |
| }; |
| |
| static struct kunit_suite drm_in_clone_mode_check_test_suite = { |
| .name = "drm_validate_clone_mode", |
| .test_cases = drm_in_clone_mode_check_test, |
| }; |
| |
| kunit_test_suites(&drm_in_clone_mode_check_test_suite, |
| &drm_test_check_modeset_test_suite); |
| |
| MODULE_AUTHOR("Jessica Zhang <quic_jesszhan@quicinc.com"); |
| MODULE_DESCRIPTION("Test cases for the drm_atomic_helper functions"); |
| MODULE_LICENSE("GPL"); |