diff options
| author | Sang-Heon Jeon <ekffu200098@gmail.com> | 2026-08-24 01:18:14 +0900 |
|---|---|---|
| committer | Julia Lawall <Julia.Lawall@inria.fr> | 2026-08-23 22:12:11 +0200 |
| commit | 2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c (patch) | |
| tree | dcc4da1d99f17541c5590ac63c77da4bf372a4ab /drivers/gpu/drm/tests | |
| download | linux-stable-2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c.tar.gz linux-stable-2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c.zip | |
coccinelle: alloc_cast: drop removed allocatorsgrafted
- pci_alloc_consistent() and pci_zalloc_consistent() were removed by
commit 7968778914e5
("PCI: Remove the deprecated "pci-dma-compat.h" API")
- kmem_alloc() was removed by commit f078d4ea8276
("xfs: convert kmem_alloc() to kmalloc()")
- kmem_zalloc() was removed by commit 10634530f7ba
("xfs: convert kmem_zalloc() to kzalloc()")
- kmem_zone_alloc() and kmem_zone_zalloc() were removed by commit
bae633a4a283 ("xfs: remove xfs_zone_{alloc,zalloc} helpers")
So drop them from the rules.
No functional change.
Signed-off-by: Sang-Heon Jeon <ekffu200098@gmail.com>
Signed-off-by: Julia Lawall <Julia.Lawall@inria.fr>
Diffstat (limited to 'drivers/gpu/drm/tests')
28 files changed, 16915 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tests/.kunitconfig b/drivers/gpu/drm/tests/.kunitconfig new file mode 100644 index 000000000..5be8e71f4 --- /dev/null +++ b/drivers/gpu/drm/tests/.kunitconfig @@ -0,0 +1,5 @@ +CONFIG_KUNIT=y +CONFIG_DRM=y +CONFIG_DRM_VKMS=y +CONFIG_DRM_FBDEV_EMULATION=y +CONFIG_DRM_KUNIT_TEST=y diff --git a/drivers/gpu/drm/tests/Makefile b/drivers/gpu/drm/tests/Makefile new file mode 100644 index 000000000..e7193b49c --- /dev/null +++ b/drivers/gpu/drm/tests/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_DRM_KUNIT_TEST_HELPERS) += \ + drm_kunit_helpers.o + +obj-$(CONFIG_DRM_KUNIT_TEST) += \ + drm_atomic_test.o \ + drm_atomic_commit_test.o \ + drm_bridge_test.o \ + drm_cmdline_parser_test.o \ + drm_connector_test.o \ + drm_damage_helper_test.o \ + drm_dp_mst_helper_test.o \ + drm_exec_test.o \ + drm_format_helper_test.o \ + drm_format_test.o \ + drm_framebuffer_test.o \ + drm_gem_shmem_test.o \ + drm_hdmi_state_helper_test.o \ + drm_managed_test.o \ + drm_mm_test.o \ + drm_modes_test.o \ + drm_plane_helper_test.o \ + drm_probe_helper_test.o \ + drm_rect_test.o \ + drm_sysfb_modeset_test.o \ + drm_fixp_test.o \ + drm_kunit_edid.o + +CFLAGS_drm_mm_test.o := $(DISABLE_STRUCTLEAK_PLUGIN) diff --git a/drivers/gpu/drm/tests/drm_atomic_commit_test.c b/drivers/gpu/drm/tests/drm_atomic_commit_test.c new file mode 100644 index 000000000..ff69bade1 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_atomic_commit_test.c @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test cases for the drm_atomic_commit 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_commit *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); + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + + ret = drm_atomic_set_crtc_for_connector(conn_state, crtc); + if (ret) + return ret; + } + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, &drm_atomic_test_mode); + if (ret) + return ret; + + crtc_state->enable = true; + crtc_state->active = true; + + if (connector) { + ret = drm_atomic_commit(state); + if (ret) + return ret; + } 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_commit *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 +retry_set_up: + ret = set_up_atomic_state(test, priv, old_conn, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_set_up; + } + 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_commit *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); + +retry_set_up: + ret = set_up_atomic_state(test, priv, NULL, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_set_up; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry: + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry; + } + 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); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry; + } + 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"); diff --git a/drivers/gpu/drm/tests/drm_atomic_test.c b/drivers/gpu/drm/tests/drm_atomic_test.c new file mode 100644 index 000000000..ea91bec65 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_atomic_test.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Kunit test for drm_atomic functions + */ +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_encoder.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_modeset_helper_vtables.h> + +#include <kunit/test.h> + +struct drm_atomic_test_priv { + struct drm_device drm; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_encoder encoder; + struct drm_connector connector; +}; + +static const struct drm_connector_helper_funcs drm_atomic_init_connector_helper_funcs = { +}; + +static const struct drm_connector_funcs drm_atomic_init_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 struct drm_atomic_test_priv *create_device(struct kunit *test) +{ + struct drm_atomic_test_priv *priv; + struct drm_connector *connector; + struct drm_encoder *enc; + struct drm_device *drm; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct device *dev; + int ret; + + dev = drm_kunit_helper_alloc_device(test); + if (IS_ERR(dev)) + return ERR_CAST(dev); + + priv = drm_kunit_helper_alloc_drm_device(test, dev, + struct drm_atomic_test_priv, drm, + DRIVER_MODESET | DRIVER_ATOMIC); + if (IS_ERR(priv)) + return ERR_CAST(priv); + + drm = &priv->drm; + plane = drm_kunit_helper_create_primary_plane(test, drm, + NULL, + NULL, + NULL, 0, + NULL); + if (IS_ERR(plane)) + return ERR_CAST(plane); + priv->plane = plane; + + crtc = drm_kunit_helper_create_crtc(test, drm, + plane, NULL, + NULL, + NULL); + if (IS_ERR(crtc)) + return ERR_CAST(crtc); + priv->crtc = crtc; + + enc = &priv->encoder; + ret = drmm_encoder_init(drm, enc, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return ERR_PTR(ret); + + enc->possible_crtcs = drm_crtc_mask(crtc); + + connector = &priv->connector; + ret = drmm_connector_init(drm, connector, + &drm_atomic_init_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL, + NULL); + if (ret) + return ERR_PTR(ret); + + drm_connector_helper_add(connector, &drm_atomic_init_connector_helper_funcs); + + drm_connector_attach_encoder(connector, enc); + + drm_mode_config_reset(drm); + + return priv; +} + +static void drm_test_drm_atomic_get_connector_for_encoder(struct kunit *test) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_test_priv *priv; + struct drm_display_mode *mode; + struct drm_connector *curr_connector; + int ret; + + priv = create_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + drm_modeset_acquire_init(&ctx, 0); + +retry_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, &priv->drm, + priv->crtc, &priv->connector, + mode, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn: + curr_connector = drm_atomic_get_connector_for_encoder(&priv->encoder, + &ctx); + if (PTR_ERR(curr_connector) == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_conn; + } + KUNIT_EXPECT_PTR_EQ(test, curr_connector, &priv->connector); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static struct kunit_case drm_atomic_get_connector_for_encoder_tests[] = { + KUNIT_CASE(drm_test_drm_atomic_get_connector_for_encoder), + { } +}; + + +static struct kunit_suite drm_atomic_get_connector_for_encoder_test_suite = { + .name = "drm_test_atomic_get_connector_for_encoder", + .test_cases = drm_atomic_get_connector_for_encoder_tests, +}; + +kunit_test_suite(drm_atomic_get_connector_for_encoder_test_suite); + +MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>"); +MODULE_DESCRIPTION("Kunit test for drm_atomic functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_bridge_test.c b/drivers/gpu/drm/tests/drm_bridge_test.c new file mode 100644 index 000000000..15583ade8 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_bridge_test.c @@ -0,0 +1,1386 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Kunit test for drm_bridge functions + */ +#include <linux/cleanup.h> +#include <linux/media-bus-format.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_bridge_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_managed.h> + +#include <kunit/device.h> +#include <kunit/test.h> + +#include "drm_kunit_edid.h" + +/* + * Mimick the typical "private" struct defined by a bridge driver, which + * embeds a bridge plus other fields. + * + * Having at least one member before @bridge ensures we test non-zero + * @bridge offset. + */ +struct drm_bridge_priv { + unsigned int enable_count; + unsigned int disable_count; + struct drm_bridge bridge; + void *data; +}; + +struct drm_bridge_init_priv { + struct drm_device drm; + /** @dev: device, only for tests not needing a whole drm_device */ + struct device *dev; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_encoder encoder; + struct drm_bridge_priv *test_bridge; + struct drm_connector *connector; + bool destroyed; +}; + +struct drm_bridge_chain_priv { + struct drm_device drm; + struct drm_encoder encoder; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_connector *connector; + unsigned int num_bridges; + + /** + * @test_bridges: array of pointers to &struct drm_bridge_priv entries + * of which the first @num_bridges entries are valid. + */ + struct drm_bridge_priv **test_bridges; +}; + +static struct drm_bridge_priv *bridge_to_priv(struct drm_bridge *bridge) +{ + return container_of(bridge, struct drm_bridge_priv, bridge); +} + +static void drm_test_bridge_priv_destroy(struct drm_bridge *bridge) +{ + struct drm_bridge_priv *bridge_priv = bridge_to_priv(bridge); + struct drm_bridge_init_priv *priv = (struct drm_bridge_init_priv *)bridge_priv->data; + + priv->destroyed = true; +} + +static void drm_test_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct drm_bridge_priv *priv = bridge_to_priv(bridge); + + priv->enable_count++; +} + +static void drm_test_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct drm_bridge_priv *priv = bridge_to_priv(bridge); + + priv->disable_count++; +} + +static const struct drm_bridge_funcs drm_test_bridge_atomic_funcs = { + .destroy = drm_test_bridge_priv_destroy, + .atomic_enable = drm_test_bridge_atomic_enable, + .atomic_disable = drm_test_bridge_atomic_disable, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, +}; + +static int dummy_clear_infoframe(struct drm_bridge *bridge) +{ + return 0; +} + +static int dummy_write_infoframe(struct drm_bridge *bridge, const u8 *buffer, + size_t len) +{ + return 0; +} + +static const struct drm_bridge_funcs drm_test_bridge_bus_fmts_funcs = { + .atomic_get_output_bus_fmts = drm_atomic_helper_bridge_get_hdmi_output_bus_fmts, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .hdmi_write_avi_infoframe = dummy_write_infoframe, + .hdmi_write_hdmi_infoframe = dummy_write_infoframe, + .hdmi_clear_avi_infoframe = dummy_clear_infoframe, + .hdmi_clear_hdmi_infoframe = dummy_clear_infoframe, +}; + +/** + * struct fmt_tuple - a tuple of input/output MEDIA_BUS_FMT_* + */ +struct fmt_tuple { + u32 in_fmt; + u32 out_fmt; +}; + +/* + * Format mapping that only accepts RGB888, and outputs only RGB888 + */ +static const struct fmt_tuple rgb8_passthrough[] = { + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, +}; + +/* + * Format mapping that only accepts YUV444, and outputs only YUV444 + */ +static const struct fmt_tuple yuv8_passthrough[] = { + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, +}; + +/* + * Format mapping where 8bpc RGB -> 8bpc YUV444, or ID(RGB) or ID(YUV444) + */ +static const struct fmt_tuple rgb8_to_yuv8_or_id[] = { + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, +}; + +static const struct fmt_tuple rgb8_to_id_yuv8_or_yuv8_to_yuv422_yuv420[] = { + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_UYVY8_1X16 }, + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_UYYVYY8_0_5X24 }, +}; + +/* + * Format mapping where 8bpc YUV444 -> 8bpc RGB, or ID(YUV444) + */ +static const struct fmt_tuple yuv8_to_rgb8_or_id[] = { + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, +}; + +/* + * A format mapping that acts like a video processor that generates an RGB signal + */ +static const struct fmt_tuple rgb_producer[] = { + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_RGB101010_1X30 }, + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_RGB121212_1X36 }, +}; + +/* + * A format mapping that acts like a video processor that generates an 8-bit RGB, + * YUV444 or YUV420 signal + */ +static const struct fmt_tuple rgb_yuv444_yuv420_producer[] = { + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_UYYVYY8_0_5X24 }, +}; + +static const struct fmt_tuple rgb8_yuv444_yuv422_passthrough[] = { + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_UYVY8_1X16 }, +}; + +static const struct fmt_tuple yuv444_yuv422_rgb8_passthrough[] = { + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_UYVY8_1X16 }, + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, +}; + +static bool fmt_in_list(const u32 fmt, const u32 *out_fmts, const size_t num_fmts) +{ + size_t i; + + for (i = 0; i < num_fmts; i++) + if (out_fmts[i] == fmt) + return true; + + return false; +} + +/** + * get_tuples_out_fmts - Get unique output formats of a &struct fmt_tuple list + * @fmt_tuples: array of &struct fmt_tuple + * @num_fmt_tuples: number of entries in @fmt_tuples + * @out_fmts: target array to store the unique output bus formats + * + * Returns the number of unique output formats, i.e. the number of entries in + * @out_fmts that were populated with sensible values. + */ +static size_t get_tuples_out_fmts(const struct fmt_tuple *fmt_tuples, + const size_t num_fmt_tuples, u32 *out_fmts) +{ + size_t num_unique = 0; + size_t i; + + for (i = 0; i < num_fmt_tuples; i++) + if (!fmt_in_list(fmt_tuples[i].out_fmt, out_fmts, num_unique)) + out_fmts[num_unique++] = fmt_tuples[i].out_fmt; + + return num_unique; +} + +#define DEFINE_FMT_FUNCS_FROM_TUPLES(name) \ +static u32 *drm_test_bridge_ ## name ## _out_fmts(struct drm_bridge *bridge, \ + struct drm_bridge_state *bridge_state, \ + struct drm_crtc_state *crtc_state, \ + struct drm_connector_state *conn_state, \ + unsigned int *num_output_fmts) \ +{ \ + u32 *out_fmts = kcalloc(ARRAY_SIZE((name)), sizeof(u32), GFP_KERNEL); \ + \ + if (out_fmts) \ + *num_output_fmts = get_tuples_out_fmts((name), ARRAY_SIZE((name)), out_fmts); \ + else \ + *num_output_fmts = 0; \ + \ + return out_fmts; \ +} \ + \ +static u32 *drm_test_bridge_ ## name ## _in_fmts(struct drm_bridge *bridge, \ + struct drm_bridge_state *bridge_state, \ + struct drm_crtc_state *crtc_state, \ + struct drm_connector_state *conn_state, \ + u32 output_fmt, \ + unsigned int *num_input_fmts) \ +{ \ + u32 *in_fmts = kcalloc(ARRAY_SIZE((name)), sizeof(u32), GFP_KERNEL); \ + unsigned int num_fmts = 0; \ + size_t i; \ + \ + if (!in_fmts) { \ + *num_input_fmts = 0; \ + return NULL; \ + } \ + \ + for (i = 0; i < ARRAY_SIZE((name)); i++) \ + if ((name)[i].out_fmt == output_fmt) \ + in_fmts[num_fmts++] = (name)[i].in_fmt; \ + \ + *num_input_fmts = num_fmts; \ + \ + return in_fmts; \ +} + +#define DRM_BRIDGE_ATOMIC_WITH_BUS_FMT_HDMI_FUNC(ident, input_fmts_func, output_fmts_func, \ + hdmi_write_infoframe_func, \ + hdmi_clear_infoframe_func) \ +static const struct drm_bridge_funcs (ident) = { \ + .atomic_enable = drm_test_bridge_atomic_enable, \ + .atomic_disable = drm_test_bridge_atomic_disable, \ + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, \ + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, \ + .atomic_create_state = drm_atomic_helper_bridge_create_state, \ + .atomic_get_input_bus_fmts = (input_fmts_func), \ + .atomic_get_output_bus_fmts = (output_fmts_func), \ + .hdmi_write_avi_infoframe = (hdmi_write_infoframe_func), \ + .hdmi_clear_avi_infoframe = (hdmi_clear_infoframe_func), \ + .hdmi_write_hdmi_infoframe = (hdmi_write_infoframe_func), \ + .hdmi_clear_hdmi_infoframe = (hdmi_clear_infoframe_func), \ +} + +#define DRM_BRIDGE_ATOMIC_WITH_BUS_FMT_FUNC(ident, input_fmts_func, output_fmts_func) \ + DRM_BRIDGE_ATOMIC_WITH_BUS_FMT_HDMI_FUNC(ident, input_fmts_func, output_fmts_func, \ + NULL, NULL) + +#define DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(_name) \ + DRM_BRIDGE_ATOMIC_WITH_BUS_FMT_FUNC(_name ## _funcs, \ + drm_test_bridge_ ## _name ## _in_fmts, \ + drm_test_bridge_ ## _name ## _out_fmts) + +static int drm_test_bridge_write_infoframe_stub(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + return 0; +} + +static int drm_test_bridge_clear_infoframe_stub(struct drm_bridge *bridge) +{ + return 0; +} + +#define DRM_BRIDGE_ATOMIC_WITH_BUS_FMT_HDMI(_name) \ + DRM_BRIDGE_ATOMIC_WITH_BUS_FMT_HDMI_FUNC(_name ## _hdmi ## _funcs, \ + drm_test_bridge_ ## _name ## _in_fmts, \ + drm_test_bridge_ ## _name ## _out_fmts, \ + drm_test_bridge_write_infoframe_stub, \ + drm_test_bridge_clear_infoframe_stub) +DEFINE_FMT_FUNCS_FROM_TUPLES(rgb8_passthrough) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(rgb8_passthrough); + +DEFINE_FMT_FUNCS_FROM_TUPLES(yuv8_passthrough) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(yuv8_passthrough); + +DEFINE_FMT_FUNCS_FROM_TUPLES(rgb8_to_yuv8_or_id) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(rgb8_to_yuv8_or_id); + +DEFINE_FMT_FUNCS_FROM_TUPLES(yuv8_to_rgb8_or_id) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(yuv8_to_rgb8_or_id); + +DEFINE_FMT_FUNCS_FROM_TUPLES(rgb_producer) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(rgb_producer); + +DEFINE_FMT_FUNCS_FROM_TUPLES(rgb_yuv444_yuv420_producer) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(rgb_yuv444_yuv420_producer); + +DEFINE_FMT_FUNCS_FROM_TUPLES(rgb8_to_id_yuv8_or_yuv8_to_yuv422_yuv420) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(rgb8_to_id_yuv8_or_yuv8_to_yuv422_yuv420); + +DEFINE_FMT_FUNCS_FROM_TUPLES(rgb8_yuv444_yuv422_passthrough) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(rgb8_yuv444_yuv422_passthrough); + +DEFINE_FMT_FUNCS_FROM_TUPLES(yuv444_yuv422_rgb8_passthrough) +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT(yuv444_yuv422_rgb8_passthrough); +DRM_BRIDGE_ATOMIC_WITH_BUS_FMT_HDMI(yuv444_yuv422_rgb8_passthrough); + +KUNIT_DEFINE_ACTION_WRAPPER(drm_bridge_remove_wrapper, + drm_bridge_remove, + struct drm_bridge *); + +static int drm_kunit_bridge_add(struct kunit *test, + struct drm_bridge *bridge) +{ + drm_bridge_add(bridge); + + return kunit_add_action_or_reset(test, + drm_bridge_remove_wrapper, + bridge); +} + +static struct drm_bridge_init_priv * +drm_test_bridge_init(struct kunit *test, const struct drm_bridge_funcs *funcs) +{ + struct drm_bridge_init_priv *priv; + struct drm_encoder *enc; + struct drm_bridge *bridge; + struct drm_device *drm; + struct device *dev; + int ret; + + dev = drm_kunit_helper_alloc_device(test); + if (IS_ERR(dev)) + return ERR_CAST(dev); + + priv = drm_kunit_helper_alloc_drm_device(test, dev, + struct drm_bridge_init_priv, drm, + DRIVER_MODESET | DRIVER_ATOMIC); + if (IS_ERR(priv)) + return ERR_CAST(priv); + + priv->test_bridge = devm_drm_bridge_alloc(dev, struct drm_bridge_priv, bridge, funcs); + if (IS_ERR(priv->test_bridge)) + return ERR_CAST(priv->test_bridge); + + priv->test_bridge->data = priv; + + drm = &priv->drm; + priv->plane = drm_kunit_helper_create_primary_plane(test, drm, + NULL, + NULL, + NULL, 0, + NULL); + if (IS_ERR(priv->plane)) + return ERR_CAST(priv->plane); + + priv->crtc = drm_kunit_helper_create_crtc(test, drm, + priv->plane, NULL, + NULL, + NULL); + if (IS_ERR(priv->crtc)) + return ERR_CAST(priv->crtc); + + enc = &priv->encoder; + ret = drmm_encoder_init(drm, enc, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return ERR_PTR(ret); + + enc->possible_crtcs = drm_crtc_mask(priv->crtc); + + bridge = &priv->test_bridge->bridge; + bridge->type = DRM_MODE_CONNECTOR_VIRTUAL; + + ret = drm_kunit_bridge_add(test, bridge); + if (ret) + return ERR_PTR(ret); + + ret = drm_bridge_attach(enc, bridge, NULL, 0); + if (ret) + return ERR_PTR(ret); + + priv->connector = drm_bridge_connector_init(drm, enc); + if (IS_ERR(priv->connector)) + return ERR_CAST(priv->connector); + + drm_mode_config_reset(drm); + + return priv; +} + +static struct drm_bridge_chain_priv * +drm_test_bridge_chain_init(struct kunit *test, unsigned int num_bridges, + const struct drm_bridge_funcs **funcs) +{ + struct drm_bridge_chain_priv *priv; + const struct drm_edid *edid; + struct drm_bridge *prev; + struct drm_encoder *enc; + struct drm_bridge *bridge; + struct drm_device *drm; + bool has_hdmi = false; + struct device *dev; + unsigned int i; + int ret; + + dev = drm_kunit_helper_alloc_device(test); + if (IS_ERR(dev)) + return ERR_CAST(dev); + + priv = drm_kunit_helper_alloc_drm_device(test, dev, struct drm_bridge_chain_priv, + drm, DRIVER_MODESET | DRIVER_ATOMIC); + if (IS_ERR(priv)) + return ERR_CAST(priv); + + drm = &priv->drm; + + priv->test_bridges = drmm_kmalloc_array(drm, num_bridges, sizeof(*priv->test_bridges), + GFP_KERNEL); + if (!priv->test_bridges) + return ERR_PTR(-ENOMEM); + + priv->num_bridges = num_bridges; + + for (i = 0; i < num_bridges; i++) { + priv->test_bridges[i] = devm_drm_bridge_alloc(dev, struct drm_bridge_priv, + bridge, funcs[i]); + if (IS_ERR(priv->test_bridges[i])) + return ERR_CAST(priv->test_bridges[i]); + + priv->test_bridges[i]->data = priv; + } + + priv->plane = drm_kunit_helper_create_primary_plane(test, drm, NULL, NULL, + NULL, 0, NULL); + if (IS_ERR(priv->plane)) + return ERR_CAST(priv->plane); + + priv->crtc = drm_kunit_helper_create_crtc(test, drm, priv->plane, NULL, + NULL, NULL); + if (IS_ERR(priv->crtc)) + return ERR_CAST(priv->crtc); + + enc = &priv->encoder; + ret = drmm_encoder_init(drm, enc, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return ERR_PTR(ret); + + enc->possible_crtcs = drm_crtc_mask(priv->crtc); + + prev = NULL; + for (i = 0; i < num_bridges; i++) { + bridge = &priv->test_bridges[i]->bridge; + bridge->type = DRM_MODE_CONNECTOR_VIRTUAL; + + if (bridge->funcs->hdmi_write_hdmi_infoframe) { + has_hdmi = true; + bridge->ops |= DRM_BRIDGE_OP_HDMI; + bridge->type = DRM_MODE_CONNECTOR_HDMIA; + bridge->vendor = "LNX"; + bridge->product = "KUnit"; + bridge->supported_formats = (BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420)); + } + + ret = drm_kunit_bridge_add(test, bridge); + if (ret) + return ERR_PTR(ret); + + ret = drm_bridge_attach(enc, bridge, prev, 0); + if (ret) + return ERR_PTR(ret); + + prev = bridge; + } + + priv->connector = drm_bridge_connector_init(drm, enc); + if (IS_ERR(priv->connector)) + return ERR_CAST(priv->connector); + + drm_connector_attach_encoder(priv->connector, enc); + + drm_mode_config_reset(drm); + + if (!has_hdmi) + return priv; + + scoped_guard(mutex, &drm->mode_config.mutex) { + edid = drm_edid_alloc(test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz, + ARRAY_SIZE(test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz)); + if (!edid) + return ERR_PTR(-EINVAL); + + drm_edid_connector_update(priv->connector, edid); + KUNIT_ASSERT_GT(test, drm_edid_connector_add_modes(priv->connector), 0); + + ret = priv->connector->funcs->fill_modes(priv->connector, 4096, 4096); + } + + return priv; +} + +static struct drm_bridge_init_priv * +drm_test_bridge_hdmi_init(struct kunit *test, const struct drm_bridge_funcs *funcs, + unsigned int supported_formats, int max_bpc) +{ + struct drm_bridge_init_priv *priv; + struct drm_encoder *enc; + struct drm_bridge *bridge; + struct drm_device *drm; + struct device *dev; + int ret; + + dev = drm_kunit_helper_alloc_device(test); + if (IS_ERR(dev)) + return ERR_CAST(dev); + + priv = drm_kunit_helper_alloc_drm_device(test, dev, + struct drm_bridge_init_priv, drm, + DRIVER_MODESET | DRIVER_ATOMIC); + if (IS_ERR(priv)) + return ERR_CAST(priv); + + priv->test_bridge = devm_drm_bridge_alloc(dev, struct drm_bridge_priv, bridge, funcs); + if (IS_ERR(priv->test_bridge)) + return ERR_CAST(priv->test_bridge); + + priv->test_bridge->data = priv; + + drm = &priv->drm; + priv->plane = drm_kunit_helper_create_primary_plane(test, drm, + NULL, + NULL, + NULL, 0, + NULL); + if (IS_ERR(priv->plane)) + return ERR_CAST(priv->plane); + + priv->crtc = drm_kunit_helper_create_crtc(test, drm, + priv->plane, NULL, + NULL, + NULL); + if (IS_ERR(priv->crtc)) + return ERR_CAST(priv->crtc); + + enc = &priv->encoder; + ret = drmm_encoder_init(drm, enc, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return ERR_PTR(ret); + + enc->possible_crtcs = drm_crtc_mask(priv->crtc); + + bridge = &priv->test_bridge->bridge; + bridge->type = DRM_MODE_CONNECTOR_HDMIA; + bridge->supported_formats = supported_formats; + bridge->max_bpc = max_bpc; + bridge->ops |= DRM_BRIDGE_OP_HDMI; + bridge->vendor = "LNX"; + bridge->product = "KUnit"; + + ret = drm_kunit_bridge_add(test, bridge); + if (ret) + return ERR_PTR(ret); + + ret = drm_bridge_attach(enc, bridge, NULL, 0); + if (ret) + return ERR_PTR(ret); + + priv->connector = drm_bridge_connector_init(drm, enc); + if (IS_ERR(priv->connector)) + return ERR_CAST(priv->connector); + + drm_connector_attach_encoder(priv->connector, enc); + + drm_mode_config_reset(drm); + + return priv; +} + +/* + * Test that drm_bridge_get_current_state() returns the last committed + * state for an atomic bridge. + */ +static void drm_test_drm_bridge_get_current_state_atomic(struct kunit *test) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_init_priv *priv; + struct drm_bridge_state *curr_bridge_state; + struct drm_bridge_state *bridge_state; + struct drm_atomic_commit *state; + struct drm_bridge *bridge; + struct drm_device *drm; + int ret; + + priv = drm_test_bridge_init(test, &drm_test_bridge_atomic_funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + drm = &priv->drm; + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_commit: + bridge = &priv->test_bridge->bridge; + bridge_state = drm_atomic_get_bridge_state(state, bridge); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bridge_state); + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + drm_modeset_acquire_init(&ctx, 0); + +retry_state: + ret = drm_modeset_lock(&bridge->base.lock, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_state; + } + + curr_bridge_state = drm_bridge_get_current_state(bridge); + KUNIT_EXPECT_PTR_EQ(test, curr_bridge_state, bridge_state); + + drm_modeset_unlock(&bridge->base.lock); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static struct kunit_case drm_bridge_get_current_state_tests[] = { + KUNIT_CASE(drm_test_drm_bridge_get_current_state_atomic), + { } +}; + + +static struct kunit_suite drm_bridge_get_current_state_test_suite = { + .name = "drm_test_bridge_get_current_state", + .test_cases = drm_bridge_get_current_state_tests, +}; + +/* + * Test that an atomic bridge is properly power-cycled when calling + * drm_bridge_helper_reset_crtc(). + */ +static void drm_test_drm_bridge_helper_reset_crtc_atomic(struct kunit *test) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_init_priv *priv; + struct drm_display_mode *mode; + struct drm_bridge_priv *bridge_priv; + int ret; + + priv = drm_test_bridge_init(test, &drm_test_bridge_atomic_funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + drm_modeset_acquire_init(&ctx, 0); + +retry_commit: + ret = drm_kunit_helper_enable_crtc_connector(test, + &priv->drm, priv->crtc, + priv->connector, + mode, + &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + bridge_priv = priv->test_bridge; + KUNIT_ASSERT_EQ(test, bridge_priv->enable_count, 1); + KUNIT_ASSERT_EQ(test, bridge_priv->disable_count, 0); + + drm_modeset_acquire_init(&ctx, 0); + +retry_reset: + ret = drm_bridge_helper_reset_crtc(&bridge_priv->bridge, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_reset; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + KUNIT_EXPECT_EQ(test, bridge_priv->enable_count, 2); + KUNIT_EXPECT_EQ(test, bridge_priv->disable_count, 1); +} + +/* + * Test that calling drm_bridge_helper_reset_crtc() on a disabled atomic + * bridge will fail and not call the enable / disable callbacks + */ +static void drm_test_drm_bridge_helper_reset_crtc_atomic_disabled(struct kunit *test) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_init_priv *priv; + struct drm_display_mode *mode; + struct drm_bridge_priv *bridge_priv; + int ret; + + priv = drm_test_bridge_init(test, &drm_test_bridge_atomic_funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + bridge_priv = priv->test_bridge; + KUNIT_ASSERT_EQ(test, bridge_priv->enable_count, 0); + KUNIT_ASSERT_EQ(test, bridge_priv->disable_count, 0); + + drm_modeset_acquire_init(&ctx, 0); + +retry_reset: + ret = drm_bridge_helper_reset_crtc(&bridge_priv->bridge, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_reset; + } + KUNIT_EXPECT_LT(test, ret, 0); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + KUNIT_EXPECT_EQ(test, bridge_priv->enable_count, 0); + KUNIT_EXPECT_EQ(test, bridge_priv->disable_count, 0); +} + +/* + * Test that a bridge using the drm_atomic_helper_bridge_get_hdmi_output_bus_fmts() + * function for &drm_bridge_funcs.atomic_get_output_bus_fmts behaves as expected + * for an HDMI connector bridge. Does so by creating an HDMI bridge connector + * with RGB444, YCBCR444, and YCBCR420 (but not YCBCR422) as supported formats, + * sets the output depth to 8 bits per component, and then validates the returned + * list of bus formats. + */ +static void drm_test_drm_bridge_helper_hdmi_output_bus_fmts(struct kunit *test) +{ + struct drm_connector_state *conn_state; + struct drm_bridge_state *bridge_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_init_priv *priv; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + unsigned int num_output_fmts; + struct drm_bridge *bridge; + u32 *out_bus_fmts; + int ret; + + priv = drm_test_bridge_hdmi_init(test, &drm_test_bridge_bus_fmts_funcs, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), + 12); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + bridge = &priv->test_bridge->bridge; + + drm_modeset_acquire_init(&ctx, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_commit: + conn_state = drm_atomic_get_connector_state(state, priv->connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + conn_state->hdmi.output_bpc = 8; + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state->enable = true; + crtc_state->active = true; + + bridge_state = drm_atomic_get_bridge_state(state, bridge); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bridge_state); + + out_bus_fmts = bridge->funcs->atomic_get_output_bus_fmts( + bridge, bridge_state, crtc_state, conn_state, &num_output_fmts); + KUNIT_EXPECT_NOT_NULL(test, out_bus_fmts); + KUNIT_EXPECT_EQ(test, num_output_fmts, 3); + + KUNIT_EXPECT_EQ(test, out_bus_fmts[0], MEDIA_BUS_FMT_RGB888_1X24); + KUNIT_EXPECT_EQ(test, out_bus_fmts[1], MEDIA_BUS_FMT_YUV8_1X24); + KUNIT_EXPECT_EQ(test, out_bus_fmts[2], MEDIA_BUS_FMT_UYYVYY8_0_5X24); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + kfree(out_bus_fmts); +} + +static struct kunit_case drm_bridge_helper_reset_crtc_tests[] = { + KUNIT_CASE(drm_test_drm_bridge_helper_reset_crtc_atomic), + KUNIT_CASE(drm_test_drm_bridge_helper_reset_crtc_atomic_disabled), + KUNIT_CASE(drm_test_drm_bridge_helper_hdmi_output_bus_fmts), + { } +}; + +static struct kunit_suite drm_bridge_helper_reset_crtc_test_suite = { + .name = "drm_test_bridge_helper_reset_crtc", + .test_cases = drm_bridge_helper_reset_crtc_tests, +}; + +static int drm_test_bridge_alloc_init(struct kunit *test) +{ + struct drm_bridge_init_priv *priv; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + priv->dev = kunit_device_register(test, "drm-bridge-dev"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->dev); + + test->priv = priv; + + priv->test_bridge = devm_drm_bridge_alloc(priv->dev, struct drm_bridge_priv, bridge, + &drm_test_bridge_atomic_funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->test_bridge); + + priv->test_bridge->data = priv; + + KUNIT_ASSERT_FALSE(test, priv->destroyed); + + return 0; +} + +/* + * Test that a bridge is freed when the device is destroyed in lack of + * other drm_bridge_get/put() operations. + */ +static void drm_test_drm_bridge_alloc_basic(struct kunit *test) +{ + struct drm_bridge_init_priv *priv = test->priv; + + KUNIT_ASSERT_FALSE(test, priv->destroyed); + + kunit_device_unregister(test, priv->dev); + KUNIT_EXPECT_TRUE(test, priv->destroyed); +} + +/* + * Test that a bridge is not freed when the device is destroyed when there + * is still a reference to it, and freed when that reference is put. + */ +static void drm_test_drm_bridge_alloc_get_put(struct kunit *test) +{ + struct drm_bridge_init_priv *priv = test->priv; + + KUNIT_ASSERT_FALSE(test, priv->destroyed); + + drm_bridge_get(&priv->test_bridge->bridge); + KUNIT_EXPECT_FALSE(test, priv->destroyed); + + kunit_device_unregister(test, priv->dev); + KUNIT_EXPECT_FALSE(test, priv->destroyed); + + drm_bridge_put(&priv->test_bridge->bridge); + KUNIT_EXPECT_TRUE(test, priv->destroyed); +} + +static struct kunit_case drm_bridge_alloc_tests[] = { + KUNIT_CASE(drm_test_drm_bridge_alloc_basic), + KUNIT_CASE(drm_test_drm_bridge_alloc_get_put), + { } +}; + +static struct kunit_suite drm_bridge_alloc_test_suite = { + .name = "drm_bridge_alloc", + .init = drm_test_bridge_alloc_init, + .test_cases = drm_bridge_alloc_tests, +}; + +/** + * drm_test_bridge_chain_verify_fmt - Verify bridge chain format selection + * @test: pointer to KUnit test object + * @priv: pointer to a &struct drm_bridge_chain_priv for this chain + * @expected: constant array of &struct fmt_tuple describing the expected + * input and output bus formats + * @num_expected: number of entries in @expected + * + * Runs the KUNIT_EXPECT clauses to verify the bridge chain format selection + * resulted in the expected formats. If %0 is given as a format in a + * &struct fmt_tuple, then it is understood to mean "any". + * + * Must be called with the modeset lock held. + */ +static void drm_test_bridge_chain_verify_fmt(struct kunit *test, + struct drm_bridge_chain_priv *priv, + const struct fmt_tuple *const expected, + const unsigned int num_expected) +{ + struct drm_bridge_state *bstate; + unsigned int i = 0; + + drm_for_each_bridge_in_chain(&priv->encoder, bridge) { + KUNIT_ASSERT_LT(test, i, num_expected); + + bstate = drm_bridge_get_current_state(bridge); + if (expected[i].in_fmt) + KUNIT_EXPECT_EQ(test, bstate->input_bus_cfg.format, + expected[i].in_fmt); + if (expected[i].out_fmt) + KUNIT_EXPECT_EQ(test, bstate->output_bus_cfg.format, + expected[i].out_fmt); + + i++; + } + + KUNIT_ASSERT_EQ_MSG(test, i, num_expected, + "Fewer bridges (%u) than expected (%u)\n", i, num_expected); +} + +/* + * Test that constructs a bridge chain in which an RGB888 producer is chained to + * two bridges that will convert from RGB to YUV and from YUV to RGB respectively. + * + * The test requests an output color_format of RGB using the color_format property, + * so to satisfy this request, the bridge chain must take a detour over YUV. + */ +static void drm_test_bridge_rgb_yuv_rgb(struct kunit *test) +{ + static const struct drm_bridge_funcs *funcs[] = { + &rgb_producer_funcs, + &rgb8_to_yuv8_or_id_funcs, + &yuv8_to_rgb8_or_id_funcs, + }; + static const struct fmt_tuple expected[] = { + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, + }; + struct drm_connector_state *conn_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_chain_priv *priv; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + int ret; + + priv = drm_test_bridge_chain_init(test, ARRAY_SIZE(funcs), funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_commit: + conn_state = drm_atomic_get_connector_state(state, priv->connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + conn_state->color_format = DRM_CONNECTOR_COLOR_FORMAT_RGB444; + + ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state->enable = true; + crtc_state->active = true; + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_test_bridge_chain_verify_fmt(test, priv, expected, ARRAY_SIZE(expected)); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test in which a bridge capable of producing RGB, YUV444 and YUV420 has to + * produce RGB and convert with a downstream bridge in the chain to reach the + * requested YUV444 color format, as no direct path exists between its YUV444 + * and the last bridge. + * + * The rationale behind this test is to devise a scenario in which naively + * assuming any format the video processor can output, and the connector + * requests, is the right format to pick, does not work. + */ +static void drm_test_bridge_must_convert_to_yuv444(struct kunit *test) +{ + static const struct drm_bridge_funcs *funcs[] = { + &rgb_yuv444_yuv420_producer_funcs, + &rgb8_passthrough_funcs, + &rgb8_to_id_yuv8_or_yuv8_to_yuv422_yuv420_funcs, + &rgb8_yuv444_yuv422_passthrough_funcs, + }; + static const struct fmt_tuple expected[] = { + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + }; + struct drm_connector_state *conn_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_chain_priv *priv; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + int ret; + + priv = drm_test_bridge_chain_init(test, ARRAY_SIZE(funcs), funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_commit: + conn_state = drm_atomic_get_connector_state(state, priv->connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + conn_state->color_format = DRM_CONNECTOR_COLOR_FORMAT_YCBCR444; + + ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state->enable = true; + crtc_state->active = true; + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_test_bridge_chain_verify_fmt(test, priv, expected, ARRAY_SIZE(expected)); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test which checks that no matter the order of bus formats returned by an + * HDMI bridge, RGB is preferred on DRM_CONNECTOR_COLOR_FORMAT_AUTO if it's + * available. + */ +static void drm_test_bridge_hdmi_auto_rgb(struct kunit *test) +{ + static const struct drm_bridge_funcs *funcs[] = { + &rgb_yuv444_yuv420_producer_funcs, + &yuv444_yuv422_rgb8_passthrough_hdmi_funcs, + }; + static const struct fmt_tuple expected[] = { + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_RGB888_1X24 }, + { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB888_1X24 }, + }; + struct drm_connector_state *conn_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_chain_priv *priv; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + int ret; + + priv = drm_test_bridge_chain_init(test, ARRAY_SIZE(funcs), funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_commit: + conn_state = drm_atomic_get_connector_state(state, priv->connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + KUNIT_ASSERT_EQ(test, conn_state->color_format, DRM_CONNECTOR_COLOR_FORMAT_AUTO); + + ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state->enable = true; + crtc_state->active = true; + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_test_bridge_chain_verify_fmt(test, priv, expected, ARRAY_SIZE(expected)); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test which checks that DRM_CONNECTOR_COLOR_FORMAT_AUTO on non-HDMI connectors + * will result in the first bus format on the output to be picked. + */ +static void drm_test_bridge_auto_first(struct kunit *test) +{ + static const struct drm_bridge_funcs *funcs[] = { + &rgb_yuv444_yuv420_producer_funcs, + &yuv444_yuv422_rgb8_passthrough_funcs, + }; + static const struct fmt_tuple expected[] = { + { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_YUV8_1X24 }, + { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_YUV8_1X24 }, + }; + struct drm_connector_state *conn_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_chain_priv *priv; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + int ret; + + priv = drm_test_bridge_chain_init(test, ARRAY_SIZE(funcs), funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_commit: + conn_state = drm_atomic_get_connector_state(state, priv->connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + KUNIT_ASSERT_EQ(test, conn_state->color_format, DRM_CONNECTOR_COLOR_FORMAT_AUTO); + + ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state->enable = true; + crtc_state->active = true; + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_test_bridge_chain_verify_fmt(test, priv, expected, ARRAY_SIZE(expected)); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test which checks that in a configuration of bridge chains where an RGB + * producer is hooked to a YUV444-only pass-through, the atomic commit fails as + * the bridge format selection cannot find a valid sequence of bus formats. + */ +static void drm_test_bridge_rgb_yuv_no_path(struct kunit *test) +{ + static const struct drm_bridge_funcs *funcs[] = { + &rgb_producer_funcs, + &yuv8_passthrough_funcs, + &rgb8_yuv444_yuv422_passthrough_funcs, + }; + struct drm_connector_state *conn_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_bridge_chain_priv *priv; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + int ret; + + priv = drm_test_bridge_chain_init(test, ARRAY_SIZE(funcs), funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_commit: + conn_state = drm_atomic_get_connector_state(state, priv->connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode); + + ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state->enable = true; + crtc_state->active = true; + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry_commit; + } + KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static struct kunit_case drm_bridge_bus_fmt_tests[] = { + KUNIT_CASE(drm_test_bridge_rgb_yuv_rgb), + KUNIT_CASE(drm_test_bridge_must_convert_to_yuv444), + KUNIT_CASE(drm_test_bridge_hdmi_auto_rgb), + KUNIT_CASE(drm_test_bridge_auto_first), + KUNIT_CASE(drm_test_bridge_rgb_yuv_no_path), + { } +}; + +static struct kunit_suite drm_bridge_bus_fmt_test_suite = { + .name = "drm_bridge_bus_fmt", + .test_cases = drm_bridge_bus_fmt_tests, +}; + +kunit_test_suites( + &drm_bridge_get_current_state_test_suite, + &drm_bridge_helper_reset_crtc_test_suite, + &drm_bridge_alloc_test_suite, + &drm_bridge_bus_fmt_test_suite, +); + +MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>"); +MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>"); +MODULE_AUTHOR("Nicolas Frattaroli <nicolas.frattaroli@collabora.com>"); + +MODULE_DESCRIPTION("Kunit test for drm_bridge functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_client_modeset_test.c b/drivers/gpu/drm/tests/drm_client_modeset_test.c new file mode 100644 index 000000000..ec58fe064 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_client_modeset_test.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 Maxime Ripard <mripard@kernel.org> + */ + +#include <kunit/test.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_edid.h> +#include <drm/drm_drv.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_modes.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_probe_helper.h> + +struct drm_client_modeset_test_priv { + struct drm_device *drm; + struct device *dev; + struct drm_connector connector; +}; + +static int drm_client_modeset_connector_get_modes(struct drm_connector *connector) +{ + struct drm_display_mode *mode; + int count; + + count = drm_add_modes_noedid(connector, 1920, 1200); + + mode = drm_mode_analog_ntsc_480i(connector->dev); + if (!mode) + return count; + + drm_mode_probed_add(connector, mode); + count += 1; + + mode = drm_mode_analog_pal_576i(connector->dev); + if (!mode) + return count; + + drm_mode_probed_add(connector, mode); + count += 1; + + return count; +} + +static const struct drm_connector_helper_funcs drm_client_modeset_connector_helper_funcs = { + .get_modes = drm_client_modeset_connector_get_modes, +}; + +static const struct drm_connector_funcs drm_client_modeset_connector_funcs = { + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state +}; + +static int drm_client_modeset_test_init(struct kunit *test) +{ + struct drm_client_modeset_test_priv *priv; + int ret; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv); + + test->priv = priv; + + priv->dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->dev); + + priv->drm = __drm_kunit_helper_alloc_drm_device(test, priv->dev, + sizeof(*priv->drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->drm); + + ret = drmm_connector_init(priv->drm, &priv->connector, + &drm_client_modeset_connector_funcs, + DRM_MODE_CONNECTOR_Unknown, + NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_connector_helper_add(&priv->connector, &drm_client_modeset_connector_helper_funcs); + + priv->connector.interlace_allowed = true; + priv->connector.doublescan_allowed = true; + + return 0; +} + +static void drm_test_pick_cmdline_res_1920_1080_60(struct kunit *test) +{ + struct drm_client_modeset_test_priv *priv = test->priv; + struct drm_device *drm = priv->drm; + struct drm_connector *connector = &priv->connector; + struct drm_cmdline_mode *cmdline_mode = &connector->cmdline_mode; + struct drm_display_mode *expected_mode; + const struct drm_display_mode *mode; + const char *cmdline = "1920x1080@60"; + int ret; + + expected_mode = drm_mode_find_dmt(priv->drm, 1920, 1080, 60, false); + KUNIT_ASSERT_NOT_NULL(test, expected_mode); + + ret = drm_kunit_add_mode_destroy_action(test, expected_mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_ASSERT_TRUE(test, + drm_mode_parse_command_line_for_connector(cmdline, + connector, + cmdline_mode)); + + mutex_lock(&drm->mode_config.mutex); + ret = drm_helper_probe_single_connector_modes(connector, 1920, 1080); + mutex_unlock(&drm->mode_config.mutex); + KUNIT_ASSERT_GT(test, ret, 0); + + mode = drm_connector_pick_cmdline_mode(connector); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_EXPECT_TRUE(test, drm_mode_equal(expected_mode, mode)); +} + +struct drm_connector_pick_cmdline_mode_test { + const char *cmdline; + struct drm_display_mode *(*func)(struct drm_device *drm); +}; + +#define TEST_CMDLINE(_cmdline, _fn) \ + { \ + .cmdline = _cmdline, \ + .func = _fn, \ + } + +static void drm_test_pick_cmdline_named(struct kunit *test) +{ + const struct drm_connector_pick_cmdline_mode_test *params = test->param_value; + struct drm_client_modeset_test_priv *priv = test->priv; + struct drm_device *drm = priv->drm; + struct drm_connector *connector = &priv->connector; + struct drm_cmdline_mode *cmdline_mode = &connector->cmdline_mode; + const struct drm_display_mode *mode; + struct drm_display_mode *expected_mode; + const char *cmdline = params->cmdline; + int ret; + + KUNIT_ASSERT_TRUE(test, + drm_mode_parse_command_line_for_connector(cmdline, + connector, + cmdline_mode)); + + mutex_lock(&drm->mode_config.mutex); + ret = drm_helper_probe_single_connector_modes(connector, 1920, 1080); + mutex_unlock(&drm->mode_config.mutex); + KUNIT_ASSERT_GT(test, ret, 0); + + mode = drm_connector_pick_cmdline_mode(connector); + KUNIT_ASSERT_NOT_NULL(test, mode); + + expected_mode = params->func(drm); + KUNIT_ASSERT_NOT_NULL(test, expected_mode); + + ret = drm_kunit_add_mode_destroy_action(test, expected_mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_TRUE(test, drm_mode_equal(expected_mode, mode)); +} + +static const +struct drm_connector_pick_cmdline_mode_test drm_connector_pick_cmdline_mode_tests[] = { + TEST_CMDLINE("NTSC", drm_mode_analog_ntsc_480i), + TEST_CMDLINE("NTSC-J", drm_mode_analog_ntsc_480i), + TEST_CMDLINE("PAL", drm_mode_analog_pal_576i), + TEST_CMDLINE("PAL-M", drm_mode_analog_ntsc_480i), +}; + +static void +drm_connector_pick_cmdline_mode_desc(const struct drm_connector_pick_cmdline_mode_test *t, + char *desc) +{ + sprintf(desc, "%s", t->cmdline); +} + +KUNIT_ARRAY_PARAM(drm_connector_pick_cmdline_mode, + drm_connector_pick_cmdline_mode_tests, + drm_connector_pick_cmdline_mode_desc); + +static struct kunit_case drm_test_pick_cmdline_tests[] = { + KUNIT_CASE(drm_test_pick_cmdline_res_1920_1080_60), + KUNIT_CASE_PARAM(drm_test_pick_cmdline_named, + drm_connector_pick_cmdline_mode_gen_params), + {} +}; + +static struct kunit_suite drm_test_pick_cmdline_test_suite = { + .name = "drm_test_pick_cmdline", + .init = drm_client_modeset_test_init, + .test_cases = drm_test_pick_cmdline_tests +}; + +kunit_test_suite(drm_test_pick_cmdline_test_suite); + +/* + * This file is included directly by drm_client_modeset.c so we can't + * use any MODULE_* macro here. + */ diff --git a/drivers/gpu/drm/tests/drm_cmdline_parser_test.c b/drivers/gpu/drm/tests/drm_cmdline_parser_test.c new file mode 100644 index 000000000..1cfcb597b --- /dev/null +++ b/drivers/gpu/drm/tests/drm_cmdline_parser_test.c @@ -0,0 +1,1079 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Bootlin + * Copyright (c) 2022 Maíra Canal <mairacanal@riseup.net> + */ + +#include <kunit/test.h> + +#include <drm/drm_connector.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_modes.h> + +static const struct drm_connector no_connector = {}; + +static void drm_test_cmdline_force_e_only(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "e"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_FALSE(test, mode.specified); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static void drm_test_cmdline_force_D_only_not_digital(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "D"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_FALSE(test, mode.specified); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static const struct drm_connector connector_hdmi = { + .connector_type = DRM_MODE_CONNECTOR_HDMIB, +}; + +static void drm_test_cmdline_force_D_only_hdmi(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "D"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &connector_hdmi, &mode)); + KUNIT_EXPECT_FALSE(test, mode.specified); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON_DIGITAL); +} + +static const struct drm_connector connector_dvi = { + .connector_type = DRM_MODE_CONNECTOR_DVII, +}; + +static void drm_test_cmdline_force_D_only_dvi(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "D"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &connector_dvi, &mode)); + KUNIT_EXPECT_FALSE(test, mode.specified); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON_DIGITAL); +} + +static void drm_test_cmdline_force_d_only(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "d"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_FALSE(test, mode.specified); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_OFF); +} + +static void drm_test_cmdline_res(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_vesa(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480M"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_TRUE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_vesa_rblank(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480MR"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_TRUE(test, mode.rb); + KUNIT_EXPECT_TRUE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_rblank(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480R"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_TRUE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_bpp(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_refresh(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480@60"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_bpp_refresh(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24@60"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_bpp_refresh_interlaced(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24@60i"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_TRUE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_bpp_refresh_margins(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24@60m"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_TRUE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_res_bpp_refresh_force_off(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24@60d"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_OFF); +} + +static void drm_test_cmdline_res_bpp_refresh_force_on(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24@60e"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static void drm_test_cmdline_res_bpp_refresh_force_on_analog(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24@60D"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static void drm_test_cmdline_res_bpp_refresh_force_on_digital(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + static const struct drm_connector connector = { + .connector_type = DRM_MODE_CONNECTOR_DVII, + }; + const char *cmdline = "720x480-24@60D"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON_DIGITAL); +} + +static void drm_test_cmdline_res_bpp_refresh_interlaced_margins_force_on(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24@60ime"; + + KUNIT_EXPECT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_TRUE(test, mode.refresh_specified); + KUNIT_EXPECT_EQ(test, mode.refresh, 60); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_TRUE(test, mode.interlace); + KUNIT_EXPECT_TRUE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static void drm_test_cmdline_res_margins_force_on(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480me"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_TRUE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static void drm_test_cmdline_res_vesa_margins(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480Mm"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_TRUE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_TRUE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_name(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "NTSC"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_STREQ(test, mode.name, "NTSC"); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); +} + +static void drm_test_cmdline_name_bpp(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "NTSC-24"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_STREQ(test, mode.name, "NTSC"); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); +} + +static void drm_test_cmdline_name_option(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "NTSC,rotate=180"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_STREQ(test, mode.name, "NTSC"); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, DRM_MODE_ROTATE_180); +} + +static void drm_test_cmdline_name_bpp_option(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "NTSC-24,rotate=180"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_STREQ(test, mode.name, "NTSC"); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, DRM_MODE_ROTATE_180); + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); +} + +static void drm_test_cmdline_rotate_0(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480,rotate=0"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, DRM_MODE_ROTATE_0); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_rotate_90(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480,rotate=90"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, DRM_MODE_ROTATE_90); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_rotate_180(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480,rotate=180"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, DRM_MODE_ROTATE_180); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_rotate_270(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480,rotate=270"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, DRM_MODE_ROTATE_270); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_hmirror(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480,reflect_x"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, (DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X)); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_vmirror(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480,reflect_y"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, (DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y)); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_margin_options(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = + "720x480,margin_right=14,margin_left=24,margin_bottom=36,margin_top=42"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.tv_margins.right, 14); + KUNIT_EXPECT_EQ(test, mode.tv_margins.left, 24); + KUNIT_EXPECT_EQ(test, mode.tv_margins.bottom, 36); + KUNIT_EXPECT_EQ(test, mode.tv_margins.top, 42); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_multiple_options(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480,rotate=270,reflect_x"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, (DRM_MODE_ROTATE_270 | DRM_MODE_REFLECT_X)); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_bpp_extra_and_option(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480-24e,rotate=180"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, DRM_MODE_ROTATE_180); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_TRUE(test, mode.bpp_specified); + KUNIT_EXPECT_EQ(test, mode.bpp, 24); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static void drm_test_cmdline_extra_and_option(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "720x480e,rotate=180"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, 720); + KUNIT_EXPECT_EQ(test, mode.yres, 480); + KUNIT_EXPECT_EQ(test, mode.rotation_reflection, DRM_MODE_ROTATE_180); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static void drm_test_cmdline_freestanding_options(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "margin_right=14,margin_left=24,margin_bottom=36,margin_top=42"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_FALSE(test, mode.specified); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_EQ(test, mode.tv_margins.right, 14); + KUNIT_EXPECT_EQ(test, mode.tv_margins.left, 24); + KUNIT_EXPECT_EQ(test, mode.tv_margins.bottom, 36); + KUNIT_EXPECT_EQ(test, mode.tv_margins.top, 42); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +static void drm_test_cmdline_freestanding_force_e_and_options(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "e,margin_right=14,margin_left=24,margin_bottom=36,margin_top=42"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_FALSE(test, mode.specified); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_EQ(test, mode.tv_margins.right, 14); + KUNIT_EXPECT_EQ(test, mode.tv_margins.left, 24); + KUNIT_EXPECT_EQ(test, mode.tv_margins.bottom, 36); + KUNIT_EXPECT_EQ(test, mode.tv_margins.top, 42); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_ON); +} + +static void drm_test_cmdline_panel_orientation(struct kunit *test) +{ + struct drm_cmdline_mode mode = { }; + const char *cmdline = "panel_orientation=upside_down"; + + KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_FALSE(test, mode.specified); + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_EQ(test, mode.panel_orientation, DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_FALSE(test, mode.interlace); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +struct drm_cmdline_invalid_test { + const char *name; + const char *cmdline; +}; + +static void drm_test_cmdline_invalid(struct kunit *test) +{ + const struct drm_cmdline_invalid_test *params = test->param_value; + struct drm_cmdline_mode mode = { }; + + KUNIT_EXPECT_FALSE(test, drm_mode_parse_command_line_for_connector(params->cmdline, + &no_connector, + &mode)); +} + +static const struct drm_cmdline_invalid_test drm_cmdline_invalid_tests[] = { + { + .name = "margin_only", + .cmdline = "m", + }, + { + .name = "interlace_only", + .cmdline = "i", + }, + { + .name = "res_missing_x", + .cmdline = "x480", + }, + { + .name = "res_missing_y", + .cmdline = "1024x", + }, + { + .name = "res_bad_y", + .cmdline = "1024xtest", + }, + { + .name = "res_missing_y_bpp", + .cmdline = "1024x-24", + }, + { + .name = "res_bad_bpp", + .cmdline = "720x480-test", + }, + { + .name = "res_bad_refresh", + .cmdline = "720x480@refresh", + }, + { + .name = "res_bpp_refresh_force_on_off", + .cmdline = "720x480-24@60de", + }, + { + .name = "res_invalid_mode", + .cmdline = "720x480f", + }, + { + .name = "res_bpp_wrong_place_mode", + .cmdline = "720x480e-24", + }, + { + .name = "name_bpp_refresh", + .cmdline = "NTSC-24@60", + }, + { + .name = "name_refresh", + .cmdline = "NTSC@60", + }, + { + .name = "name_refresh_wrong_mode", + .cmdline = "NTSC@60m", + }, + { + .name = "name_refresh_invalid_mode", + .cmdline = "NTSC@60f", + }, + { + .name = "rotate_multiple", + .cmdline = "720x480,rotate=0,rotate=90", + }, + { + .name = "rotate_invalid_val", + .cmdline = "720x480,rotate=42", + }, + { + .name = "rotate_truncated", + .cmdline = "720x480,rotate=", + }, + { + .name = "invalid_option", + .cmdline = "720x480,test=42", + }, + { + .name = "invalid_tv_option", + .cmdline = "720x480i,tv_mode=invalid", + }, + { + .name = "truncated_tv_option", + .cmdline = "720x480i,tv_mode=NTS", + }, +}; + +static void drm_cmdline_invalid_desc(const struct drm_cmdline_invalid_test *t, + char *desc) +{ + sprintf(desc, "%s", t->name); +} + +KUNIT_ARRAY_PARAM(drm_cmdline_invalid, drm_cmdline_invalid_tests, drm_cmdline_invalid_desc); + +struct drm_cmdline_tv_option_test { + const char *name; + const char *cmdline; + struct drm_display_mode *(*mode_fn)(struct drm_device *dev); + enum drm_connector_tv_mode tv_mode; +}; + +static void drm_test_cmdline_tv_options(struct kunit *test) +{ + const struct drm_cmdline_tv_option_test *params = test->param_value; + struct drm_display_mode *expected_mode; + struct drm_cmdline_mode mode = { }; + int ret; + + expected_mode = params->mode_fn(NULL); + KUNIT_ASSERT_NOT_NULL(test, expected_mode); + + ret = drm_kunit_add_mode_destroy_action(test, expected_mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_TRUE(test, drm_mode_parse_command_line_for_connector(params->cmdline, + &no_connector, &mode)); + KUNIT_EXPECT_TRUE(test, mode.specified); + KUNIT_EXPECT_EQ(test, mode.xres, expected_mode->hdisplay); + KUNIT_EXPECT_EQ(test, mode.yres, expected_mode->vdisplay); + KUNIT_EXPECT_EQ(test, mode.tv_mode, params->tv_mode); + + KUNIT_EXPECT_FALSE(test, mode.refresh_specified); + + KUNIT_EXPECT_FALSE(test, mode.bpp_specified); + + KUNIT_EXPECT_FALSE(test, mode.rb); + KUNIT_EXPECT_FALSE(test, mode.cvt); + KUNIT_EXPECT_EQ(test, mode.interlace, !!(expected_mode->flags & DRM_MODE_FLAG_INTERLACE)); + KUNIT_EXPECT_FALSE(test, mode.margins); + KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED); +} + +#define TV_OPT_TEST(_opt, _cmdline, _mode_fn) \ + { \ + .name = #_opt, \ + .cmdline = _cmdline, \ + .mode_fn = _mode_fn, \ + .tv_mode = DRM_MODE_TV_MODE_ ## _opt, \ + } + +static const struct drm_cmdline_tv_option_test drm_cmdline_tv_option_tests[] = { + TV_OPT_TEST(NTSC, "720x480i,tv_mode=NTSC", drm_mode_analog_ntsc_480i), + TV_OPT_TEST(NTSC_443, "720x480i,tv_mode=NTSC-443", drm_mode_analog_ntsc_480i), + TV_OPT_TEST(NTSC_J, "720x480i,tv_mode=NTSC-J", drm_mode_analog_ntsc_480i), + TV_OPT_TEST(PAL, "720x576i,tv_mode=PAL", drm_mode_analog_pal_576i), + TV_OPT_TEST(PAL_M, "720x480i,tv_mode=PAL-M", drm_mode_analog_ntsc_480i), + TV_OPT_TEST(PAL_N, "720x576i,tv_mode=PAL-N", drm_mode_analog_pal_576i), + TV_OPT_TEST(SECAM, "720x576i,tv_mode=SECAM", drm_mode_analog_pal_576i), + { + .name = "MONO_525", + .cmdline = "720x480i,tv_mode=Mono", + .mode_fn = drm_mode_analog_ntsc_480i, + .tv_mode = DRM_MODE_TV_MODE_MONOCHROME, + }, { + .name = "MONO_625", + .cmdline = "720x576i,tv_mode=Mono", + .mode_fn = drm_mode_analog_pal_576i, + .tv_mode = DRM_MODE_TV_MODE_MONOCHROME, + }, +}; + +static void drm_cmdline_tv_option_desc(const struct drm_cmdline_tv_option_test *t, + char *desc) +{ + sprintf(desc, "%s", t->name); +} + +KUNIT_ARRAY_PARAM(drm_cmdline_tv_option, + drm_cmdline_tv_option_tests, + drm_cmdline_tv_option_desc); + +static struct kunit_case drm_cmdline_parser_tests[] = { + KUNIT_CASE(drm_test_cmdline_force_d_only), + KUNIT_CASE(drm_test_cmdline_force_D_only_dvi), + KUNIT_CASE(drm_test_cmdline_force_D_only_hdmi), + KUNIT_CASE(drm_test_cmdline_force_D_only_not_digital), + KUNIT_CASE(drm_test_cmdline_force_e_only), + KUNIT_CASE(drm_test_cmdline_res), + KUNIT_CASE(drm_test_cmdline_res_vesa), + KUNIT_CASE(drm_test_cmdline_res_vesa_rblank), + KUNIT_CASE(drm_test_cmdline_res_rblank), + KUNIT_CASE(drm_test_cmdline_res_bpp), + KUNIT_CASE(drm_test_cmdline_res_refresh), + KUNIT_CASE(drm_test_cmdline_res_bpp_refresh), + KUNIT_CASE(drm_test_cmdline_res_bpp_refresh_interlaced), + KUNIT_CASE(drm_test_cmdline_res_bpp_refresh_margins), + KUNIT_CASE(drm_test_cmdline_res_bpp_refresh_force_off), + KUNIT_CASE(drm_test_cmdline_res_bpp_refresh_force_on), + KUNIT_CASE(drm_test_cmdline_res_bpp_refresh_force_on_analog), + KUNIT_CASE(drm_test_cmdline_res_bpp_refresh_force_on_digital), + KUNIT_CASE(drm_test_cmdline_res_bpp_refresh_interlaced_margins_force_on), + KUNIT_CASE(drm_test_cmdline_res_margins_force_on), + KUNIT_CASE(drm_test_cmdline_res_vesa_margins), + KUNIT_CASE(drm_test_cmdline_name), + KUNIT_CASE(drm_test_cmdline_name_bpp), + KUNIT_CASE(drm_test_cmdline_name_option), + KUNIT_CASE(drm_test_cmdline_name_bpp_option), + KUNIT_CASE(drm_test_cmdline_rotate_0), + KUNIT_CASE(drm_test_cmdline_rotate_90), + KUNIT_CASE(drm_test_cmdline_rotate_180), + KUNIT_CASE(drm_test_cmdline_rotate_270), + KUNIT_CASE(drm_test_cmdline_hmirror), + KUNIT_CASE(drm_test_cmdline_vmirror), + KUNIT_CASE(drm_test_cmdline_margin_options), + KUNIT_CASE(drm_test_cmdline_multiple_options), + KUNIT_CASE(drm_test_cmdline_bpp_extra_and_option), + KUNIT_CASE(drm_test_cmdline_extra_and_option), + KUNIT_CASE(drm_test_cmdline_freestanding_options), + KUNIT_CASE(drm_test_cmdline_freestanding_force_e_and_options), + KUNIT_CASE(drm_test_cmdline_panel_orientation), + KUNIT_CASE_PARAM(drm_test_cmdline_invalid, drm_cmdline_invalid_gen_params), + KUNIT_CASE_PARAM(drm_test_cmdline_tv_options, drm_cmdline_tv_option_gen_params), + {} +}; + +static struct kunit_suite drm_cmdline_parser_test_suite = { + .name = "drm_cmdline_parser", + .test_cases = drm_cmdline_parser_tests +}; + +kunit_test_suite(drm_cmdline_parser_test_suite); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); +MODULE_DESCRIPTION("Kunit test for drm_cmdline_parser functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c new file mode 100644 index 000000000..beb1d50a6 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_connector_test.c @@ -0,0 +1,1837 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Kunit test for drm_modes functions + */ + +#include <linux/i2c.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_file.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_modes.h> + +#include <drm/display/drm_hdmi_helper.h> + +#include <kunit/test.h> + +#include "../drm_crtc_internal.h" + +struct drm_connector_init_priv { + struct drm_device drm; + struct drm_connector connector; + struct i2c_adapter ddc; +}; + +static int accept_infoframe_clear_infoframe(struct drm_connector *connector) +{ + return 0; +} + +static int accept_infoframe_write_infoframe(struct drm_connector *connector, + const u8 *buffer, size_t len) +{ + return 0; +} + +static const struct drm_connector_hdmi_funcs dummy_hdmi_funcs = { + .avi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, + .hdmi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, +}; + +static const struct drm_connector_funcs dummy_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 dummy_ddc_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + return num; +} + +static u32 dummy_ddc_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm dummy_ddc_algorithm = { + .master_xfer = dummy_ddc_xfer, + .functionality = dummy_ddc_func, +}; + +static void i2c_del_adapter_wrapper(void *ptr) +{ + struct i2c_adapter *adap = ptr; + + i2c_del_adapter(adap); +} + +static int drm_test_connector_init(struct kunit *test) +{ + struct drm_connector_init_priv *priv; + 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_connector_init_priv, drm, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + strscpy(priv->ddc.name, "dummy-connector-ddc", sizeof(priv->ddc.name)); + priv->ddc.owner = THIS_MODULE; + priv->ddc.algo = &dummy_ddc_algorithm; + priv->ddc.dev.parent = dev; + + ret = i2c_add_adapter(&priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_add_action_or_reset(test, i2c_del_adapter_wrapper, &priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); + + test->priv = priv; + return 0; +} + +/* + * Test that the registration of a bog standard connector works as + * expected and doesn't report any error. + */ +static void drm_test_drmm_connector_init(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_init(&priv->drm, &priv->connector, + &dummy_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/* + * Test that the registration of a connector without a DDC adapter + * doesn't report any error. + */ +static void drm_test_drmm_connector_init_null_ddc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_init(&priv->drm, &priv->connector, + &dummy_funcs, + DRM_MODE_CONNECTOR_HDMIA, + NULL); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/* + * Test that the registration of a connector succeeds for all possible + * connector types. + */ +static void drm_test_drmm_connector_init_type_valid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + unsigned int connector_type = *(unsigned int *)test->param_value; + int ret; + + ret = drmm_connector_init(&priv->drm, &priv->connector, + &dummy_funcs, + connector_type, + &priv->ddc); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +static const unsigned int drm_connector_init_type_valid_tests[] = { + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_VGA, + DRM_MODE_CONNECTOR_DVII, + DRM_MODE_CONNECTOR_DVID, + DRM_MODE_CONNECTOR_DVIA, + DRM_MODE_CONNECTOR_Composite, + DRM_MODE_CONNECTOR_SVIDEO, + DRM_MODE_CONNECTOR_LVDS, + DRM_MODE_CONNECTOR_Component, + DRM_MODE_CONNECTOR_9PinDIN, + DRM_MODE_CONNECTOR_DisplayPort, + DRM_MODE_CONNECTOR_HDMIA, + DRM_MODE_CONNECTOR_HDMIB, + DRM_MODE_CONNECTOR_TV, + DRM_MODE_CONNECTOR_eDP, + DRM_MODE_CONNECTOR_VIRTUAL, + DRM_MODE_CONNECTOR_DSI, + DRM_MODE_CONNECTOR_DPI, + DRM_MODE_CONNECTOR_WRITEBACK, + DRM_MODE_CONNECTOR_SPI, + DRM_MODE_CONNECTOR_USB, +}; + +static void drm_connector_init_type_desc(const unsigned int *type, char *desc) +{ + sprintf(desc, "%s", drm_get_connector_type_name(*type)); +} + +KUNIT_ARRAY_PARAM(drm_connector_init_type_valid, + drm_connector_init_type_valid_tests, + drm_connector_init_type_desc); + +static struct kunit_case drmm_connector_init_tests[] = { + KUNIT_CASE(drm_test_drmm_connector_init), + KUNIT_CASE(drm_test_drmm_connector_init_null_ddc), + KUNIT_CASE_PARAM(drm_test_drmm_connector_init_type_valid, + drm_connector_init_type_valid_gen_params), + { } +}; + +static struct kunit_suite drmm_connector_init_test_suite = { + .name = "drmm_connector_init", + .init = drm_test_connector_init, + .test_cases = drmm_connector_init_tests, +}; + +static const struct drm_connector_funcs dummy_dynamic_init_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, + .destroy = drm_connector_cleanup, +}; + +/* + * Test that the initialization of a bog standard dynamic connector works + * as expected and doesn't report any error. + */ +static void drm_test_drm_connector_dynamic_init(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + int ret; + + ret = drm_connector_dynamic_init(&priv->drm, connector, + &dummy_dynamic_init_funcs, + DRM_MODE_CONNECTOR_DisplayPort, + &priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); +} + +static void drm_test_connector_dynamic_init_cleanup(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + + drm_connector_cleanup(connector); +} + +/* + * Test that the initialization of a dynamic connector without a DDC adapter + * doesn't report any error. + */ +static void drm_test_drm_connector_dynamic_init_null_ddc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + int ret; + + ret = drm_connector_dynamic_init(&priv->drm, connector, + &dummy_dynamic_init_funcs, + DRM_MODE_CONNECTOR_DisplayPort, + NULL); + KUNIT_ASSERT_EQ(test, ret, 0); +} + +/* + * Test that the initialization of a dynamic connector doesn't add the + * connector to the connector list. + */ +static void drm_test_drm_connector_dynamic_init_not_added(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + int ret; + + ret = drm_connector_dynamic_init(&priv->drm, connector, + &dummy_dynamic_init_funcs, + DRM_MODE_CONNECTOR_DisplayPort, + &priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_PTR_EQ(test, connector->head.next, &connector->head); +} + +static void test_connector_property(struct kunit *test, + struct drm_connector *connector, + const struct drm_property *expected_prop) +{ + struct drm_property *prop; + uint64_t val; + int ret; + + KUNIT_ASSERT_NOT_NULL(test, expected_prop); + prop = drm_mode_obj_find_prop_id(&connector->base, expected_prop->base.id); + KUNIT_ASSERT_PTR_EQ_MSG(test, prop, expected_prop, + "Can't find property %s", expected_prop->name); + + ret = drm_object_property_get_default_value(&connector->base, prop, &val); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, val, 0); + + /* TODO: Check property value in the connector state. */ +} + +/* + * Test that the initialization of a dynamic connector adds all the expected + * properties to it. + */ +static void drm_test_drm_connector_dynamic_init_properties(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + struct drm_mode_config *config = &priv->drm.mode_config; + const struct drm_property *props[] = { + config->edid_property, + config->dpms_property, + config->link_status_property, + config->non_desktop_property, + config->tile_property, + config->prop_crtc_id, + }; + int ret; + int i; + + ret = drm_connector_dynamic_init(&priv->drm, connector, + &dummy_dynamic_init_funcs, + DRM_MODE_CONNECTOR_DisplayPort, + &priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); + + for (i = 0; i < ARRAY_SIZE(props); i++) + test_connector_property(test, connector, props[i]); +} + +/* + * Test that the initialization of a dynamic connector succeeds for all + * possible connector types. + */ +static void drm_test_drm_connector_dynamic_init_type_valid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + unsigned int connector_type = *(unsigned int *)test->param_value; + int ret; + + ret = drm_connector_dynamic_init(&priv->drm, connector, + &dummy_dynamic_init_funcs, + connector_type, + &priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); +} + +/* + * Test that the initialization of a dynamic connector sets the expected name + * for it for all possible connector types. + */ +static void drm_test_drm_connector_dynamic_init_name(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + unsigned int connector_type = *(unsigned int *)test->param_value; + char expected_name[128]; + int ret; + + ret = drm_connector_dynamic_init(&priv->drm, connector, + &dummy_dynamic_init_funcs, + connector_type, + &priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); + + snprintf(expected_name, sizeof(expected_name), "%s-%d", + drm_get_connector_type_name(connector_type), connector->connector_type_id); + KUNIT_ASSERT_STREQ(test, connector->name, expected_name); +} + +static struct kunit_case drm_connector_dynamic_init_tests[] = { + KUNIT_CASE(drm_test_drm_connector_dynamic_init), + KUNIT_CASE(drm_test_drm_connector_dynamic_init_null_ddc), + KUNIT_CASE(drm_test_drm_connector_dynamic_init_not_added), + KUNIT_CASE(drm_test_drm_connector_dynamic_init_properties), + KUNIT_CASE_PARAM(drm_test_drm_connector_dynamic_init_type_valid, + drm_connector_init_type_valid_gen_params), + KUNIT_CASE_PARAM(drm_test_drm_connector_dynamic_init_name, + drm_connector_init_type_valid_gen_params), + {} +}; + +static struct kunit_suite drm_connector_dynamic_init_test_suite = { + .name = "drm_connector_dynamic_init", + .init = drm_test_connector_init, + .exit = drm_test_connector_dynamic_init_cleanup, + .test_cases = drm_connector_dynamic_init_tests, +}; + +static int drm_test_connector_dynamic_register_early_init(struct kunit *test) +{ + struct drm_connector_init_priv *priv; + int ret; + + ret = drm_test_connector_init(test); + KUNIT_ASSERT_EQ(test, ret, 0); + + priv = test->priv; + + ret = drm_connector_dynamic_init(&priv->drm, &priv->connector, + &dummy_dynamic_init_funcs, + DRM_MODE_CONNECTOR_DisplayPort, + &priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); + + return 0; +} + +static void drm_test_connector_dynamic_register_early_cleanup(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + + drm_connector_unregister(connector); + drm_connector_put(connector); +} + +/* + * Test that registration of a dynamic connector adds it to the connector list. + */ +static void drm_test_drm_connector_dynamic_register_early_on_list(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + int ret; + + KUNIT_ASSERT_TRUE(test, list_empty(&connector->head)); + + ret = drm_connector_dynamic_register(connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_ASSERT_PTR_EQ(test, connector->head.next, &priv->drm.mode_config.connector_list); +} + +/* + * Test that the registration of a dynamic connector before the drm device is + * registered results in deferring the connector's user interface registration. + */ +static void drm_test_drm_connector_dynamic_register_early_defer(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + int ret; + + ret = drm_connector_dynamic_register(connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_ASSERT_EQ(test, connector->registration_state, DRM_CONNECTOR_INITIALIZING); +} + +/* + * Test that the registration of a dynamic connector fails, if this is done before + * the connector is initialized. + */ +static void drm_test_drm_connector_dynamic_register_early_no_init(struct kunit *test) +{ + struct drm_connector *connector; + int ret; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); /* auto freed */ + KUNIT_ASSERT_NOT_NULL(test, connector); + + ret = drm_connector_dynamic_register(connector); + KUNIT_ASSERT_EQ(test, ret, -EINVAL); +} + +/* + * Test that the registration of a dynamic connector before the drm device is + * registered results in deferring adding a mode object for the connector. + */ +static void drm_test_drm_connector_dynamic_register_early_no_mode_object(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + struct drm_connector *tmp_connector; + int ret; + + ret = drm_connector_dynamic_register(&priv->connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + tmp_connector = drm_connector_lookup(connector->dev, NULL, connector->base.id); + KUNIT_ASSERT_NULL(test, tmp_connector); +} + +static struct kunit_case drm_connector_dynamic_register_early_tests[] = { + KUNIT_CASE(drm_test_drm_connector_dynamic_register_early_on_list), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_early_defer), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_early_no_init), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_early_no_mode_object), + { } +}; + +static struct kunit_suite drm_connector_dynamic_register_early_test_suite = { + .name = "drm_connector_dynamic_register_early", + .init = drm_test_connector_dynamic_register_early_init, + .exit = drm_test_connector_dynamic_register_early_cleanup, + .test_cases = drm_connector_dynamic_register_early_tests, +}; + +static int drm_test_connector_dynamic_register_init(struct kunit *test) +{ + struct drm_connector_init_priv *priv; + int ret; + + ret = drm_test_connector_dynamic_register_early_init(test); + KUNIT_ASSERT_EQ(test, ret, 0); + + priv = test->priv; + + ret = drm_dev_register(priv->connector.dev, 0); + KUNIT_ASSERT_EQ(test, ret, 0); + + return 0; +} + +static void drm_test_connector_dynamic_register_cleanup(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_device *dev = priv->connector.dev; + + drm_connector_unregister(&priv->connector); + drm_connector_put(&priv->connector); + + drm_dev_unregister(dev); + + drm_test_connector_dynamic_register_early_cleanup(test); +} + +static void drm_test_drm_connector_dynamic_register_on_list(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + KUNIT_ASSERT_TRUE(test, list_empty(&priv->connector.head)); + + ret = drm_connector_dynamic_register(&priv->connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_ASSERT_PTR_EQ(test, priv->connector.head.next, &priv->drm.mode_config.connector_list); +} + +/* + * Test that the registration of a dynamic connector doesn't get deferred if + * this is done after the drm device is registered. + */ +static void drm_test_drm_connector_dynamic_register_no_defer(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + KUNIT_ASSERT_EQ(test, priv->connector.registration_state, DRM_CONNECTOR_INITIALIZING); + + ret = drm_connector_dynamic_register(&priv->connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_ASSERT_EQ(test, priv->connector.registration_state, DRM_CONNECTOR_REGISTERED); +} + +/* + * Test that the registration of a dynamic connector fails if this is done after the + * drm device is registered, but before the connector is initialized. + */ +static void drm_test_drm_connector_dynamic_register_no_init(struct kunit *test) +{ + struct drm_connector *connector; + int ret; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); /* auto freed */ + KUNIT_ASSERT_NOT_NULL(test, connector); + + ret = drm_connector_dynamic_register(connector); + KUNIT_ASSERT_EQ(test, ret, -EINVAL); +} + +/* + * Test that the registration of a dynamic connector after the drm device is + * registered adds the mode object for the connector. + */ +static void drm_test_drm_connector_dynamic_register_mode_object(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + struct drm_connector *tmp_connector; + int ret; + + tmp_connector = drm_connector_lookup(connector->dev, NULL, connector->base.id); + KUNIT_ASSERT_NULL(test, tmp_connector); + + ret = drm_connector_dynamic_register(&priv->connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + tmp_connector = drm_connector_lookup(connector->dev, NULL, connector->base.id); + KUNIT_ASSERT_PTR_EQ(test, tmp_connector, connector); +} + +/* + * Test that the registration of a dynamic connector after the drm device is + * registered adds the connector to sysfs. + */ +static void drm_test_drm_connector_dynamic_register_sysfs(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + int ret; + + KUNIT_ASSERT_NULL(test, connector->kdev); + + ret = drm_connector_dynamic_register(connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_ASSERT_NOT_NULL(test, connector->kdev); +} + +/* + * Test that the registration of a dynamic connector after the drm device is + * registered sets the connector's sysfs name as expected. + */ +static void drm_test_drm_connector_dynamic_register_sysfs_name(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + char expected_name[128]; + int ret; + + ret = drm_connector_dynamic_register(connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + snprintf(expected_name, sizeof(expected_name), "card%d-%s", + connector->dev->primary->index, connector->name); + + KUNIT_ASSERT_STREQ(test, dev_name(connector->kdev), expected_name); +} + +/* + * Test that the registration of a dynamic connector after the drm device is + * registered adds the connector to debugfs. + */ +static void drm_test_drm_connector_dynamic_register_debugfs(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + KUNIT_ASSERT_NULL(test, priv->connector.debugfs_entry); + + ret = drm_connector_dynamic_register(&priv->connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + if (IS_ENABLED(CONFIG_DEBUG_FS)) + KUNIT_ASSERT_NOT_NULL(test, priv->connector.debugfs_entry); + else + KUNIT_ASSERT_NULL(test, priv->connector.debugfs_entry); +} + +static struct kunit_case drm_connector_dynamic_register_tests[] = { + KUNIT_CASE(drm_test_drm_connector_dynamic_register_on_list), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_no_defer), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_no_init), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_mode_object), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_sysfs), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_sysfs_name), + KUNIT_CASE(drm_test_drm_connector_dynamic_register_debugfs), + { } +}; + +static struct kunit_suite drm_connector_dynamic_register_test_suite = { + .name = "drm_connector_dynamic_register", + .init = drm_test_connector_dynamic_register_init, + .exit = drm_test_connector_dynamic_register_cleanup, + .test_cases = drm_connector_dynamic_register_tests, +}; + +/* + * Test that the registration of a bog standard connector works as + * expected and doesn't report any error. + */ +static void drm_test_connector_hdmi_init_valid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/* + * Test that the registration of a connector without a DDC adapter + * doesn't report any error. + */ +static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + NULL, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/* + * Test that the registration of an HDMI connector with a NULL vendor + * fails. + */ +static void drm_test_connector_hdmi_init_null_vendor(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + NULL, "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_LT(test, ret, 0); +} + +/* + * Test that the registration of an HDMI connector with a NULL product + * fails. + */ +static void drm_test_connector_hdmi_init_null_product(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", NULL, + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_LT(test, ret, 0); +} + +/* + * Test that the registration of a connector with a valid, shorter than + * the max length, product name succeeds, and is stored padded with 0. + */ +static void drm_test_connector_hdmi_init_product_valid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const unsigned char expected_product[DRM_CONNECTOR_HDMI_PRODUCT_LEN] = { + 'P', 'r', 'o', 'd', + }; + const char *product_name = "Prod"; + int ret; + + KUNIT_ASSERT_LT(test, strlen(product_name), DRM_CONNECTOR_HDMI_PRODUCT_LEN); + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", product_name, + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_MEMEQ(test, + priv->connector.hdmi.product, + expected_product, + sizeof(priv->connector.hdmi.product)); +} + +/* + * Test that the registration of a connector with a valid, at max + * length, product name succeeds, and is stored padded without any + * trailing \0. + */ +static void drm_test_connector_hdmi_init_product_length_exact(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const unsigned char expected_product[DRM_CONNECTOR_HDMI_PRODUCT_LEN] = { + 'P', 'r', 'o', 'd', 'u', 'c', 't', + 'P', 'r', 'o', 'd', 'u', 'c', 't', + 'P', 'r', + }; + const char *product_name = "ProductProductPr"; + int ret; + + KUNIT_ASSERT_EQ(test, strlen(product_name), DRM_CONNECTOR_HDMI_PRODUCT_LEN); + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", product_name, + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_MEMEQ(test, + priv->connector.hdmi.product, + expected_product, + sizeof(priv->connector.hdmi.product)); +} + +/* + * Test that the registration of a connector with a product name larger + * than the maximum length fails. + */ +static void drm_test_connector_hdmi_init_product_length_too_long(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const char *product_name = "ProductProductProduct"; + int ret; + + KUNIT_ASSERT_GT(test, strlen(product_name), DRM_CONNECTOR_HDMI_PRODUCT_LEN); + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", product_name, + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_LT(test, ret, 0); +} + +/* + * Test that the registration of a connector with a vendor name smaller + * than the maximum length succeeds, and is stored padded with zeros. + */ +static void drm_test_connector_hdmi_init_vendor_valid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const char expected_vendor[DRM_CONNECTOR_HDMI_VENDOR_LEN] = { + 'V', 'e', 'n', 'd', + }; + const char *vendor_name = "Vend"; + int ret; + + KUNIT_ASSERT_LT(test, strlen(vendor_name), DRM_CONNECTOR_HDMI_VENDOR_LEN); + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + vendor_name, "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_MEMEQ(test, + priv->connector.hdmi.vendor, + expected_vendor, + sizeof(priv->connector.hdmi.vendor)); +} + +/* + * Test that the registration of a connector with a vendor name at the + * maximum length succeeds, and is stored padded without the trailing + * zero. + */ +static void drm_test_connector_hdmi_init_vendor_length_exact(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const char expected_vendor[DRM_CONNECTOR_HDMI_VENDOR_LEN] = { + 'V', 'e', 'n', 'd', 'o', 'r', + 'V', 'e', + }; + const char *vendor_name = "VendorVe"; + int ret; + + KUNIT_ASSERT_EQ(test, strlen(vendor_name), DRM_CONNECTOR_HDMI_VENDOR_LEN); + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + vendor_name, "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_MEMEQ(test, + priv->connector.hdmi.vendor, + expected_vendor, + sizeof(priv->connector.hdmi.vendor)); +} + +/* + * Test that the registration of a connector with a vendor name larger + * than the maximum length fails. + */ +static void drm_test_connector_hdmi_init_vendor_length_too_long(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const char *vendor_name = "VendorVendor"; + int ret; + + KUNIT_ASSERT_GT(test, strlen(vendor_name), DRM_CONNECTOR_HDMI_VENDOR_LEN); + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + vendor_name, "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_LT(test, ret, 0); +} + +/* + * Test that the registration of a connector with an invalid maximum bpc + * count fails. + */ +static void drm_test_connector_hdmi_init_bpc_invalid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 9); + KUNIT_EXPECT_LT(test, ret, 0); +} + +/* + * Test that the registration of a connector with a null maximum bpc + * count fails. + */ +static void drm_test_connector_hdmi_init_bpc_null(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 0); + KUNIT_EXPECT_LT(test, ret, 0); +} + +/* + * Test that the registration of a connector with a maximum bpc count of + * 8 succeeds, registers the max bpc property, but doesn't register the + * HDR output metadata one. + */ +static void drm_test_connector_hdmi_init_bpc_8(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector_state *state; + struct drm_connector *connector = &priv->connector; + struct drm_property *prop; + uint64_t val; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); + + prop = connector->max_bpc_property; + KUNIT_ASSERT_NOT_NULL(test, prop); + KUNIT_EXPECT_NOT_NULL(test, drm_mode_obj_find_prop_id(&connector->base, prop->base.id)); + + ret = drm_object_property_get_default_value(&connector->base, prop, &val); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, val, 8); + + state = connector->state; + KUNIT_EXPECT_EQ(test, state->max_bpc, 8); + KUNIT_EXPECT_EQ(test, state->max_requested_bpc, 8); + + prop = priv->drm.mode_config.hdr_output_metadata_property; + KUNIT_ASSERT_NOT_NULL(test, prop); + KUNIT_EXPECT_NULL(test, drm_mode_obj_find_prop_id(&connector->base, prop->base.id)); +} + +/* + * Test that the registration of a connector with a maximum bpc count of + * 10 succeeds and registers the max bpc and HDR output metadata + * properties. + */ +static void drm_test_connector_hdmi_init_bpc_10(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector_state *state; + struct drm_connector *connector = &priv->connector; + struct drm_property *prop; + uint64_t val; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 10); + KUNIT_EXPECT_EQ(test, ret, 0); + + prop = connector->max_bpc_property; + KUNIT_ASSERT_NOT_NULL(test, prop); + KUNIT_EXPECT_NOT_NULL(test, drm_mode_obj_find_prop_id(&connector->base, prop->base.id)); + + ret = drm_object_property_get_default_value(&connector->base, prop, &val); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, val, 10); + + state = connector->state; + KUNIT_EXPECT_EQ(test, state->max_bpc, 10); + KUNIT_EXPECT_EQ(test, state->max_requested_bpc, 10); + + prop = priv->drm.mode_config.hdr_output_metadata_property; + KUNIT_ASSERT_NOT_NULL(test, prop); + KUNIT_EXPECT_NOT_NULL(test, drm_mode_obj_find_prop_id(&connector->base, prop->base.id)); +} + +/* + * Test that the registration of a connector with a maximum bpc count of + * 12 succeeds and registers the max bpc and HDR output metadata + * properties. + */ +static void drm_test_connector_hdmi_init_bpc_12(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector_state *state; + struct drm_connector *connector = &priv->connector; + struct drm_property *prop; + uint64_t val; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 12); + KUNIT_EXPECT_EQ(test, ret, 0); + + prop = connector->max_bpc_property; + KUNIT_ASSERT_NOT_NULL(test, prop); + KUNIT_EXPECT_NOT_NULL(test, drm_mode_obj_find_prop_id(&connector->base, prop->base.id)); + + ret = drm_object_property_get_default_value(&connector->base, prop, &val); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, val, 12); + + state = connector->state; + KUNIT_EXPECT_EQ(test, state->max_bpc, 12); + KUNIT_EXPECT_EQ(test, state->max_requested_bpc, 12); + + prop = priv->drm.mode_config.hdr_output_metadata_property; + KUNIT_ASSERT_NOT_NULL(test, prop); + KUNIT_EXPECT_NOT_NULL(test, drm_mode_obj_find_prop_id(&connector->base, prop->base.id)); +} + +/* + * Test that the registration of an HDMI connector with no supported + * format fails. + */ +static void drm_test_connector_hdmi_init_formats_empty(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + 0, + 8); + KUNIT_EXPECT_LT(test, ret, 0); +} + +/* + * Test that the registration of an HDMI connector not listing RGB as a + * supported format fails. + */ +static void drm_test_connector_hdmi_init_formats_no_rgb(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422), + 8); + KUNIT_EXPECT_LT(test, ret, 0); +} + +struct drm_connector_hdmi_init_formats_yuv420_allowed_test { + unsigned long supported_formats; + bool yuv420_allowed; + int expected_result; +}; + +#define YUV420_ALLOWED_TEST(_formats, _allowed, _result) \ + { \ + .supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | (_formats), \ + .yuv420_allowed = _allowed, \ + .expected_result = _result, \ + } + +static const struct drm_connector_hdmi_init_formats_yuv420_allowed_test +drm_connector_hdmi_init_formats_yuv420_allowed_tests[] = { + YUV420_ALLOWED_TEST(BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), true, 0), + YUV420_ALLOWED_TEST(BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), false, -EINVAL), + YUV420_ALLOWED_TEST(BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422), true, -EINVAL), + YUV420_ALLOWED_TEST(BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422), false, 0), +}; + +static void +drm_connector_hdmi_init_formats_yuv420_allowed_desc(const struct drm_connector_hdmi_init_formats_yuv420_allowed_test *t, + char *desc) +{ + sprintf(desc, "supported_formats=0x%lx yuv420_allowed=%d", + t->supported_formats, t->yuv420_allowed); +} + +KUNIT_ARRAY_PARAM(drm_connector_hdmi_init_formats_yuv420_allowed, + drm_connector_hdmi_init_formats_yuv420_allowed_tests, + drm_connector_hdmi_init_formats_yuv420_allowed_desc); + +/* + * Test that the registration of an HDMI connector succeeds only when + * the presence of YUV420 in the supported formats matches the value + * of the ycbcr_420_allowed flag. + */ +static void drm_test_connector_hdmi_init_formats_yuv420_allowed(struct kunit *test) +{ + const struct drm_connector_hdmi_init_formats_yuv420_allowed_test *params; + struct drm_connector_init_priv *priv = test->priv; + int ret; + + params = test->param_value; + priv->connector.ycbcr_420_allowed = params->yuv420_allowed; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + params->supported_formats, + 8); + KUNIT_EXPECT_EQ(test, ret, params->expected_result); +} + +/* + * Test that the registration of an HDMI connector with an HDMI + * connector type succeeds. + */ +static void drm_test_connector_hdmi_init_type_valid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + unsigned int connector_type = *(unsigned int *)test->param_value; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + connector_type, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +static const unsigned int drm_connector_hdmi_init_type_valid_tests[] = { + DRM_MODE_CONNECTOR_HDMIA, + DRM_MODE_CONNECTOR_HDMIB, +}; + +static void drm_connector_hdmi_init_type_desc(const unsigned int *type, char *desc) +{ + sprintf(desc, "%s", drm_get_connector_type_name(*type)); +} + +KUNIT_ARRAY_PARAM(drm_connector_hdmi_init_type_valid, + drm_connector_hdmi_init_type_valid_tests, + drm_connector_hdmi_init_type_desc); + +/* + * Test that the registration of an HDMI connector with an !HDMI + * connector type fails. + */ +static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + unsigned int connector_type = *(unsigned int *)test->param_value; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + connector_type, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_LT(test, ret, 0); +} + +static const unsigned int drm_connector_hdmi_init_type_invalid_tests[] = { + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_VGA, + DRM_MODE_CONNECTOR_DVII, + DRM_MODE_CONNECTOR_DVID, + DRM_MODE_CONNECTOR_DVIA, + DRM_MODE_CONNECTOR_Composite, + DRM_MODE_CONNECTOR_SVIDEO, + DRM_MODE_CONNECTOR_LVDS, + DRM_MODE_CONNECTOR_Component, + DRM_MODE_CONNECTOR_9PinDIN, + DRM_MODE_CONNECTOR_DisplayPort, + DRM_MODE_CONNECTOR_TV, + DRM_MODE_CONNECTOR_eDP, + DRM_MODE_CONNECTOR_VIRTUAL, + DRM_MODE_CONNECTOR_DSI, + DRM_MODE_CONNECTOR_DPI, + DRM_MODE_CONNECTOR_WRITEBACK, + DRM_MODE_CONNECTOR_SPI, + DRM_MODE_CONNECTOR_USB, +}; + +KUNIT_ARRAY_PARAM(drm_connector_hdmi_init_type_invalid, + drm_connector_hdmi_init_type_invalid_tests, + drm_connector_hdmi_init_type_desc); + +static struct kunit_case drmm_connector_hdmi_init_tests[] = { + KUNIT_CASE(drm_test_connector_hdmi_init_valid), + KUNIT_CASE(drm_test_connector_hdmi_init_bpc_8), + KUNIT_CASE(drm_test_connector_hdmi_init_bpc_10), + KUNIT_CASE(drm_test_connector_hdmi_init_bpc_12), + KUNIT_CASE(drm_test_connector_hdmi_init_bpc_invalid), + KUNIT_CASE(drm_test_connector_hdmi_init_bpc_null), + KUNIT_CASE(drm_test_connector_hdmi_init_formats_empty), + KUNIT_CASE(drm_test_connector_hdmi_init_formats_no_rgb), + KUNIT_CASE_PARAM(drm_test_connector_hdmi_init_formats_yuv420_allowed, + drm_connector_hdmi_init_formats_yuv420_allowed_gen_params), + KUNIT_CASE(drm_test_connector_hdmi_init_null_ddc), + KUNIT_CASE(drm_test_connector_hdmi_init_null_product), + KUNIT_CASE(drm_test_connector_hdmi_init_null_vendor), + KUNIT_CASE(drm_test_connector_hdmi_init_product_length_exact), + KUNIT_CASE(drm_test_connector_hdmi_init_product_length_too_long), + KUNIT_CASE(drm_test_connector_hdmi_init_product_valid), + KUNIT_CASE(drm_test_connector_hdmi_init_vendor_length_exact), + KUNIT_CASE(drm_test_connector_hdmi_init_vendor_length_too_long), + KUNIT_CASE(drm_test_connector_hdmi_init_vendor_valid), + KUNIT_CASE_PARAM(drm_test_connector_hdmi_init_type_valid, + drm_connector_hdmi_init_type_valid_gen_params), + KUNIT_CASE_PARAM(drm_test_connector_hdmi_init_type_invalid, + drm_connector_hdmi_init_type_invalid_gen_params), + { } +}; + +static struct kunit_suite drmm_connector_hdmi_init_test_suite = { + .name = "drmm_connector_hdmi_init", + .init = drm_test_connector_init, + .test_cases = drmm_connector_hdmi_init_tests, +}; + +struct drm_get_tv_mode_from_name_test { + const char *name; + enum drm_connector_tv_mode expected_mode; +}; + +#define TV_MODE_NAME(_name, _mode) \ + { \ + .name = _name, \ + .expected_mode = _mode, \ + } + +static void drm_test_get_tv_mode_from_name_valid(struct kunit *test) +{ + const struct drm_get_tv_mode_from_name_test *params = test->param_value; + + KUNIT_EXPECT_EQ(test, + drm_get_tv_mode_from_name(params->name, strlen(params->name)), + params->expected_mode); +} + +static const +struct drm_get_tv_mode_from_name_test drm_get_tv_mode_from_name_valid_tests[] = { + TV_MODE_NAME("NTSC", DRM_MODE_TV_MODE_NTSC), + TV_MODE_NAME("NTSC-443", DRM_MODE_TV_MODE_NTSC_443), + TV_MODE_NAME("NTSC-J", DRM_MODE_TV_MODE_NTSC_J), + TV_MODE_NAME("PAL", DRM_MODE_TV_MODE_PAL), + TV_MODE_NAME("PAL-M", DRM_MODE_TV_MODE_PAL_M), + TV_MODE_NAME("PAL-N", DRM_MODE_TV_MODE_PAL_N), + TV_MODE_NAME("SECAM", DRM_MODE_TV_MODE_SECAM), + TV_MODE_NAME("Mono", DRM_MODE_TV_MODE_MONOCHROME), +}; + +static void +drm_get_tv_mode_from_name_valid_desc(const struct drm_get_tv_mode_from_name_test *t, + char *desc) +{ + sprintf(desc, "%s", t->name); +} + +KUNIT_ARRAY_PARAM(drm_get_tv_mode_from_name_valid, + drm_get_tv_mode_from_name_valid_tests, + drm_get_tv_mode_from_name_valid_desc); + +static void drm_test_get_tv_mode_from_name_truncated(struct kunit *test) +{ + const char *name = "NTS"; + int ret; + + ret = drm_get_tv_mode_from_name(name, strlen(name)); + KUNIT_EXPECT_LT(test, ret, 0); +}; + +static struct kunit_case drm_get_tv_mode_from_name_tests[] = { + KUNIT_CASE_PARAM(drm_test_get_tv_mode_from_name_valid, + drm_get_tv_mode_from_name_valid_gen_params), + KUNIT_CASE(drm_test_get_tv_mode_from_name_truncated), + { } +}; + +static struct kunit_suite drm_get_tv_mode_from_name_test_suite = { + .name = "drm_get_tv_mode_from_name", + .test_cases = drm_get_tv_mode_from_name_tests, +}; + +struct drm_hdmi_connector_get_broadcast_rgb_name_test { + unsigned int kind; + const char *expected_name; +}; + +#define BROADCAST_RGB_TEST(_kind, _name) \ + { \ + .kind = _kind, \ + .expected_name = _name, \ + } + +static void drm_test_drm_hdmi_connector_get_broadcast_rgb_name(struct kunit *test) +{ + const struct drm_hdmi_connector_get_broadcast_rgb_name_test *params = + test->param_value; + + KUNIT_EXPECT_STREQ(test, + drm_hdmi_connector_get_broadcast_rgb_name(params->kind), + params->expected_name); +} + +static const +struct drm_hdmi_connector_get_broadcast_rgb_name_test +drm_hdmi_connector_get_broadcast_rgb_name_valid_tests[] = { + BROADCAST_RGB_TEST(DRM_HDMI_BROADCAST_RGB_AUTO, "Automatic"), + BROADCAST_RGB_TEST(DRM_HDMI_BROADCAST_RGB_FULL, "Full"), + BROADCAST_RGB_TEST(DRM_HDMI_BROADCAST_RGB_LIMITED, "Limited 16:235"), +}; + +static void +drm_hdmi_connector_get_broadcast_rgb_name_valid_desc(const struct drm_hdmi_connector_get_broadcast_rgb_name_test *t, + char *desc) +{ + sprintf(desc, "%s", t->expected_name); +} + +KUNIT_ARRAY_PARAM(drm_hdmi_connector_get_broadcast_rgb_name_valid, + drm_hdmi_connector_get_broadcast_rgb_name_valid_tests, + drm_hdmi_connector_get_broadcast_rgb_name_valid_desc); + +static void drm_test_drm_hdmi_connector_get_broadcast_rgb_name_invalid(struct kunit *test) +{ + KUNIT_EXPECT_NULL(test, drm_hdmi_connector_get_broadcast_rgb_name(3)); +}; + +static struct kunit_case drm_hdmi_connector_get_broadcast_rgb_name_tests[] = { + KUNIT_CASE_PARAM(drm_test_drm_hdmi_connector_get_broadcast_rgb_name, + drm_hdmi_connector_get_broadcast_rgb_name_valid_gen_params), + KUNIT_CASE(drm_test_drm_hdmi_connector_get_broadcast_rgb_name_invalid), + { } +}; + +static struct kunit_suite drm_hdmi_connector_get_broadcast_rgb_name_test_suite = { + .name = "drm_hdmi_connector_get_broadcast_rgb_name", + .test_cases = drm_hdmi_connector_get_broadcast_rgb_name_tests, +}; + +struct drm_hdmi_connector_get_output_format_name_test { + unsigned int kind; + const char *expected_name; +}; + +#define OUTPUT_FORMAT_TEST(_kind, _name) \ + { \ + .kind = _kind, \ + .expected_name = _name, \ + } + +static void drm_test_drm_hdmi_connector_get_output_format_name(struct kunit *test) +{ + const struct drm_hdmi_connector_get_output_format_name_test *params = + test->param_value; + + KUNIT_EXPECT_STREQ(test, + drm_hdmi_connector_get_output_format_name(params->kind), + params->expected_name); +} + +static const +struct drm_hdmi_connector_get_output_format_name_test +drm_hdmi_connector_get_output_format_name_valid_tests[] = { + OUTPUT_FORMAT_TEST(DRM_OUTPUT_COLOR_FORMAT_RGB444, "RGB"), + OUTPUT_FORMAT_TEST(DRM_OUTPUT_COLOR_FORMAT_YCBCR420, "YUV 4:2:0"), + OUTPUT_FORMAT_TEST(DRM_OUTPUT_COLOR_FORMAT_YCBCR422, "YUV 4:2:2"), + OUTPUT_FORMAT_TEST(DRM_OUTPUT_COLOR_FORMAT_YCBCR444, "YUV 4:4:4"), +}; + +static void +drm_hdmi_connector_get_output_format_name_valid_desc(const struct drm_hdmi_connector_get_output_format_name_test *t, + char *desc) +{ + sprintf(desc, "%s", t->expected_name); +} + +KUNIT_ARRAY_PARAM(drm_hdmi_connector_get_output_format_name_valid, + drm_hdmi_connector_get_output_format_name_valid_tests, + drm_hdmi_connector_get_output_format_name_valid_desc); + +static void drm_test_drm_hdmi_connector_get_output_format_name_invalid(struct kunit *test) +{ + KUNIT_EXPECT_NULL(test, drm_hdmi_connector_get_output_format_name(4)); +}; + +static struct kunit_case drm_hdmi_connector_get_output_format_name_tests[] = { + KUNIT_CASE_PARAM(drm_test_drm_hdmi_connector_get_output_format_name, + drm_hdmi_connector_get_output_format_name_valid_gen_params), + KUNIT_CASE(drm_test_drm_hdmi_connector_get_output_format_name_invalid), + { } +}; + +static struct kunit_suite drm_hdmi_connector_get_output_format_name_test_suite = { + .name = "drm_hdmi_connector_get_output_format_name", + .test_cases = drm_hdmi_connector_get_output_format_name_tests, +}; + +static void drm_test_drm_connector_attach_broadcast_rgb_property(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + struct drm_property *prop; + int ret; + + ret = drmm_connector_init(&priv->drm, connector, + &dummy_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_connector_attach_broadcast_rgb_property(connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + prop = connector->broadcast_rgb_property; + KUNIT_ASSERT_NOT_NULL(test, prop); + KUNIT_EXPECT_NOT_NULL(test, drm_mode_obj_find_prop_id(&connector->base, prop->base.id)); +} + +static void drm_test_drm_connector_attach_broadcast_rgb_property_hdmi_connector(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + struct drm_property *prop; + int ret; + + ret = drmm_connector_hdmi_init(&priv->drm, connector, + "Vendor", "Product", + &dummy_funcs, + &dummy_hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + &priv->ddc, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_EXPECT_EQ(test, ret, 0); + + ret = drm_connector_attach_broadcast_rgb_property(connector); + KUNIT_ASSERT_EQ(test, ret, 0); + + prop = connector->broadcast_rgb_property; + KUNIT_ASSERT_NOT_NULL(test, prop); + KUNIT_EXPECT_NOT_NULL(test, drm_mode_obj_find_prop_id(&connector->base, prop->base.id)); +} + +static struct kunit_case drm_connector_attach_broadcast_rgb_property_tests[] = { + KUNIT_CASE(drm_test_drm_connector_attach_broadcast_rgb_property), + KUNIT_CASE(drm_test_drm_connector_attach_broadcast_rgb_property_hdmi_connector), + { } +}; + +static struct kunit_suite drm_connector_attach_broadcast_rgb_property_test_suite = { + .name = "drm_connector_attach_broadcast_rgb_property", + .init = drm_test_connector_init, + .test_cases = drm_connector_attach_broadcast_rgb_property_tests, +}; + +/* + * Test that for a given mode, with 8bpc and an RGB output the TMDS + * character rate is equal to the mode pixel clock. + */ +static void drm_test_drm_hdmi_compute_mode_clock_rgb(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + unsigned long long rate; + struct drm_device *drm = &priv->drm; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, 0); + KUNIT_EXPECT_EQ(test, mode->clock * 1000ULL, rate); +} + +/* + * Test that for a given mode, with 10bpc and an RGB output the TMDS + * character rate is equal to 1.25 times the mode pixel clock. + */ +static void drm_test_drm_hdmi_compute_mode_clock_rgb_10bpc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + unsigned long long rate; + struct drm_device *drm = &priv->drm; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, 0); + KUNIT_EXPECT_EQ(test, mode->clock * 1250, rate); +} + +/* + * Test that for the VIC-1 mode, with 10bpc and an RGB output the TMDS + * character rate computation fails. + */ +static void drm_test_drm_hdmi_compute_mode_clock_rgb_10bpc_vic_1(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + unsigned long long rate; + struct drm_device *drm = &priv->drm; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 1); + KUNIT_ASSERT_NOT_NULL(test, mode); + + rate = drm_hdmi_compute_mode_clock(mode, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_EXPECT_EQ(test, rate, 0); +} + +/* + * Test that for a given mode, with 12bpc and an RGB output the TMDS + * character rate is equal to 1.5 times the mode pixel clock. + */ +static void drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + unsigned long long rate; + struct drm_device *drm = &priv->drm; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, 0); + KUNIT_EXPECT_EQ(test, mode->clock * 1500, rate); +} + +/* + * Test that for the VIC-1 mode, with 12bpc and an RGB output the TMDS + * character rate computation fails. + */ +static void drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc_vic_1(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + unsigned long long rate; + struct drm_device *drm = &priv->drm; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 1); + KUNIT_ASSERT_NOT_NULL(test, mode); + + rate = drm_hdmi_compute_mode_clock(mode, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_EXPECT_EQ(test, rate, 0); +} + +/* + * Test that for a mode with the pixel repetition flag, the TMDS + * character rate is indeed double the mode pixel clock. + */ +static void drm_test_drm_hdmi_compute_mode_clock_rgb_double(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + unsigned long long rate; + struct drm_device *drm = &priv->drm; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 6); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_TRUE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, 0); + KUNIT_EXPECT_EQ(test, (mode->clock * 1000ULL) * 2, rate); +} + +/* + * Test that the TMDS character rate computation for the VIC modes + * explicitly listed in the spec as supporting YUV420 succeed and return + * half the mode pixel clock. + */ +static void drm_test_connector_hdmi_compute_mode_clock_yuv420_valid(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + struct drm_device *drm = &priv->drm; + unsigned long long rate; + unsigned int vic = *(unsigned int *)test->param_value; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, vic); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + KUNIT_ASSERT_GT(test, rate, 0); + KUNIT_EXPECT_EQ(test, (mode->clock * 1000ULL) / 2, rate); +} + +static const unsigned int drm_hdmi_compute_mode_clock_yuv420_vic_valid_tests[] = { + 96, 97, 101, 102, 106, 107, +}; + +static void drm_hdmi_compute_mode_clock_yuv420_vic_desc(const unsigned int *vic, char *desc) +{ + sprintf(desc, "VIC %u", *vic); +} + +KUNIT_ARRAY_PARAM(drm_hdmi_compute_mode_clock_yuv420_valid, + drm_hdmi_compute_mode_clock_yuv420_vic_valid_tests, + drm_hdmi_compute_mode_clock_yuv420_vic_desc); + +/* + * Test that for a given mode listed supporting it and an YUV420 output + * with 10bpc, the TMDS character rate is equal to 0.625 times the mode + * pixel clock. + */ +static void drm_test_connector_hdmi_compute_mode_clock_yuv420_10_bpc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + struct drm_device *drm = &priv->drm; + unsigned int vic = + drm_hdmi_compute_mode_clock_yuv420_vic_valid_tests[0]; + unsigned long long rate; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, vic); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 10, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + KUNIT_ASSERT_GT(test, rate, 0); + + KUNIT_EXPECT_EQ(test, mode->clock * 625, rate); +} + +/* + * Test that for a given mode listed supporting it and an YUV420 output + * with 12bpc, the TMDS character rate is equal to 0.75 times the mode + * pixel clock. + */ +static void drm_test_connector_hdmi_compute_mode_clock_yuv420_12_bpc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + struct drm_device *drm = &priv->drm; + unsigned int vic = + drm_hdmi_compute_mode_clock_yuv420_vic_valid_tests[0]; + unsigned long long rate; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, vic); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + KUNIT_ASSERT_GT(test, rate, 0); + + KUNIT_EXPECT_EQ(test, mode->clock * 750, rate); +} + +/* + * Test that for a given mode, the computation of the TMDS character + * rate with 8bpc and a YUV422 output succeeds and returns a rate equal + * to the mode pixel clock. + */ +static void drm_test_connector_hdmi_compute_mode_clock_yuv422_8_bpc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + struct drm_device *drm = &priv->drm; + unsigned long long rate; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + KUNIT_ASSERT_GT(test, rate, 0); + KUNIT_EXPECT_EQ(test, mode->clock * 1000, rate); +} + +/* + * Test that for a given mode, the computation of the TMDS character + * rate with 10bpc and a YUV422 output succeeds and returns a rate equal + * to the mode pixel clock. + */ +static void drm_test_connector_hdmi_compute_mode_clock_yuv422_10_bpc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + struct drm_device *drm = &priv->drm; + unsigned long long rate; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 10, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + KUNIT_ASSERT_GT(test, rate, 0); + KUNIT_EXPECT_EQ(test, mode->clock * 1000, rate); +} + +/* + * Test that for a given mode, the computation of the TMDS character + * rate with 12bpc and a YUV422 output succeeds and returns a rate equal + * to the mode pixel clock. + */ +static void drm_test_connector_hdmi_compute_mode_clock_yuv422_12_bpc(struct kunit *test) +{ + struct drm_connector_init_priv *priv = test->priv; + const struct drm_display_mode *mode; + struct drm_device *drm = &priv->drm; + unsigned long long rate; + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 16); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(mode, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + KUNIT_ASSERT_GT(test, rate, 0); + KUNIT_EXPECT_EQ(test, mode->clock * 1000, rate); +} + +static struct kunit_case drm_hdmi_compute_mode_clock_tests[] = { + KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb), + KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb_10bpc), + KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb_10bpc_vic_1), + KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc), + KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc_vic_1), + KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb_double), + KUNIT_CASE_PARAM(drm_test_connector_hdmi_compute_mode_clock_yuv420_valid, + drm_hdmi_compute_mode_clock_yuv420_valid_gen_params), + KUNIT_CASE(drm_test_connector_hdmi_compute_mode_clock_yuv420_10_bpc), + KUNIT_CASE(drm_test_connector_hdmi_compute_mode_clock_yuv420_12_bpc), + KUNIT_CASE(drm_test_connector_hdmi_compute_mode_clock_yuv422_8_bpc), + KUNIT_CASE(drm_test_connector_hdmi_compute_mode_clock_yuv422_10_bpc), + KUNIT_CASE(drm_test_connector_hdmi_compute_mode_clock_yuv422_12_bpc), + { } +}; + +static struct kunit_suite drm_hdmi_compute_mode_clock_test_suite = { + .name = "drm_test_connector_hdmi_compute_mode_clock", + .init = drm_test_connector_init, + .test_cases = drm_hdmi_compute_mode_clock_tests, +}; + +kunit_test_suites( + &drmm_connector_hdmi_init_test_suite, + &drmm_connector_init_test_suite, + &drm_connector_dynamic_init_test_suite, + &drm_connector_dynamic_register_early_test_suite, + &drm_connector_dynamic_register_test_suite, + &drm_connector_attach_broadcast_rgb_property_test_suite, + &drm_get_tv_mode_from_name_test_suite, + &drm_hdmi_compute_mode_clock_test_suite, + &drm_hdmi_connector_get_broadcast_rgb_name_test_suite, + &drm_hdmi_connector_get_output_format_name_test_suite +); + +MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>"); +MODULE_DESCRIPTION("Kunit test for drm_modes functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_damage_helper_test.c b/drivers/gpu/drm/tests/drm_damage_helper_test.c new file mode 100644 index 000000000..0df2e1a54 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_damage_helper_test.c @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test case for drm_damage_helper functions + * + * Copyright (c) 2022 Maíra Canal <mairacanal@riseup.net> + */ + +#include <kunit/test.h> + +#include <drm/drm_damage_helper.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_plane.h> +#include <drm/drm_drv.h> + +struct drm_damage_mock { + struct drm_driver driver; + struct drm_device device; + struct drm_object_properties obj_props; + struct drm_plane plane; + struct drm_property prop; + struct drm_framebuffer fb; + struct drm_plane_state state; + struct drm_plane_state old_state; +}; + +static int drm_damage_helper_init(struct kunit *test) +{ + struct drm_damage_mock *mock; + + mock = kunit_kzalloc(test, sizeof(*mock), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mock); + + mock->fb.width = 2048; + mock->fb.height = 2048; + + mock->state.crtc = ZERO_SIZE_PTR; + mock->state.fb = &mock->fb; + mock->state.visible = true; + + mock->old_state.plane = &mock->plane; + mock->state.plane = &mock->plane; + + /* just enough so that drm_plane_enable_fb_damage_clips() works */ + mock->device.driver = &mock->driver; + mock->device.mode_config.prop_fb_damage_clips = &mock->prop; + mock->plane.dev = &mock->device; + mock->obj_props.count = 0; + mock->plane.base.properties = &mock->obj_props; + mock->prop.base.id = 1; /* 0 is an invalid id */ + mock->prop.dev = &mock->device; + + drm_plane_enable_fb_damage_clips(&mock->plane); + + test->priv = mock; + + return 0; +} + +static void set_plane_src(struct drm_plane_state *state, int x1, int y1, int x2, + int y2) +{ + state->src_x = x1; + state->src_y = y1; + state->src_w = x2 - x1; + state->src_h = y2 - y1; + + state->src.x1 = x1; + state->src.y1 = y1; + state->src.x2 = x2; + state->src.y2 = y2; +} + +static void set_damage_clip(struct drm_mode_rect *r, int x1, int y1, int x2, + int y2) +{ + r->x1 = x1; + r->y1 = y1; + r->x2 = x2; + r->y2 = y2; +} + +static void set_damage_blob(struct drm_property_blob *damage_blob, + struct drm_mode_rect *r, u32 size) +{ + damage_blob->length = size; + damage_blob->data = r; +} + +static void set_plane_damage(struct drm_plane_state *state, + struct drm_property_blob *damage_blob) +{ + state->fb_damage_clips = damage_blob; +} + +static void check_damage_clip(struct kunit *test, struct drm_rect *r, + int x1, int y1, int x2, int y2) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_plane_state state = mock->state; + + /* + * Round down x1/y1 and round up x2/y2. This is because damage is not in + * 16.16 fixed point so to catch all pixels. + */ + int src_x1 = state.src.x1 >> 16; + int src_y1 = state.src.y1 >> 16; + int src_x2 = (state.src.x2 >> 16) + !!(state.src.x2 & 0xFFFF); + int src_y2 = (state.src.y2 >> 16) + !!(state.src.y2 & 0xFFFF); + + if (x1 >= x2 || y1 >= y2) + KUNIT_FAIL(test, "Cannot have damage clip with no dimension."); + if (x1 < src_x1 || y1 < src_y1 || x2 > src_x2 || y2 > src_y2) + KUNIT_FAIL(test, "Damage cannot be outside rounded plane src."); + if (r->x1 != x1 || r->y1 != y1 || r->x2 != x2 || r->y2 != y2) + KUNIT_FAIL(test, "Damage = %d %d %d %d, want = %d %d %d %d", + r->x1, r->y1, r->x2, r->y2, x1, y1, x2, y2); +} + +static void drm_test_damage_iter_no_damage(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src same as fb size. */ + set_plane_src(&mock->old_state, 0, 0, mock->fb.width << 16, mock->fb.height << 16); + set_plane_src(&mock->state, 0, 0, mock->fb.width << 16, mock->fb.height << 16); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, "Should return plane src as damage."); + check_damage_clip(test, &clip, 0, 0, 2048, 2048); +} + +static void drm_test_damage_iter_no_damage_fractional_src(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src has fractional part. */ + set_plane_src(&mock->old_state, 0x3fffe, 0x3fffe, + 0x3fffe + (1024 << 16), 0x3fffe + (768 << 16)); + set_plane_src(&mock->state, 0x3fffe, 0x3fffe, + 0x3fffe + (1024 << 16), 0x3fffe + (768 << 16)); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, + "Should return rounded off plane src as damage."); + check_damage_clip(test, &clip, 3, 3, 1028, 772); +} + +static void drm_test_damage_iter_no_damage_src_moved(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src moved since old plane state. */ + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 10 << 16, 10 << 16, + (10 + 1024) << 16, (10 + 768) << 16); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, "Should return plane src as damage."); + check_damage_clip(test, &clip, 10, 10, 1034, 778); +} + +static void drm_test_damage_iter_no_damage_fractional_src_moved(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src has fractional part and it moved since old plane state. */ + set_plane_src(&mock->old_state, 0x3fffe, 0x3fffe, + 0x3fffe + (1024 << 16), 0x3fffe + (768 << 16)); + set_plane_src(&mock->state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, "Should return plane src as damage."); + check_damage_clip(test, &clip, 4, 4, 1029, 773); +} + +static void drm_test_damage_iter_no_damage_not_visible(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_rect clip; + u32 num_hits = 0; + + mock->state.visible = false; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 0, "Should have no damage."); +} + +static void drm_test_damage_iter_no_damage_no_crtc(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_rect clip; + u32 num_hits = 0; + + mock->state.crtc = NULL; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 0, "Should have no damage."); +} + +static void drm_test_damage_iter_no_damage_no_fb(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_rect clip; + u32 num_hits = 0; + + mock->state.fb = NULL; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 0, "Should have no damage."); +} + +static void drm_test_damage_iter_simple_damage(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + /* Damage set to plane src */ + set_damage_clip(&damage, 0, 0, 1024, 768); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, "Should return damage when set."); + check_damage_clip(test, &clip, 0, 0, 1024, 768); +} + +static void drm_test_damage_iter_single_damage(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + set_damage_clip(&damage, 256, 192, 768, 576); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, "Should return damage when set."); + check_damage_clip(test, &clip, 256, 192, 768, 576); +} + +static void drm_test_damage_iter_single_damage_intersect_src(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + /* Damage intersect with plane src. */ + set_damage_clip(&damage, 256, 192, 1360, 768); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, "Should return damage clipped to src."); + check_damage_clip(test, &clip, 256, 192, 1024, 768); +} + +static void drm_test_damage_iter_single_damage_outside_src(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + /* Damage clip outside plane src */ + set_damage_clip(&damage, 1360, 1360, 1380, 1380); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 0, "Should have no damage."); +} + +static void drm_test_damage_iter_single_damage_fractional_src(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src has fractional part. */ + set_plane_src(&mock->old_state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + set_plane_src(&mock->state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + set_damage_clip(&damage, 10, 10, 256, 330); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, "Should return damage when set."); + check_damage_clip(test, &clip, 10, 10, 256, 330); +} + +static void drm_test_damage_iter_single_damage_intersect_fractional_src(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src has fractional part. */ + set_plane_src(&mock->old_state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + set_plane_src(&mock->state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + /* Damage intersect with plane src. */ + set_damage_clip(&damage, 10, 1, 1360, 330); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, + "Should return damage clipped to rounded off src."); + check_damage_clip(test, &clip, 10, 4, 1029, 330); +} + +static void drm_test_damage_iter_single_damage_outside_fractional_src(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src has fractional part. */ + set_plane_src(&mock->old_state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + set_plane_src(&mock->state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + /* Damage clip outside plane src */ + set_damage_clip(&damage, 1360, 1360, 1380, 1380); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 0, "Should have no damage."); +} + +static void drm_test_damage_iter_single_damage_src_moved(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src moved since old plane state. */ + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 10 << 16, 10 << 16, + (10 + 1024) << 16, (10 + 768) << 16); + set_damage_clip(&damage, 20, 30, 256, 256); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, + "Should return plane src as damage."); + check_damage_clip(test, &clip, 10, 10, 1034, 778); +} + +static void drm_test_damage_iter_single_damage_fractional_src_moved(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage; + struct drm_rect clip; + u32 num_hits = 0; + + /* Plane src with fractional part moved since old plane state. */ + set_plane_src(&mock->old_state, 0x3fffe, 0x3fffe, + 0x3fffe + (1024 << 16), 0x3fffe + (768 << 16)); + set_plane_src(&mock->state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + /* Damage intersect with plane src. */ + set_damage_clip(&damage, 20, 30, 1360, 256); + set_damage_blob(&damage_blob, &damage, sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, + "Should return rounded off plane as damage."); + check_damage_clip(test, &clip, 4, 4, 1029, 773); +} + +static void drm_test_damage_iter_damage(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage[2]; + struct drm_rect clip; + u32 num_hits = 0; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + /* 2 damage clips. */ + set_damage_clip(&damage[0], 20, 30, 200, 180); + set_damage_clip(&damage[1], 240, 200, 280, 250); + set_damage_blob(&damage_blob, &damage[0], sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) { + if (num_hits == 0) + check_damage_clip(test, &clip, 20, 30, 200, 180); + if (num_hits == 1) + check_damage_clip(test, &clip, 240, 200, 280, 250); + num_hits++; + } + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 2, "Should return damage when set."); +} + +static void drm_test_damage_iter_damage_one_intersect(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage[2]; + struct drm_rect clip; + u32 num_hits = 0; + + set_plane_src(&mock->old_state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + set_plane_src(&mock->state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + /* 2 damage clips, one intersect plane src. */ + set_damage_clip(&damage[0], 20, 30, 200, 180); + set_damage_clip(&damage[1], 2, 2, 1360, 1360); + set_damage_blob(&damage_blob, &damage[0], sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) { + if (num_hits == 0) + check_damage_clip(test, &clip, 20, 30, 200, 180); + if (num_hits == 1) + check_damage_clip(test, &clip, 4, 4, 1029, 773); + num_hits++; + } + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 2, "Should return damage when set."); +} + +static void drm_test_damage_iter_damage_one_outside(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage[2]; + struct drm_rect clip; + u32 num_hits = 0; + + set_plane_src(&mock->old_state, 0, 0, 1024 << 16, 768 << 16); + set_plane_src(&mock->state, 0, 0, 1024 << 16, 768 << 16); + /* 2 damage clips, one outside plane src. */ + set_damage_clip(&damage[0], 1360, 1360, 1380, 1380); + set_damage_clip(&damage[1], 240, 200, 280, 250); + set_damage_blob(&damage_blob, &damage[0], sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, "Should return damage when set."); + check_damage_clip(test, &clip, 240, 200, 280, 250); +} + +static void drm_test_damage_iter_damage_src_moved(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage[2]; + struct drm_rect clip; + u32 num_hits = 0; + + set_plane_src(&mock->old_state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + set_plane_src(&mock->state, 0x3fffe, 0x3fffe, + 0x3fffe + (1024 << 16), 0x3fffe + (768 << 16)); + /* 2 damage clips, one outside plane src. */ + set_damage_clip(&damage[0], 1360, 1360, 1380, 1380); + set_damage_clip(&damage[1], 240, 200, 280, 250); + set_damage_blob(&damage_blob, &damage[0], sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 1, + "Should return round off plane src as damage."); + check_damage_clip(test, &clip, 3, 3, 1028, 772); +} + +static void drm_test_damage_iter_damage_not_visible(struct kunit *test) +{ + struct drm_damage_mock *mock = test->priv; + struct drm_atomic_helper_damage_iter iter; + struct drm_property_blob damage_blob; + struct drm_mode_rect damage[2]; + struct drm_rect clip; + u32 num_hits = 0; + + mock->state.visible = false; + + set_plane_src(&mock->old_state, 0x40002, 0x40002, + 0x40002 + (1024 << 16), 0x40002 + (768 << 16)); + set_plane_src(&mock->state, 0x3fffe, 0x3fffe, + 0x3fffe + (1024 << 16), 0x3fffe + (768 << 16)); + /* 2 damage clips, one outside plane src. */ + set_damage_clip(&damage[0], 1360, 1360, 1380, 1380); + set_damage_clip(&damage[1], 240, 200, 280, 250); + set_damage_blob(&damage_blob, &damage[0], sizeof(damage)); + set_plane_damage(&mock->state, &damage_blob); + drm_atomic_helper_damage_iter_init(&iter, &mock->old_state, &mock->state); + drm_atomic_for_each_plane_damage(&iter, &clip) + num_hits++; + + KUNIT_EXPECT_EQ_MSG(test, num_hits, 0, "Should not return any damage."); +} + +static struct kunit_case drm_damage_helper_tests[] = { + KUNIT_CASE(drm_test_damage_iter_no_damage), + KUNIT_CASE(drm_test_damage_iter_no_damage_fractional_src), + KUNIT_CASE(drm_test_damage_iter_no_damage_src_moved), + KUNIT_CASE(drm_test_damage_iter_no_damage_fractional_src_moved), + KUNIT_CASE(drm_test_damage_iter_no_damage_not_visible), + KUNIT_CASE(drm_test_damage_iter_no_damage_no_crtc), + KUNIT_CASE(drm_test_damage_iter_no_damage_no_fb), + KUNIT_CASE(drm_test_damage_iter_simple_damage), + KUNIT_CASE(drm_test_damage_iter_single_damage), + KUNIT_CASE(drm_test_damage_iter_single_damage_intersect_src), + KUNIT_CASE(drm_test_damage_iter_single_damage_outside_src), + KUNIT_CASE(drm_test_damage_iter_single_damage_fractional_src), + KUNIT_CASE(drm_test_damage_iter_single_damage_intersect_fractional_src), + KUNIT_CASE(drm_test_damage_iter_single_damage_outside_fractional_src), + KUNIT_CASE(drm_test_damage_iter_single_damage_src_moved), + KUNIT_CASE(drm_test_damage_iter_single_damage_fractional_src_moved), + KUNIT_CASE(drm_test_damage_iter_damage), + KUNIT_CASE(drm_test_damage_iter_damage_one_intersect), + KUNIT_CASE(drm_test_damage_iter_damage_one_outside), + KUNIT_CASE(drm_test_damage_iter_damage_src_moved), + KUNIT_CASE(drm_test_damage_iter_damage_not_visible), + { } +}; + +static struct kunit_suite drm_damage_helper_test_suite = { + .name = "drm_damage_helper", + .init = drm_damage_helper_init, + .test_cases = drm_damage_helper_tests, +}; + +kunit_test_suite(drm_damage_helper_test_suite); + +MODULE_DESCRIPTION("Test case for drm_damage_helper functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_dp_mst_helper_test.c b/drivers/gpu/drm/tests/drm_dp_mst_helper_test.c new file mode 100644 index 000000000..9e0e2fb65 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_dp_mst_helper_test.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test cases for the DRM DP MST helpers + * + * Copyright (c) 2022 Maíra Canal <mairacanal@riseup.net> + */ + +#include <kunit/test.h> + +#include <drm/display/drm_dp_mst_helper.h> +#include <drm/drm_print.h> + +#include "../display/drm_dp_mst_topology_internal.h" + +struct drm_dp_mst_calc_pbn_mode_test { + const int clock; + const int bpp; + const bool dsc; + const int expected; +}; + +static const struct drm_dp_mst_calc_pbn_mode_test drm_dp_mst_calc_pbn_mode_cases[] = { + { + .clock = 154000, + .bpp = 30, + .dsc = false, + .expected = 689 + }, + { + .clock = 234000, + .bpp = 30, + .dsc = false, + .expected = 1047 + }, + { + .clock = 297000, + .bpp = 24, + .dsc = false, + .expected = 1063 + }, + { + .clock = 332880, + .bpp = 24, + .dsc = true, + .expected = 1191 + }, + { + .clock = 324540, + .bpp = 24, + .dsc = true, + .expected = 1161 + }, +}; + +static void drm_test_dp_mst_calc_pbn_mode(struct kunit *test) +{ + const struct drm_dp_mst_calc_pbn_mode_test *params = test->param_value; + + KUNIT_EXPECT_EQ(test, drm_dp_calc_pbn_mode(params->clock, params->bpp << 4), + params->expected); +} + +static void dp_mst_calc_pbn_mode_desc(const struct drm_dp_mst_calc_pbn_mode_test *t, char *desc) +{ + sprintf(desc, "Clock %d BPP %d DSC %s", t->clock, t->bpp, t->dsc ? "enabled" : "disabled"); +} + +KUNIT_ARRAY_PARAM(drm_dp_mst_calc_pbn_mode, drm_dp_mst_calc_pbn_mode_cases, + dp_mst_calc_pbn_mode_desc); + +struct drm_dp_mst_calc_pbn_div_test { + int link_rate; + int lane_count; + fixed20_12 expected; +}; + +#define fp_init(__int, __frac) { \ + .full = (__int) * (1 << 12) + \ + (__frac) * (1 << 12) / 100000 \ +} + +static const struct drm_dp_mst_calc_pbn_div_test drm_dp_mst_calc_pbn_div_dp1_4_cases[] = { + /* + * UHBR rates (DP Standard v2.1 2.7.6.3, specifying the rounded to + * closest value to 2 decimal places): + * .expected = .link_rate * .lane_count * 0.9671 / 8 / 54 / 100 + * DP1.4 rates (DP Standard v2.1 2.6.4.2): + * .expected = .link_rate * .lane_count * 0.8000 / 8 / 54 / 100 + * + * truncated to 5 decimal places. + */ + { + .link_rate = 2000000, + .lane_count = 4, + .expected = fp_init(179, 9259), /* 179.09259 */ + }, + { + .link_rate = 2000000, + .lane_count = 2, + .expected = fp_init(89, 54629), + }, + { + .link_rate = 2000000, + .lane_count = 1, + .expected = fp_init(44, 77314), + }, + { + .link_rate = 1350000, + .lane_count = 4, + .expected = fp_init(120, 88750), + }, + { + .link_rate = 1350000, + .lane_count = 2, + .expected = fp_init(60, 44375), + }, + { + .link_rate = 1350000, + .lane_count = 1, + .expected = fp_init(30, 22187), + }, + { + .link_rate = 1000000, + .lane_count = 4, + .expected = fp_init(89, 54629), + }, + { + .link_rate = 1000000, + .lane_count = 2, + .expected = fp_init(44, 77314), + }, + { + .link_rate = 1000000, + .lane_count = 1, + .expected = fp_init(22, 38657), + }, + { + .link_rate = 810000, + .lane_count = 4, + .expected = fp_init(60, 0), + }, + { + .link_rate = 810000, + .lane_count = 2, + .expected = fp_init(30, 0), + }, + { + .link_rate = 810000, + .lane_count = 1, + .expected = fp_init(15, 0), + }, + { + .link_rate = 540000, + .lane_count = 4, + .expected = fp_init(40, 0), + }, + { + .link_rate = 540000, + .lane_count = 2, + .expected = fp_init(20, 0), + }, + { + .link_rate = 540000, + .lane_count = 1, + .expected = fp_init(10, 0), + }, + { + .link_rate = 270000, + .lane_count = 4, + .expected = fp_init(20, 0), + }, + { + .link_rate = 270000, + .lane_count = 2, + .expected = fp_init(10, 0), + }, + { + .link_rate = 270000, + .lane_count = 1, + .expected = fp_init(5, 0), + }, + { + .link_rate = 162000, + .lane_count = 4, + .expected = fp_init(12, 0), + }, + { + .link_rate = 162000, + .lane_count = 2, + .expected = fp_init(6, 0), + }, + { + .link_rate = 162000, + .lane_count = 1, + .expected = fp_init(3, 0), + }, +}; + +static void drm_test_dp_mst_calc_pbn_div(struct kunit *test) +{ + const struct drm_dp_mst_calc_pbn_div_test *params = test->param_value; + + KUNIT_EXPECT_EQ(test, drm_dp_get_vc_payload_bw(params->link_rate, params->lane_count).full, + params->expected.full); +} + +static void dp_mst_calc_pbn_div_desc(const struct drm_dp_mst_calc_pbn_div_test *t, char *desc) +{ + sprintf(desc, "Link rate %d lane count %d", t->link_rate, t->lane_count); +} + +KUNIT_ARRAY_PARAM(drm_dp_mst_calc_pbn_div, drm_dp_mst_calc_pbn_div_dp1_4_cases, + dp_mst_calc_pbn_div_desc); + +static u8 data[] = { 0xff, 0x00, 0xdd }; + +struct drm_dp_mst_sideband_msg_req_test { + const char *desc; + const struct drm_dp_sideband_msg_req_body in; +}; + +static const struct drm_dp_mst_sideband_msg_req_test drm_dp_mst_sideband_msg_req_cases[] = { + { + .desc = "DP_ENUM_PATH_RESOURCES with port number", + .in = { + .req_type = DP_ENUM_PATH_RESOURCES, + .u.port_num.port_number = 5, + }, + }, + { + .desc = "DP_POWER_UP_PHY with port number", + .in = { + .req_type = DP_POWER_UP_PHY, + .u.port_num.port_number = 5, + }, + }, + { + .desc = "DP_POWER_DOWN_PHY with port number", + .in = { + .req_type = DP_POWER_DOWN_PHY, + .u.port_num.port_number = 5, + }, + }, + { + .desc = "DP_ALLOCATE_PAYLOAD with SDP stream sinks", + .in = { + .req_type = DP_ALLOCATE_PAYLOAD, + .u.allocate_payload.number_sdp_streams = 3, + .u.allocate_payload.sdp_stream_sink = { 1, 2, 3 }, + }, + }, + { + .desc = "DP_ALLOCATE_PAYLOAD with port number", + .in = { + .req_type = DP_ALLOCATE_PAYLOAD, + .u.allocate_payload.port_number = 0xf, + }, + }, + { + .desc = "DP_ALLOCATE_PAYLOAD with VCPI", + .in = { + .req_type = DP_ALLOCATE_PAYLOAD, + .u.allocate_payload.vcpi = 0x7f, + }, + }, + { + .desc = "DP_ALLOCATE_PAYLOAD with PBN", + .in = { + .req_type = DP_ALLOCATE_PAYLOAD, + .u.allocate_payload.pbn = U16_MAX, + }, + }, + { + .desc = "DP_QUERY_PAYLOAD with port number", + .in = { + .req_type = DP_QUERY_PAYLOAD, + .u.query_payload.port_number = 0xf, + }, + }, + { + .desc = "DP_QUERY_PAYLOAD with VCPI", + .in = { + .req_type = DP_QUERY_PAYLOAD, + .u.query_payload.vcpi = 0x7f, + }, + }, + { + .desc = "DP_REMOTE_DPCD_READ with port number", + .in = { + .req_type = DP_REMOTE_DPCD_READ, + .u.dpcd_read.port_number = 0xf, + }, + }, + { + .desc = "DP_REMOTE_DPCD_READ with DPCD address", + .in = { + .req_type = DP_REMOTE_DPCD_READ, + .u.dpcd_read.dpcd_address = 0xfedcb, + }, + }, + { + .desc = "DP_REMOTE_DPCD_READ with max number of bytes", + .in = { + .req_type = DP_REMOTE_DPCD_READ, + .u.dpcd_read.num_bytes = U8_MAX, + }, + }, + { + .desc = "DP_REMOTE_DPCD_WRITE with port number", + .in = { + .req_type = DP_REMOTE_DPCD_WRITE, + .u.dpcd_write.port_number = 0xf, + }, + }, + { + .desc = "DP_REMOTE_DPCD_WRITE with DPCD address", + .in = { + .req_type = DP_REMOTE_DPCD_WRITE, + .u.dpcd_write.dpcd_address = 0xfedcb, + }, + }, + { + .desc = "DP_REMOTE_DPCD_WRITE with data array", + .in = { + .req_type = DP_REMOTE_DPCD_WRITE, + .u.dpcd_write.num_bytes = ARRAY_SIZE(data), + .u.dpcd_write.bytes = data, + }, + }, + { + .desc = "DP_REMOTE_I2C_READ with port number", + .in = { + .req_type = DP_REMOTE_I2C_READ, + .u.i2c_read.port_number = 0xf, + }, + }, + { + .desc = "DP_REMOTE_I2C_READ with I2C device ID", + .in = { + .req_type = DP_REMOTE_I2C_READ, + .u.i2c_read.read_i2c_device_id = 0x7f, + }, + }, + { + .desc = "DP_REMOTE_I2C_READ with transactions array", + .in = { + .req_type = DP_REMOTE_I2C_READ, + .u.i2c_read.num_transactions = 3, + .u.i2c_read.num_bytes_read = ARRAY_SIZE(data) * 3, + .u.i2c_read.transactions = { + { .bytes = data, .num_bytes = ARRAY_SIZE(data), .i2c_dev_id = 0x7f, + .i2c_transaction_delay = 0xf, }, + { .bytes = data, .num_bytes = ARRAY_SIZE(data), .i2c_dev_id = 0x7e, + .i2c_transaction_delay = 0xe, }, + { .bytes = data, .num_bytes = ARRAY_SIZE(data), .i2c_dev_id = 0x7d, + .i2c_transaction_delay = 0xd, }, + }, + }, + }, + { + .desc = "DP_REMOTE_I2C_WRITE with port number", + .in = { + .req_type = DP_REMOTE_I2C_WRITE, + .u.i2c_write.port_number = 0xf, + }, + }, + { + .desc = "DP_REMOTE_I2C_WRITE with I2C device ID", + .in = { + .req_type = DP_REMOTE_I2C_WRITE, + .u.i2c_write.write_i2c_device_id = 0x7f, + }, + }, + { + .desc = "DP_REMOTE_I2C_WRITE with data array", + .in = { + .req_type = DP_REMOTE_I2C_WRITE, + .u.i2c_write.num_bytes = ARRAY_SIZE(data), + .u.i2c_write.bytes = data, + }, + }, + { + .desc = "DP_QUERY_STREAM_ENC_STATUS with stream ID", + .in = { + .req_type = DP_QUERY_STREAM_ENC_STATUS, + .u.enc_status.stream_id = 1, + }, + }, + { + .desc = "DP_QUERY_STREAM_ENC_STATUS with client ID", + .in = { + .req_type = DP_QUERY_STREAM_ENC_STATUS, + .u.enc_status.client_id = { 0x4f, 0x7f, 0xb4, 0x00, 0x8c, 0x0d, 0x67 }, + }, + }, + { + .desc = "DP_QUERY_STREAM_ENC_STATUS with stream event", + .in = { + .req_type = DP_QUERY_STREAM_ENC_STATUS, + .u.enc_status.stream_event = 3, + }, + }, + { + .desc = "DP_QUERY_STREAM_ENC_STATUS with valid stream event", + .in = { + .req_type = DP_QUERY_STREAM_ENC_STATUS, + .u.enc_status.valid_stream_event = 0, + }, + }, + { + .desc = "DP_QUERY_STREAM_ENC_STATUS with stream behavior", + .in = { + .req_type = DP_QUERY_STREAM_ENC_STATUS, + .u.enc_status.stream_behavior = 3, + }, + }, + { + .desc = "DP_QUERY_STREAM_ENC_STATUS with a valid stream behavior", + .in = { + .req_type = DP_QUERY_STREAM_ENC_STATUS, + .u.enc_status.valid_stream_behavior = 1, + } + }, +}; + +static bool +sideband_msg_req_equal(const struct drm_dp_sideband_msg_req_body *in, + const struct drm_dp_sideband_msg_req_body *out) +{ + const struct drm_dp_remote_i2c_read_tx *txin, *txout; + int i; + + if (in->req_type != out->req_type) + return false; + + switch (in->req_type) { + /* + * Compare struct members manually for request types which can't be + * compared simply using memcmp(). This is because said request types + * contain pointers to other allocated structs + */ + case DP_REMOTE_I2C_READ: +#define IN in->u.i2c_read +#define OUT out->u.i2c_read + if (IN.num_bytes_read != OUT.num_bytes_read || + IN.num_transactions != OUT.num_transactions || + IN.port_number != OUT.port_number || + IN.read_i2c_device_id != OUT.read_i2c_device_id) + return false; + + for (i = 0; i < IN.num_transactions; i++) { + txin = &IN.transactions[i]; + txout = &OUT.transactions[i]; + + if (txin->i2c_dev_id != txout->i2c_dev_id || + txin->no_stop_bit != txout->no_stop_bit || + txin->num_bytes != txout->num_bytes || + txin->i2c_transaction_delay != + txout->i2c_transaction_delay) + return false; + + if (memcmp(txin->bytes, txout->bytes, + txin->num_bytes) != 0) + return false; + } + break; +#undef IN +#undef OUT + + case DP_REMOTE_DPCD_WRITE: +#define IN in->u.dpcd_write +#define OUT out->u.dpcd_write + if (IN.dpcd_address != OUT.dpcd_address || + IN.num_bytes != OUT.num_bytes || + IN.port_number != OUT.port_number) + return false; + + return memcmp(IN.bytes, OUT.bytes, IN.num_bytes) == 0; +#undef IN +#undef OUT + + case DP_REMOTE_I2C_WRITE: +#define IN in->u.i2c_write +#define OUT out->u.i2c_write + if (IN.port_number != OUT.port_number || + IN.write_i2c_device_id != OUT.write_i2c_device_id || + IN.num_bytes != OUT.num_bytes) + return false; + + return memcmp(IN.bytes, OUT.bytes, IN.num_bytes) == 0; +#undef IN +#undef OUT + + default: + return memcmp(in, out, sizeof(*in)) == 0; + } + + return true; +} + +static void drm_test_dp_mst_msg_printf(struct drm_printer *p, struct va_format *vaf) +{ + struct kunit *test = p->arg; + + kunit_err(test, "%pV", vaf); +} + +static void drm_test_dp_mst_sideband_msg_req_decode(struct kunit *test) +{ + const struct drm_dp_mst_sideband_msg_req_test *params = test->param_value; + const struct drm_dp_sideband_msg_req_body *in = ¶ms->in; + struct drm_dp_sideband_msg_req_body *out; + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_printer p = { + .printfn = drm_test_dp_mst_msg_printf, + .arg = test + }; + int i; + + out = kunit_kzalloc(test, sizeof(*out), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, out); + + txmsg = kunit_kzalloc(test, sizeof(*txmsg), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, txmsg); + + drm_dp_encode_sideband_req(in, txmsg); + KUNIT_EXPECT_GE_MSG(test, drm_dp_decode_sideband_req(txmsg, out), 0, + "Failed to decode sideband request"); + + if (!sideband_msg_req_equal(in, out)) { + KUNIT_FAIL(test, "Encode/decode failed"); + kunit_err(test, "Expected:"); + drm_dp_dump_sideband_msg_req_body(in, 1, &p); + kunit_err(test, "Got:"); + drm_dp_dump_sideband_msg_req_body(out, 1, &p); + } + + switch (in->req_type) { + case DP_REMOTE_DPCD_WRITE: + kfree(out->u.dpcd_write.bytes); + break; + case DP_REMOTE_I2C_READ: + for (i = 0; i < out->u.i2c_read.num_transactions; i++) + kfree(out->u.i2c_read.transactions[i].bytes); + break; + case DP_REMOTE_I2C_WRITE: + kfree(out->u.i2c_write.bytes); + break; + } +} + +static void +drm_dp_mst_sideband_msg_req_desc(const struct drm_dp_mst_sideband_msg_req_test *t, char *desc) +{ + strcpy(desc, t->desc); +} + +KUNIT_ARRAY_PARAM(drm_dp_mst_sideband_msg_req, drm_dp_mst_sideband_msg_req_cases, + drm_dp_mst_sideband_msg_req_desc); + +static struct kunit_case drm_dp_mst_helper_tests[] = { + KUNIT_CASE_PARAM(drm_test_dp_mst_calc_pbn_mode, drm_dp_mst_calc_pbn_mode_gen_params), + KUNIT_CASE_PARAM(drm_test_dp_mst_calc_pbn_div, drm_dp_mst_calc_pbn_div_gen_params), + KUNIT_CASE_PARAM(drm_test_dp_mst_sideband_msg_req_decode, + drm_dp_mst_sideband_msg_req_gen_params), + { } +}; + +static struct kunit_suite drm_dp_mst_helper_test_suite = { + .name = "drm_dp_mst_helper", + .test_cases = drm_dp_mst_helper_tests, +}; + +kunit_test_suite(drm_dp_mst_helper_test_suite); + +MODULE_DESCRIPTION("Test cases for the DRM DP MST helpers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_exec_test.c b/drivers/gpu/drm/tests/drm_exec_test.c new file mode 100644 index 000000000..7a374e462 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_exec_test.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2022 Advanced Micro Devices, Inc. + */ + +#define pr_fmt(fmt) "drm_exec: " fmt + +#include <kunit/test.h> + +#include <linux/module.h> +#include <linux/prime_numbers.h> + +#include <drm/drm_exec.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_gem.h> +#include <drm/drm_kunit_helpers.h> + +struct drm_exec_priv { + struct device *dev; + struct drm_device *drm; +}; + +static int drm_exec_test_init(struct kunit *test) +{ + struct drm_exec_priv *priv; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + test->priv = priv; + + priv->dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->dev); + + priv->drm = __drm_kunit_helper_alloc_drm_device(test, priv->dev, sizeof(*priv->drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->drm); + + return 0; +} + +static void sanitycheck(struct kunit *test) +{ + struct drm_exec exec; + + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0); + drm_exec_fini(&exec); + KUNIT_SUCCEED(test); +} + +static void test_lock(struct kunit *test) +{ + struct drm_exec_priv *priv = test->priv; + struct drm_gem_object gobj = { }; + struct drm_exec exec; + int ret; + + drm_gem_private_object_init(priv->drm, &gobj, PAGE_SIZE); + + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0); + drm_exec_until_all_locked(&exec) { + ret = drm_exec_lock_obj(&exec, &gobj); + drm_exec_retry_on_contention(&exec); + KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + break; + } + drm_exec_fini(&exec); +} + +static void test_lock_unlock(struct kunit *test) +{ + struct drm_exec_priv *priv = test->priv; + struct drm_gem_object gobj = { }; + struct drm_exec exec; + int ret; + + drm_gem_private_object_init(priv->drm, &gobj, PAGE_SIZE); + + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0); + drm_exec_until_all_locked(&exec) { + ret = drm_exec_lock_obj(&exec, &gobj); + drm_exec_retry_on_contention(&exec); + KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + break; + + drm_exec_unlock_obj(&exec, &gobj); + ret = drm_exec_lock_obj(&exec, &gobj); + drm_exec_retry_on_contention(&exec); + KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + break; + } + drm_exec_fini(&exec); +} + +static void test_duplicates(struct kunit *test) +{ + struct drm_exec_priv *priv = test->priv; + struct drm_gem_object gobj = { }; + struct drm_exec exec; + int ret; + + drm_gem_private_object_init(priv->drm, &gobj, PAGE_SIZE); + + drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES, 0); + drm_exec_until_all_locked(&exec) { + ret = drm_exec_lock_obj(&exec, &gobj); + drm_exec_retry_on_contention(&exec); + KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + break; + + ret = drm_exec_lock_obj(&exec, &gobj); + drm_exec_retry_on_contention(&exec); + KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + break; + } + drm_exec_unlock_obj(&exec, &gobj); + drm_exec_fini(&exec); +} + +static void test_prepare(struct kunit *test) +{ + struct drm_exec_priv *priv = test->priv; + struct drm_gem_object gobj = { }; + struct drm_exec exec; + int ret; + + drm_gem_private_object_init(priv->drm, &gobj, PAGE_SIZE); + + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0); + drm_exec_until_all_locked(&exec) { + ret = drm_exec_prepare_obj(&exec, &gobj, 1); + drm_exec_retry_on_contention(&exec); + KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + break; + } + drm_exec_fini(&exec); + + drm_gem_private_object_fini(&gobj); +} + +static void test_prepare_array(struct kunit *test) +{ + struct drm_exec_priv *priv = test->priv; + struct drm_gem_object *gobj1; + struct drm_gem_object *gobj2; + struct drm_gem_object *array[] = { + (gobj1 = kunit_kzalloc(test, sizeof(*gobj1), GFP_KERNEL)), + (gobj2 = kunit_kzalloc(test, sizeof(*gobj2), GFP_KERNEL)), + }; + struct drm_exec exec; + int ret; + + if (!gobj1 || !gobj2) { + KUNIT_FAIL(test, "Failed to allocate GEM objects.\n"); + return; + } + + drm_gem_private_object_init(priv->drm, gobj1, PAGE_SIZE); + drm_gem_private_object_init(priv->drm, gobj2, PAGE_SIZE); + + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0); + drm_exec_until_all_locked(&exec) + ret = drm_exec_prepare_array(&exec, array, ARRAY_SIZE(array), + 1); + KUNIT_EXPECT_EQ(test, ret, 0); + drm_exec_fini(&exec); + + drm_gem_private_object_fini(gobj1); + drm_gem_private_object_fini(gobj2); +} + +static void test_multiple_loops(struct kunit *test) +{ + struct drm_exec exec; + + { + __label__ drm_exec_retry; + + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0); + drm_exec_until_all_locked(&exec) + { + break; + } + drm_exec_fini(&exec); + } + + { + __label__ drm_exec_retry; + + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0); + drm_exec_until_all_locked(&exec) + { + break; + } + drm_exec_fini(&exec); + } + KUNIT_SUCCEED(test); +} + +static struct kunit_case drm_exec_tests[] = { + KUNIT_CASE(sanitycheck), + KUNIT_CASE(test_lock), + KUNIT_CASE(test_lock_unlock), + KUNIT_CASE(test_duplicates), + KUNIT_CASE(test_prepare), + KUNIT_CASE(test_prepare_array), + KUNIT_CASE(test_multiple_loops), + {} +}; + +static struct kunit_suite drm_exec_test_suite = { + .name = "drm_exec", + .init = drm_exec_test_init, + .test_cases = drm_exec_tests, +}; + +kunit_test_suite(drm_exec_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("Kunit test for drm_exec functions"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/gpu/drm/tests/drm_fixp_test.c b/drivers/gpu/drm/tests/drm_fixp_test.c new file mode 100644 index 000000000..dd77fdedb --- /dev/null +++ b/drivers/gpu/drm/tests/drm_fixp_test.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2022 Advanced Micro Devices, Inc. + */ + +#include <kunit/test.h> +#include <drm/drm_fixed.h> + +static void drm_test_sm2fixp(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, 0x7fffffffffffffffll, ((1ull << 63) - 1)); + + /* 1 */ + KUNIT_EXPECT_EQ(test, drm_int2fixp(1), drm_sm2fixp(1ull << DRM_FIXED_POINT)); + + /* -1 */ + KUNIT_EXPECT_EQ(test, drm_int2fixp(-1), + drm_sm2fixp((1ull << 63) | (1ull << DRM_FIXED_POINT))); + + /* 0.5 */ + KUNIT_EXPECT_EQ(test, drm_fixp_from_fraction(1, 2), + drm_sm2fixp(1ull << (DRM_FIXED_POINT - 1))); + + /* -0.5 */ + KUNIT_EXPECT_EQ(test, drm_fixp_from_fraction(-1, 2), + drm_sm2fixp((1ull << 63) | (1ull << (DRM_FIXED_POINT - 1)))); +} + +static void drm_test_int2fixp(struct kunit *test) +{ + /* 1 */ + KUNIT_EXPECT_EQ(test, 1ll << 32, drm_int2fixp(1)); + + /* -1 */ + KUNIT_EXPECT_EQ(test, -(1ll << 32), drm_int2fixp(-1)); + + /* 1 + (-1) = 0 */ + KUNIT_EXPECT_EQ(test, 0, drm_int2fixp(1) + drm_int2fixp(-1)); + + /* 1 / 2 */ + KUNIT_EXPECT_EQ(test, 1ll << 31, drm_fixp_from_fraction(1, 2)); + + /* -0.5 */ + KUNIT_EXPECT_EQ(test, -(1ll << 31), drm_fixp_from_fraction(-1, 2)); + + /* (1 / 2) + (-1) = 0.5 */ + KUNIT_EXPECT_EQ(test, 1ll << 31, drm_fixp_from_fraction(-1, 2) + drm_int2fixp(1)); + + /* (1 / 2) - 1) = 0.5 */ + KUNIT_EXPECT_EQ(test, -(1ll << 31), drm_fixp_from_fraction(1, 2) + drm_int2fixp(-1)); + + /* (1 / 2) - 1) = 0.5 */ + KUNIT_EXPECT_EQ(test, -(1ll << 31), drm_fixp_from_fraction(1, 2) - drm_int2fixp(1)); +} + +static struct kunit_case drm_fixp_tests[] = { + KUNIT_CASE(drm_test_int2fixp), + KUNIT_CASE(drm_test_sm2fixp), + { } +}; + +static struct kunit_suite drm_fixp_test_suite = { + .name = "drm_fixp", + .test_cases = drm_fixp_tests, +}; + +kunit_test_suite(drm_fixp_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Unit tests for drm_fixed.h"); diff --git a/drivers/gpu/drm/tests/drm_format_helper_test.c b/drivers/gpu/drm/tests/drm_format_helper_test.c new file mode 100644 index 000000000..981dada8f --- /dev/null +++ b/drivers/gpu/drm/tests/drm_format_helper_test.c @@ -0,0 +1,1740 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <kunit/test.h> + +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_file.h> +#include <drm/drm_format_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_mode.h> +#include <drm/drm_print.h> +#include <drm/drm_rect.h> + +#include "../drm_crtc_internal.h" + +#define TEST_BUF_SIZE 50 + +#define TEST_USE_DEFAULT_PITCH 0 + +static unsigned char fmtcnv_state_mem[PAGE_SIZE]; +static struct drm_format_conv_state fmtcnv_state = + DRM_FORMAT_CONV_STATE_INIT_PREALLOCATED(fmtcnv_state_mem, sizeof(fmtcnv_state_mem)); + +struct convert_to_gray8_result { + unsigned int dst_pitch; + const u8 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_rgb332_result { + unsigned int dst_pitch; + const u8 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_rgb565_result { + unsigned int dst_pitch; + const u16 expected[TEST_BUF_SIZE]; + const u16 expected_swab[TEST_BUF_SIZE]; +}; + +struct convert_to_xrgb1555_result { + unsigned int dst_pitch; + const u16 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_argb1555_result { + unsigned int dst_pitch; + const u16 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_rgba5551_result { + unsigned int dst_pitch; + const u16 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_rgb888_result { + unsigned int dst_pitch; + const u8 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_bgr888_result { + unsigned int dst_pitch; + const u8 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_argb8888_result { + unsigned int dst_pitch; + const u32 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_xrgb2101010_result { + unsigned int dst_pitch; + const u32 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_argb2101010_result { + unsigned int dst_pitch; + const u32 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_mono_result { + unsigned int dst_pitch; + const u8 expected[TEST_BUF_SIZE]; +}; + +struct fb_swab_result { + unsigned int dst_pitch; + const u32 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_xbgr8888_result { + unsigned int dst_pitch; + const u32 expected[TEST_BUF_SIZE]; +}; + +struct convert_to_abgr8888_result { + unsigned int dst_pitch; + const u32 expected[TEST_BUF_SIZE]; +}; + +struct convert_xrgb8888_case { + const char *name; + unsigned int pitch; + struct drm_rect clip; + const u32 xrgb8888[TEST_BUF_SIZE]; + struct convert_to_gray8_result gray8_result; + struct convert_to_rgb332_result rgb332_result; + struct convert_to_rgb565_result rgb565_result; + struct convert_to_xrgb1555_result xrgb1555_result; + struct convert_to_argb1555_result argb1555_result; + struct convert_to_rgba5551_result rgba5551_result; + struct convert_to_rgb888_result rgb888_result; + struct convert_to_bgr888_result bgr888_result; + struct convert_to_argb8888_result argb8888_result; + struct convert_to_xrgb2101010_result xrgb2101010_result; + struct convert_to_argb2101010_result argb2101010_result; + struct convert_to_mono_result mono_result; + struct fb_swab_result swab_result; + struct convert_to_xbgr8888_result xbgr8888_result; + struct convert_to_abgr8888_result abgr8888_result; +}; + +static struct convert_xrgb8888_case convert_xrgb8888_cases[] = { + { + .name = "single_pixel_source_buffer", + .pitch = 1 * 4, + .clip = DRM_RECT_INIT(0, 0, 1, 1), + .xrgb8888 = { 0x01FF0000 }, + .gray8_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x4C }, + }, + .rgb332_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xE0 }, + }, + .rgb565_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xF800 }, + .expected_swab = { 0x00F8 }, + }, + .xrgb1555_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x7C00 }, + }, + .argb1555_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFC00 }, + }, + .rgba5551_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xF801 }, + }, + .rgb888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x00, 0x00, 0xFF }, + }, + .bgr888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFF, 0x00, 0x00 }, + }, + .argb8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFFFF0000 }, + }, + .xrgb2101010_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x3FF00000 }, + }, + .argb2101010_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFFF00000 }, + }, + .mono_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0b0 }, + }, + .swab_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x0000FF01 }, + }, + .xbgr8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x010000FF }, + }, + .abgr8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFF0000FF }, + }, + }, + { + .name = "single_pixel_clip_rectangle", + .pitch = 2 * 4, + .clip = DRM_RECT_INIT(1, 1, 1, 1), + .xrgb8888 = { + 0x00000000, 0x00000000, + 0x00000000, 0x10FF0000, + }, + .gray8_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x4C }, + }, + .rgb332_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xE0 }, + }, + .rgb565_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xF800 }, + .expected_swab = { 0x00F8 }, + }, + .xrgb1555_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x7C00 }, + }, + .argb1555_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFC00 }, + }, + .rgba5551_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xF801 }, + }, + .rgb888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x00, 0x00, 0xFF }, + }, + .bgr888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFF, 0x00, 0x00 }, + }, + .argb8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFFFF0000 }, + }, + .xrgb2101010_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x3FF00000 }, + }, + .argb2101010_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFFF00000 }, + }, + .mono_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0b0 }, + }, + .swab_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x0000FF10 }, + }, + .xbgr8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0x100000FF }, + }, + .abgr8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { 0xFF0000FF }, + }, + }, + { + /* Well known colors: White, black, red, green, blue, magenta, + * yellow and cyan. Different values for the X in XRGB8888 to + * make sure it is ignored. Partial clip area. + */ + .name = "well_known_colors", + .pitch = 4 * 4, + .clip = DRM_RECT_INIT(1, 1, 2, 4), + .xrgb8888 = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x11FFFFFF, 0x22000000, 0x00000000, + 0x00000000, 0x33FF0000, 0x4400FF00, 0x00000000, + 0x00000000, 0x550000FF, 0x66FF00FF, 0x00000000, + 0x00000000, 0x77FFFF00, 0x8800FFFF, 0x00000000, + }, + .gray8_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFF, 0x00, + 0x4C, 0x95, + 0x1C, 0x69, + 0xE2, 0xB2, + }, + }, + .rgb332_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFF, 0x00, + 0xE0, 0x1C, + 0x03, 0xE3, + 0xFC, 0x1F, + }, + }, + .rgb565_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFFFF, 0x0000, + 0xF800, 0x07E0, + 0x001F, 0xF81F, + 0xFFE0, 0x07FF, + }, + .expected_swab = { + 0xFFFF, 0x0000, + 0x00F8, 0xE007, + 0x1F00, 0x1FF8, + 0xE0FF, 0xFF07, + }, + }, + .xrgb1555_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0x7FFF, 0x0000, + 0x7C00, 0x03E0, + 0x001F, 0x7C1F, + 0x7FE0, 0x03FF, + }, + }, + .argb1555_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFFFF, 0x8000, + 0xFC00, 0x83E0, + 0x801F, 0xFC1F, + 0xFFE0, 0x83FF, + }, + }, + .rgba5551_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFFFF, 0x0001, + 0xF801, 0x07C1, + 0x003F, 0xF83F, + 0xFFC1, 0x07FF, + }, + }, + .rgb888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + }, + }, + .bgr888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + }, + }, + .argb8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFFFFFFFF, 0xFF000000, + 0xFFFF0000, 0xFF00FF00, + 0xFF0000FF, 0xFFFF00FF, + 0xFFFFFF00, 0xFF00FFFF, + }, + }, + .xrgb2101010_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0x3FFFFFFF, 0x00000000, + 0x3FF00000, 0x000FFC00, + 0x000003FF, 0x3FF003FF, + 0x3FFFFC00, 0x000FFFFF, + }, + }, + .argb2101010_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFFFFFFFF, 0xC0000000, + 0xFFF00000, 0xC00FFC00, + 0xC00003FF, 0xFFF003FF, + 0xFFFFFC00, 0xC00FFFFF, + }, + }, + .mono_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0b01, + 0b10, + 0b00, + 0b11, + }, + }, + .swab_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFFFFFF11, 0x00000022, + 0x0000FF33, 0x00FF0044, + 0xFF000055, 0xFF00FF66, + 0x00FFFF77, 0xFFFF0088, + }, + }, + .xbgr8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0x11FFFFFF, 0x22000000, + 0x330000FF, 0x4400FF00, + 0x55FF0000, 0x66FF00FF, + 0x7700FFFF, 0x88FFFF00, + }, + }, + .abgr8888_result = { + .dst_pitch = TEST_USE_DEFAULT_PITCH, + .expected = { + 0xFFFFFFFF, 0xFF000000, + 0xFF0000FF, 0xFF00FF00, + 0xFFFF0000, 0xFFFF00FF, + 0xFF00FFFF, 0xFFFFFF00, + }, + }, + }, + { + /* Randomly picked colors. Full buffer within the clip area. */ + .name = "destination_pitch", + .pitch = 3 * 4, + .clip = DRM_RECT_INIT(0, 0, 3, 3), + .xrgb8888 = { + 0xA10E449C, 0xB1114D05, 0xC1A8F303, + 0xD16CF073, 0xA20E449C, 0xB2114D05, + 0xC2A80303, 0xD26CF073, 0xA30E449C, + }, + .gray8_result = { + .dst_pitch = 5, + .expected = { + 0x3D, 0x32, 0xC1, 0x00, 0x00, + 0xBA, 0x3D, 0x32, 0x00, 0x00, + 0x34, 0xBA, 0x3D, 0x00, 0x00, + }, + }, + .rgb332_result = { + .dst_pitch = 5, + .expected = { + 0x0A, 0x08, 0xBC, 0x00, 0x00, + 0x7D, 0x0A, 0x08, 0x00, 0x00, + 0xA0, 0x7D, 0x0A, 0x00, 0x00, + }, + }, + .rgb565_result = { + .dst_pitch = 10, + .expected = { + 0x0A33, 0x1260, 0xAF80, 0x0000, 0x0000, + 0x6F8E, 0x0A33, 0x1260, 0x0000, 0x0000, + 0xA800, 0x6F8E, 0x0A33, 0x0000, 0x0000, + }, + .expected_swab = { + 0x330A, 0x6012, 0x80AF, 0x0000, 0x0000, + 0x8E6F, 0x330A, 0x6012, 0x0000, 0x0000, + 0x00A8, 0x8E6F, 0x330A, 0x0000, 0x0000, + }, + }, + .xrgb1555_result = { + .dst_pitch = 10, + .expected = { + 0x0513, 0x0920, 0x57C0, 0x0000, 0x0000, + 0x37CE, 0x0513, 0x0920, 0x0000, 0x0000, + 0x5400, 0x37CE, 0x0513, 0x0000, 0x0000, + }, + }, + .argb1555_result = { + .dst_pitch = 10, + .expected = { + 0x8513, 0x8920, 0xD7C0, 0x0000, 0x0000, + 0xB7CE, 0x8513, 0x8920, 0x0000, 0x0000, + 0xD400, 0xB7CE, 0x8513, 0x0000, 0x0000, + }, + }, + .rgba5551_result = { + .dst_pitch = 10, + .expected = { + 0x0A27, 0x1241, 0xAF81, 0x0000, 0x0000, + 0x6F9D, 0x0A27, 0x1241, 0x0000, 0x0000, + 0xA801, 0x6F9D, 0x0A27, 0x0000, 0x0000, + }, + }, + .rgb888_result = { + .dst_pitch = 15, + .expected = { + 0x9C, 0x44, 0x0E, 0x05, 0x4D, 0x11, 0x03, 0xF3, 0xA8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x73, 0xF0, 0x6C, 0x9C, 0x44, 0x0E, 0x05, 0x4D, 0x11, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x03, 0xA8, 0x73, 0xF0, 0x6C, 0x9C, 0x44, 0x0E, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, + .bgr888_result = { + .dst_pitch = 15, + .expected = { + 0x0E, 0x44, 0x9C, 0x11, 0x4D, 0x05, 0xA8, 0xF3, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6C, 0xF0, 0x73, 0x0E, 0x44, 0x9C, 0x11, 0x4D, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA8, 0x03, 0x03, 0x6C, 0xF0, 0x73, 0x0E, 0x44, 0x9C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, + .argb8888_result = { + .dst_pitch = 20, + .expected = { + 0xFF0E449C, 0xFF114D05, 0xFFA8F303, 0x00000000, 0x00000000, + 0xFF6CF073, 0xFF0E449C, 0xFF114D05, 0x00000000, 0x00000000, + 0xFFA80303, 0xFF6CF073, 0xFF0E449C, 0x00000000, 0x00000000, + }, + }, + .xrgb2101010_result = { + .dst_pitch = 20, + .expected = { + 0x03844672, 0x0444D414, 0x2A2F3C0C, 0x00000000, 0x00000000, + 0x1B1F0DCD, 0x03844672, 0x0444D414, 0x00000000, 0x00000000, + 0x2A20300C, 0x1B1F0DCD, 0x03844672, 0x00000000, 0x00000000, + }, + }, + .argb2101010_result = { + .dst_pitch = 20, + .expected = { + 0xC3844672, 0xC444D414, 0xEA2F3C0C, 0x00000000, 0x00000000, + 0xDB1F0DCD, 0xC3844672, 0xC444D414, 0x00000000, 0x00000000, + 0xEA20300C, 0xDB1F0DCD, 0xC3844672, 0x00000000, 0x00000000, + }, + }, + .mono_result = { + .dst_pitch = 2, + .expected = { + 0b100, 0b000, + 0b001, 0b000, + 0b010, 0b000, + }, + }, + .swab_result = { + .dst_pitch = 20, + .expected = { + 0x9C440EA1, 0x054D11B1, 0x03F3A8C1, 0x00000000, 0x00000000, + 0x73F06CD1, 0x9C440EA2, 0x054D11B2, 0x00000000, 0x00000000, + 0x0303A8C2, 0x73F06CD2, 0x9C440EA3, 0x00000000, 0x00000000, + }, + }, + .xbgr8888_result = { + .dst_pitch = 20, + .expected = { + 0xA19C440E, 0xB1054D11, 0xC103F3A8, 0x00000000, 0x00000000, + 0xD173F06C, 0xA29C440E, 0xB2054D11, 0x00000000, 0x00000000, + 0xC20303A8, 0xD273F06C, 0xA39C440E, 0x00000000, 0x00000000, + }, + }, + .abgr8888_result = { + .dst_pitch = 20, + .expected = { + 0xFF9C440E, 0xFF054D11, 0xFF03F3A8, 0x00000000, 0x00000000, + 0xFF73F06C, 0xFF9C440E, 0xFF054D11, 0x00000000, 0x00000000, + 0xFF0303A8, 0xFF73F06C, 0xFF9C440E, 0x00000000, 0x00000000, + }, + }, + }, +}; + +/* + * conversion_buf_size - Return the destination buffer size required to convert + * between formats. + * @dst_format: destination buffer pixel format (DRM_FORMAT_*) + * @dst_pitch: Number of bytes between two consecutive scanlines within dst + * @clip: Clip rectangle area to convert + * + * Returns: + * The size of the destination buffer or negative value on error. + */ +static size_t conversion_buf_size(u32 dst_format, unsigned int dst_pitch, + const struct drm_rect *clip, int plane) +{ + const struct drm_format_info *dst_fi = drm_format_info(dst_format); + + if (!dst_fi) + return -EINVAL; + + if (!dst_pitch) + dst_pitch = drm_format_info_min_pitch(dst_fi, plane, drm_rect_width(clip)); + + return dst_pitch * drm_rect_height(clip); +} + +static u16 *le16buf_to_cpu(struct kunit *test, const __le16 *buf, size_t buf_size) +{ + u16 *dst = NULL; + int n; + + dst = kunit_kzalloc(test, sizeof(*dst) * buf_size, GFP_KERNEL); + if (!dst) + return NULL; + + for (n = 0; n < buf_size; n++) + dst[n] = le16_to_cpu(buf[n]); + + return dst; +} + +static u32 *le32buf_to_cpu(struct kunit *test, const __le32 *buf, size_t buf_size) +{ + u32 *dst = NULL; + int n; + + dst = kunit_kzalloc(test, sizeof(*dst) * buf_size, GFP_KERNEL); + if (!dst) + return NULL; + + for (n = 0; n < buf_size; n++) + dst[n] = le32_to_cpu((__force __le32)buf[n]); + + return dst; +} + +static __le32 *cpubuf_to_le32(struct kunit *test, const u32 *buf, size_t buf_size) +{ + __le32 *dst = NULL; + int n; + + dst = kunit_kzalloc(test, sizeof(*dst) * buf_size, GFP_KERNEL); + if (!dst) + return NULL; + + for (n = 0; n < buf_size; n++) + dst[n] = cpu_to_le32(buf[n]); + + return dst; +} + +static void convert_xrgb8888_case_desc(struct convert_xrgb8888_case *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(convert_xrgb8888, convert_xrgb8888_cases, + convert_xrgb8888_case_desc); + +static void drm_test_fb_xrgb8888_to_gray8(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_gray8_result *result = ¶ms->gray8_result; + size_t dst_size; + u8 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_R8, result->dst_pitch, + ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_gray8(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_rgb332(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_rgb332_result *result = ¶ms->rgb332_result; + size_t dst_size; + u8 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_RGB332, result->dst_pitch, + ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_rgb332(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_rgb565(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_rgb565_result *result = ¶ms->rgb565_result; + size_t dst_size; + u16 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_RGB565, result->dst_pitch, + ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_rgb565(&dst, dst_pitch, &src, &fb, ¶ms->clip, + &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + drm_fb_xrgb8888_to_rgb565be(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip, + &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected_swab, dst_size); + + buf = dst.vaddr; + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_rgb565(&dst, dst_pitch, &src, &fb, ¶ms->clip, + &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_xrgb1555(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_xrgb1555_result *result = ¶ms->xrgb1555_result; + size_t dst_size; + u16 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_XRGB1555, result->dst_pitch, + ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_xrgb1555(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_xrgb1555(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_argb1555(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_argb1555_result *result = ¶ms->argb1555_result; + size_t dst_size; + u16 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_ARGB1555, result->dst_pitch, + ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_argb1555(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_argb1555(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_rgba5551(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_rgba5551_result *result = ¶ms->rgba5551_result; + size_t dst_size; + u16 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_RGBA5551, result->dst_pitch, + ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_rgba5551(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_rgba5551(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le16buf_to_cpu(test, (__force const __le16 *)buf, dst_size / sizeof(__le16)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_rgb888(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_rgb888_result *result = ¶ms->rgb888_result; + size_t dst_size; + u8 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_RGB888, result->dst_pitch, + ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + /* + * RGB888 expected results are already in little-endian + * order, so there's no need to convert the test output. + */ + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_rgb888(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_rgb888(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_bgr888(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_bgr888_result *result = ¶ms->bgr888_result; + size_t dst_size; + u8 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_BGR888, result->dst_pitch, + ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + /* + * BGR888 expected results are already in little-endian + * order, so there's no need to convert the test output. + */ + drm_fb_xrgb8888_to_bgr888(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip, + &fmtcnv_state); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_bgr888(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip, + &fmtcnv_state); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_argb8888(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_argb8888_result *result = ¶ms->argb8888_result; + size_t dst_size; + u32 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_ARGB8888, + result->dst_pitch, ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_argb8888(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_argb8888(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_xrgb2101010(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_xrgb2101010_result *result = ¶ms->xrgb2101010_result; + size_t dst_size; + u32 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_XRGB2101010, + result->dst_pitch, ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_xrgb2101010(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_xrgb2101010(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_argb2101010(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_argb2101010_result *result = ¶ms->argb2101010_result; + size_t dst_size; + u32 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_ARGB2101010, + result->dst_pitch, ¶ms->clip, 0); + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_argb2101010(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_argb2101010(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_mono(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_mono_result *result = ¶ms->mono_result; + size_t dst_size; + u8 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_C1, result->dst_pitch, ¶ms->clip, 0); + + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_mono(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_swab(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct fb_swab_result *result = ¶ms->swab_result; + size_t dst_size; + u32 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_XRGB8888, result->dst_pitch, ¶ms->clip, 0); + + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_swab(&dst, dst_pitch, &src, &fb, ¶ms->clip, false, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; /* restore original value of buf */ + memset(buf, 0, dst_size); + + drm_fb_swab(&dst, dst_pitch, &src, &fb, ¶ms->clip, false, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; + memset(buf, 0, dst_size); + + drm_fb_xrgb8888_to_bgrx8888(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); + + buf = dst.vaddr; + memset(buf, 0, dst_size); + + struct drm_format_info mock_format = *fb.format; + + mock_format.format |= DRM_FORMAT_BIG_ENDIAN; + fb.format = &mock_format; + + drm_fb_swab(&dst, dst_pitch, &src, &fb, ¶ms->clip, false, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_abgr8888(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_abgr8888_result *result = ¶ms->abgr8888_result; + size_t dst_size; + u32 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_XBGR8888, result->dst_pitch, ¶ms->clip, 0); + + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_abgr8888(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +static void drm_test_fb_xrgb8888_to_xbgr8888(struct kunit *test) +{ + const struct convert_xrgb8888_case *params = test->param_value; + const struct convert_to_xbgr8888_result *result = ¶ms->xbgr8888_result; + size_t dst_size; + u32 *buf = NULL; + __le32 *xrgb8888 = NULL; + struct iosys_map dst, src; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_XBGR8888, result->dst_pitch, ¶ms->clip, 0); + + KUNIT_ASSERT_GT(test, dst_size, 0); + + buf = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + iosys_map_set_vaddr(&dst, buf); + + xrgb8888 = cpubuf_to_le32(test, params->xrgb8888, TEST_BUF_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xrgb8888); + iosys_map_set_vaddr(&src, xrgb8888); + + const unsigned int *dst_pitch = (result->dst_pitch == TEST_USE_DEFAULT_PITCH) ? + NULL : &result->dst_pitch; + + drm_fb_xrgb8888_to_xbgr8888(&dst, dst_pitch, &src, &fb, ¶ms->clip, &fmtcnv_state); + buf = le32buf_to_cpu(test, (__force const __le32 *)buf, dst_size / sizeof(u32)); + KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size); +} + +struct clip_offset_case { + const char *name; + unsigned int pitch; + u32 format; + struct drm_rect clip; + unsigned int expected_offset; +}; + +static struct clip_offset_case clip_offset_cases[] = { + { + .name = "pass through", + .pitch = TEST_USE_DEFAULT_PITCH, + .format = DRM_FORMAT_XRGB8888, + .clip = DRM_RECT_INIT(0, 0, 3, 3), + .expected_offset = 0 + }, + { + .name = "horizontal offset", + .pitch = TEST_USE_DEFAULT_PITCH, + .format = DRM_FORMAT_XRGB8888, + .clip = DRM_RECT_INIT(1, 0, 3, 3), + .expected_offset = 4, + }, + { + .name = "vertical offset", + .pitch = TEST_USE_DEFAULT_PITCH, + .format = DRM_FORMAT_XRGB8888, + .clip = DRM_RECT_INIT(0, 1, 3, 3), + .expected_offset = 12, + }, + { + .name = "horizontal and vertical offset", + .pitch = TEST_USE_DEFAULT_PITCH, + .format = DRM_FORMAT_XRGB8888, + .clip = DRM_RECT_INIT(1, 1, 3, 3), + .expected_offset = 16, + }, + { + .name = "horizontal offset (custom pitch)", + .pitch = 20, + .format = DRM_FORMAT_XRGB8888, + .clip = DRM_RECT_INIT(1, 0, 3, 3), + .expected_offset = 4, + }, + { + .name = "vertical offset (custom pitch)", + .pitch = 20, + .format = DRM_FORMAT_XRGB8888, + .clip = DRM_RECT_INIT(0, 1, 3, 3), + .expected_offset = 20, + }, + { + .name = "horizontal and vertical offset (custom pitch)", + .pitch = 20, + .format = DRM_FORMAT_XRGB8888, + .clip = DRM_RECT_INIT(1, 1, 3, 3), + .expected_offset = 24, + }, +}; + +static void clip_offset_case_desc(struct clip_offset_case *t, char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(clip_offset, clip_offset_cases, clip_offset_case_desc); + +static void drm_test_fb_clip_offset(struct kunit *test) +{ + const struct clip_offset_case *params = test->param_value; + const struct drm_format_info *format_info = drm_format_info(params->format); + + unsigned int offset; + unsigned int pitch = params->pitch; + + if (pitch == TEST_USE_DEFAULT_PITCH) + pitch = drm_format_info_min_pitch(format_info, 0, + drm_rect_width(¶ms->clip)); + + /* + * Assure that the pitch is not zero, because this will inevitable cause the + * wrong expected result + */ + KUNIT_ASSERT_NE(test, pitch, 0); + + offset = drm_fb_clip_offset(pitch, format_info, ¶ms->clip); + + KUNIT_EXPECT_EQ(test, offset, params->expected_offset); +} + +struct fb_memcpy_case { + const char *name; + u32 format; + struct drm_rect clip; + unsigned int src_pitches[DRM_FORMAT_MAX_PLANES]; + const u32 src[DRM_FORMAT_MAX_PLANES][TEST_BUF_SIZE]; + unsigned int dst_pitches[DRM_FORMAT_MAX_PLANES]; + const u32 expected[DRM_FORMAT_MAX_PLANES][TEST_BUF_SIZE]; +}; + +/* The `src` and `expected` buffers are u32 arrays. To deal with planes that + * have a cpp != 4 the values are stored together on the same u32 number in a + * way so the order in memory is correct in a little-endian machine. + * + * Because of that, on some occasions, parts of a u32 will not be part of the + * test, to make this explicit the 0xFF byte is used on those parts. + */ + +static struct fb_memcpy_case fb_memcpy_cases[] = { + { + .name = "single_pixel_source_buffer", + .format = DRM_FORMAT_XRGB8888, + .clip = DRM_RECT_INIT(0, 0, 1, 1), + .src_pitches = { 1 * 4 }, + .src = {{ 0x01020304 }}, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = {{ 0x01020304 }}, + }, + { + .name = "single_pixel_source_buffer", + .format = DRM_FORMAT_XRGB8888_A8, + .clip = DRM_RECT_INIT(0, 0, 1, 1), + .src_pitches = { 1 * 4, 1 }, + .src = { + { 0x01020304 }, + { 0xFFFFFF01 }, + }, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = { + { 0x01020304 }, + { 0x00000001 }, + }, + }, + { + .name = "single_pixel_source_buffer", + .format = DRM_FORMAT_YUV444, + .clip = DRM_RECT_INIT(0, 0, 1, 1), + .src_pitches = { 1, 1, 1 }, + .src = { + { 0xFFFFFF01 }, + { 0xFFFFFF01 }, + { 0xFFFFFF01 }, + }, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = { + { 0x00000001 }, + { 0x00000001 }, + { 0x00000001 }, + }, + }, + { + .name = "single_pixel_clip_rectangle", + .format = DRM_FORMAT_XBGR8888, + .clip = DRM_RECT_INIT(1, 1, 1, 1), + .src_pitches = { 2 * 4 }, + .src = { + { + 0x00000000, 0x00000000, + 0x00000000, 0x01020304, + }, + }, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = { + { 0x01020304 }, + }, + }, + { + .name = "single_pixel_clip_rectangle", + .format = DRM_FORMAT_XRGB8888_A8, + .clip = DRM_RECT_INIT(1, 1, 1, 1), + .src_pitches = { 2 * 4, 2 * 1 }, + .src = { + { + 0x00000000, 0x00000000, + 0x00000000, 0x01020304, + }, + { 0x01000000 }, + }, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = { + { 0x01020304 }, + { 0x00000001 }, + }, + }, + { + .name = "single_pixel_clip_rectangle", + .format = DRM_FORMAT_YUV444, + .clip = DRM_RECT_INIT(1, 1, 1, 1), + .src_pitches = { 2 * 1, 2 * 1, 2 * 1 }, + .src = { + { 0x01000000 }, + { 0x01000000 }, + { 0x01000000 }, + }, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = { + { 0x00000001 }, + { 0x00000001 }, + { 0x00000001 }, + }, + }, + { + .name = "well_known_colors", + .format = DRM_FORMAT_XBGR8888, + .clip = DRM_RECT_INIT(1, 1, 2, 4), + .src_pitches = { 4 * 4 }, + .src = { + { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x11FFFFFF, 0x22000000, 0x00000000, + 0x00000000, 0x33FF0000, 0x4400FF00, 0x00000000, + 0x00000000, 0x550000FF, 0x66FF00FF, 0x00000000, + 0x00000000, 0x77FFFF00, 0x8800FFFF, 0x00000000, + }, + }, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = { + { + 0x11FFFFFF, 0x22000000, + 0x33FF0000, 0x4400FF00, + 0x550000FF, 0x66FF00FF, + 0x77FFFF00, 0x8800FFFF, + }, + }, + }, + { + .name = "well_known_colors", + .format = DRM_FORMAT_XRGB8888_A8, + .clip = DRM_RECT_INIT(1, 1, 2, 4), + .src_pitches = { 4 * 4, 4 * 1 }, + .src = { + { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xFFFFFFFF, 0xFF000000, 0x00000000, + 0x00000000, 0xFFFF0000, 0xFF00FF00, 0x00000000, + 0x00000000, 0xFF0000FF, 0xFFFF00FF, 0x00000000, + 0x00000000, 0xFFFFFF00, 0xFF00FFFF, 0x00000000, + }, + { + 0x00000000, + 0x00221100, + 0x00443300, + 0x00665500, + 0x00887700, + }, + }, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = { + { + 0xFFFFFFFF, 0xFF000000, + 0xFFFF0000, 0xFF00FF00, + 0xFF0000FF, 0xFFFF00FF, + 0xFFFFFF00, 0xFF00FFFF, + }, + { + 0x44332211, + 0x88776655, + }, + }, + }, + { + .name = "well_known_colors", + .format = DRM_FORMAT_YUV444, + .clip = DRM_RECT_INIT(1, 1, 2, 4), + .src_pitches = { 4 * 1, 4 * 1, 4 * 1 }, + .src = { + { + 0x00000000, + 0x0000FF00, + 0x00954C00, + 0x00691D00, + 0x00B2E100, + }, + { + 0x00000000, + 0x00000000, + 0x00BEDE00, + 0x00436500, + 0x00229B00, + }, + { + 0x00000000, + 0x00000000, + 0x007E9C00, + 0x0083E700, + 0x00641A00, + }, + }, + .dst_pitches = { TEST_USE_DEFAULT_PITCH }, + .expected = { + { + 0x954C00FF, + 0xB2E1691D, + }, + { + 0xBEDE0000, + 0x229B4365, + }, + { + 0x7E9C0000, + 0x641A83E7, + }, + }, + }, + { + .name = "destination_pitch", + .format = DRM_FORMAT_XBGR8888, + .clip = DRM_RECT_INIT(0, 0, 3, 3), + .src_pitches = { 3 * 4 }, + .src = { + { + 0xA10E449C, 0xB1114D05, 0xC1A8F303, + 0xD16CF073, 0xA20E449C, 0xB2114D05, + 0xC2A80303, 0xD26CF073, 0xA30E449C, + }, + }, + .dst_pitches = { 5 * 4 }, + .expected = { + { + 0xA10E449C, 0xB1114D05, 0xC1A8F303, 0x00000000, 0x00000000, + 0xD16CF073, 0xA20E449C, 0xB2114D05, 0x00000000, 0x00000000, + 0xC2A80303, 0xD26CF073, 0xA30E449C, 0x00000000, 0x00000000, + }, + }, + }, + { + .name = "destination_pitch", + .format = DRM_FORMAT_XRGB8888_A8, + .clip = DRM_RECT_INIT(0, 0, 3, 3), + .src_pitches = { 3 * 4, 3 * 1 }, + .src = { + { + 0xFF0E449C, 0xFF114D05, 0xFFA8F303, + 0xFF6CF073, 0xFF0E449C, 0xFF114D05, + 0xFFA80303, 0xFF6CF073, 0xFF0E449C, + }, + { + 0xB2C1B1A1, + 0xD2A3D1A2, + 0xFFFFFFC2, + }, + }, + .dst_pitches = { 5 * 4, 5 * 1 }, + .expected = { + { + 0xFF0E449C, 0xFF114D05, 0xFFA8F303, 0x00000000, 0x00000000, + 0xFF6CF073, 0xFF0E449C, 0xFF114D05, 0x00000000, 0x00000000, + 0xFFA80303, 0xFF6CF073, 0xFF0E449C, 0x00000000, 0x00000000, + }, + { + 0x00C1B1A1, + 0xD1A2B200, + 0xD2A30000, + 0xFF0000C2, + }, + }, + }, + { + .name = "destination_pitch", + .format = DRM_FORMAT_YUV444, + .clip = DRM_RECT_INIT(0, 0, 3, 3), + .src_pitches = { 3 * 1, 3 * 1, 3 * 1 }, + .src = { + { + 0xBAC1323D, + 0xBA34323D, + 0xFFFFFF3D, + }, + { + 0xE1ABEC2A, + 0xE1EAEC2A, + 0xFFFFFF2A, + }, + { + 0xBCEBE4D7, + 0xBC65E4D7, + 0xFFFFFFD7, + }, + }, + .dst_pitches = { 5 * 1, 5 * 1, 5 * 1 }, + .expected = { + { + 0x00C1323D, + 0x323DBA00, + 0xBA340000, + 0xFF00003D, + }, + { + 0x00ABEC2A, + 0xEC2AE100, + 0xE1EA0000, + 0xFF00002A, + }, + { + 0x00EBE4D7, + 0xE4D7BC00, + 0xBC650000, + 0xFF0000D7, + }, + }, + }, +}; + +static void fb_memcpy_case_desc(struct fb_memcpy_case *t, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s: %p4cc", t->name, &t->format); +} + +KUNIT_ARRAY_PARAM(fb_memcpy, fb_memcpy_cases, fb_memcpy_case_desc); + +static void drm_test_fb_memcpy(struct kunit *test) +{ + const struct fb_memcpy_case *params = test->param_value; + size_t dst_size[DRM_FORMAT_MAX_PLANES] = { 0 }; + u32 *buf[DRM_FORMAT_MAX_PLANES] = { 0 }; + __le32 *src_cp[DRM_FORMAT_MAX_PLANES] = { 0 }; + __le32 *expected[DRM_FORMAT_MAX_PLANES] = { 0 }; + struct iosys_map dst[DRM_FORMAT_MAX_PLANES]; + struct iosys_map src[DRM_FORMAT_MAX_PLANES]; + + struct drm_framebuffer fb = { + .format = drm_format_info(params->format), + }; + + memcpy(fb.pitches, params->src_pitches, DRM_FORMAT_MAX_PLANES * sizeof(int)); + + for (size_t i = 0; i < fb.format->num_planes; i++) { + dst_size[i] = conversion_buf_size(params->format, params->dst_pitches[i], + ¶ms->clip, i); + KUNIT_ASSERT_GT(test, dst_size[i], 0); + + buf[i] = kunit_kzalloc(test, dst_size[i], GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf[i]); + iosys_map_set_vaddr(&dst[i], buf[i]); + + src_cp[i] = cpubuf_to_le32(test, params->src[i], TEST_BUF_SIZE); + iosys_map_set_vaddr(&src[i], src_cp[i]); + } + + const unsigned int *dst_pitches = params->dst_pitches[0] == TEST_USE_DEFAULT_PITCH ? NULL : + params->dst_pitches; + + drm_fb_memcpy(dst, dst_pitches, src, &fb, ¶ms->clip); + + for (size_t i = 0; i < fb.format->num_planes; i++) { + expected[i] = cpubuf_to_le32(test, params->expected[i], TEST_BUF_SIZE); + KUNIT_EXPECT_MEMEQ_MSG(test, buf[i], expected[i], dst_size[i], + "Failed expectation on plane %zu", i); + + memset(buf[i], 0, dst_size[i]); + } + + drm_fb_memcpy(dst, dst_pitches, src, &fb, ¶ms->clip); + + for (size_t i = 0; i < fb.format->num_planes; i++) { + expected[i] = cpubuf_to_le32(test, params->expected[i], TEST_BUF_SIZE); + KUNIT_EXPECT_MEMEQ_MSG(test, buf[i], expected[i], dst_size[i], + "Failed expectation on plane %zu", i); + } +} + +static struct kunit_case drm_format_helper_test_cases[] = { + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_gray8, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_rgb332, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_rgb565, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_xrgb1555, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_argb1555, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_rgba5551, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_rgb888, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_bgr888, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_argb8888, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_xrgb2101010, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_argb2101010, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_mono, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_swab, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_xbgr8888, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_xrgb8888_to_abgr8888, convert_xrgb8888_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_clip_offset, clip_offset_gen_params), + KUNIT_CASE_PARAM(drm_test_fb_memcpy, fb_memcpy_gen_params), + {} +}; + +static struct kunit_suite drm_format_helper_test_suite = { + .name = "drm_format_helper_test", + .test_cases = drm_format_helper_test_cases, +}; + +kunit_test_suite(drm_format_helper_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the drm_format_helper APIs"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>"); diff --git a/drivers/gpu/drm/tests/drm_format_test.c b/drivers/gpu/drm/tests/drm_format_test.c new file mode 100644 index 000000000..22e2371fd --- /dev/null +++ b/drivers/gpu/drm/tests/drm_format_test.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test cases for the drm_format functions + * + * Copyright (c) 2022 Maíra Canal <mairacanal@riseup.net> + */ + +#include <kunit/test.h> + +#include <drm/drm_fourcc.h> + +static void drm_test_format_block_width_invalid(struct kunit *test) +{ + const struct drm_format_info *info = NULL; + + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, -1), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 1), 0); +} + +static void drm_test_format_block_width_one_plane(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_XRGB4444); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 0), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 1), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, -1), 0); +} + +static void drm_test_format_block_width_two_plane(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_NV12); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 0), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 2), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, -1), 0); +} + +static void drm_test_format_block_width_three_plane(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_YUV422); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 0), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 2), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 3), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, -1), 0); +} + +static void drm_test_format_block_width_tiled(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_X0L0); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 0), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, 1), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_width(info, -1), 0); +} + +static void drm_test_format_block_height_invalid(struct kunit *test) +{ + const struct drm_format_info *info = NULL; + + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, -1), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 1), 0); +} + +static void drm_test_format_block_height_one_plane(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_XRGB4444); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 0), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, -1), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 1), 0); +} + +static void drm_test_format_block_height_two_plane(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_NV12); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 0), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 2), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, -1), 0); +} + +static void drm_test_format_block_height_three_plane(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_YUV422); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 0), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 2), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 3), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, -1), 0); +} + +static void drm_test_format_block_height_tiled(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_X0L0); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 0), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, 1), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_block_height(info, -1), 0); +} + +static void drm_test_format_min_pitch_invalid(struct kunit *test) +{ + const struct drm_format_info *info = NULL; + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, -1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 0), 0); +} + +static void drm_test_format_min_pitch_one_plane_8bpp(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_RGB332); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, -1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 0), 0); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 2), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 640), 640); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1024), 1024); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1920), 1920); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 4096), 4096); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 671), 671); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX), + (uint64_t)UINT_MAX); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, (UINT_MAX - 1)), + (uint64_t)(UINT_MAX - 1)); +} + +static void drm_test_format_min_pitch_one_plane_16bpp(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_XRGB4444); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, -1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 0), 0); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 2), 4); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 640), 1280); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1024), 2048); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1920), 3840); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 4096), 8192); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 671), 1342); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX), + (uint64_t)UINT_MAX * 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, (UINT_MAX - 1)), + (uint64_t)(UINT_MAX - 1) * 2); +} + +static void drm_test_format_min_pitch_one_plane_24bpp(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_RGB888); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, -1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 0), 0); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1), 3); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 2), 6); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 640), 1920); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1024), 3072); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1920), 5760); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 4096), 12288); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 671), 2013); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX), + (uint64_t)UINT_MAX * 3); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX - 1), + (uint64_t)(UINT_MAX - 1) * 3); +} + +static void drm_test_format_min_pitch_one_plane_32bpp(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_ABGR8888); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, -1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 0), 0); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1), 4); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 2), 8); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 640), 2560); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1024), 4096); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1920), 7680); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 4096), 16384); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 671), 2684); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX), + (uint64_t)UINT_MAX * 4); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX - 1), + (uint64_t)(UINT_MAX - 1) * 4); +} + +static void drm_test_format_min_pitch_two_plane(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_NV12); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, -1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 0), 0); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 1), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 2), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 1), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 640), 640); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 320), 640); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1024), 1024); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 512), 1024); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1920), 1920); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 960), 1920); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 4096), 4096); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 2048), 4096); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 671), 671); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 336), 672); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX), + (uint64_t)UINT_MAX); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, UINT_MAX / 2 + 1), + (uint64_t)UINT_MAX + 1); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, (UINT_MAX - 1)), + (uint64_t)(UINT_MAX - 1)); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, (UINT_MAX - 1) / 2), + (uint64_t)(UINT_MAX - 1)); +} + +static void drm_test_format_min_pitch_three_plane_8bpp(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_YUV422); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, -1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 3, 0), 0); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 1), 1); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 2), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 2), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 2), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 640), 640); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 320), 320); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 320), 320); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1024), 1024); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 512), 512); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 512), 512); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1920), 1920); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 960), 960); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 960), 960); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 4096), 4096); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 2048), 2048); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 2048), 2048); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 671), 671); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 336), 336); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, 336), 336); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX), + (uint64_t)UINT_MAX); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, UINT_MAX / 2 + 1), + (uint64_t)UINT_MAX / 2 + 1); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, UINT_MAX / 2 + 1), + (uint64_t)UINT_MAX / 2 + 1); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, (UINT_MAX - 1) / 2), + (uint64_t)(UINT_MAX - 1) / 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, (UINT_MAX - 1) / 2), + (uint64_t)(UINT_MAX - 1) / 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 2, (UINT_MAX - 1) / 2), + (uint64_t)(UINT_MAX - 1) / 2); +} + +static void drm_test_format_min_pitch_tiled(struct kunit *test) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_X0L2); + + KUNIT_ASSERT_NOT_NULL(test, info); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, -1, 0), 0); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 1, 0), 0); + + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1), 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 2), 4); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 640), 1280); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1024), 2048); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 1920), 3840); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 4096), 8192); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, 671), 1342); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX), + (uint64_t)UINT_MAX * 2); + KUNIT_EXPECT_EQ(test, drm_format_info_min_pitch(info, 0, UINT_MAX - 1), + (uint64_t)(UINT_MAX - 1) * 2); +} + +static struct kunit_case drm_format_tests[] = { + KUNIT_CASE(drm_test_format_block_width_invalid), + KUNIT_CASE(drm_test_format_block_width_one_plane), + KUNIT_CASE(drm_test_format_block_width_two_plane), + KUNIT_CASE(drm_test_format_block_width_three_plane), + KUNIT_CASE(drm_test_format_block_width_tiled), + KUNIT_CASE(drm_test_format_block_height_invalid), + KUNIT_CASE(drm_test_format_block_height_one_plane), + KUNIT_CASE(drm_test_format_block_height_two_plane), + KUNIT_CASE(drm_test_format_block_height_three_plane), + KUNIT_CASE(drm_test_format_block_height_tiled), + KUNIT_CASE(drm_test_format_min_pitch_invalid), + KUNIT_CASE(drm_test_format_min_pitch_one_plane_8bpp), + KUNIT_CASE(drm_test_format_min_pitch_one_plane_16bpp), + KUNIT_CASE(drm_test_format_min_pitch_one_plane_24bpp), + KUNIT_CASE(drm_test_format_min_pitch_one_plane_32bpp), + KUNIT_CASE(drm_test_format_min_pitch_two_plane), + KUNIT_CASE(drm_test_format_min_pitch_three_plane_8bpp), + KUNIT_CASE(drm_test_format_min_pitch_tiled), + {} +}; + +static struct kunit_suite drm_format_test_suite = { + .name = "drm_format", + .test_cases = drm_format_tests, +}; + +kunit_test_suite(drm_format_test_suite); + +MODULE_DESCRIPTION("Test cases for the drm_format functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_framebuffer_test.c b/drivers/gpu/drm/tests/drm_framebuffer_test.c new file mode 100644 index 000000000..9b8e01e8c --- /dev/null +++ b/drivers/gpu/drm/tests/drm_framebuffer_test.c @@ -0,0 +1,725 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test cases for the drm_framebuffer functions + * + * Copyright (c) 2022 Maíra Canal <mairacanal@riseup.net> + */ + +#include <kunit/device.h> +#include <kunit/test.h> + +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_mode.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_print.h> + +#include "../drm_crtc_internal.h" + +#define MIN_WIDTH 4 +#define MAX_WIDTH 4096 +#define MIN_HEIGHT 4 +#define MAX_HEIGHT 4096 + +#define DRM_MODE_FB_INVALID BIT(2) + +struct drm_framebuffer_test { + int buffer_created; + struct drm_mode_fb_cmd2 cmd; + const char *name; +}; + +static const struct drm_framebuffer_test drm_framebuffer_create_cases[] = { +{ .buffer_created = 1, .name = "ABGR8888 normal sizes", + .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .pitches = { 4 * 600, 0, 0 }, + } +}, +{ .buffer_created = 1, .name = "ABGR8888 max sizes", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, + } +}, +{ .buffer_created = 1, .name = "ABGR8888 pitch greater than min required", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH + 1, 0, 0 }, + } +}, +{ .buffer_created = 0, .name = "ABGR8888 pitch less than min required", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH - 1, 0, 0 }, + } +}, +{ .buffer_created = 0, .name = "ABGR8888 Invalid width", + .cmd = { .width = MAX_WIDTH + 1, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .pitches = { 4 * (MAX_WIDTH + 1), 0, 0 }, + } +}, +{ .buffer_created = 0, .name = "ABGR8888 Invalid buffer handle", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 0, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, + } +}, +{ .buffer_created = 0, .name = "No pixel format", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = 0, + .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, + } +}, +{ .buffer_created = 0, .name = "ABGR8888 Width 0", + .cmd = { .width = 0, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, + } +}, +{ .buffer_created = 0, .name = "ABGR8888 Height 0", + .cmd = { .width = MAX_WIDTH, .height = 0, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, + } +}, +{ .buffer_created = 0, .name = "ABGR8888 Out of bound height * pitch combination", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .offsets = { UINT_MAX - 1, 0, 0 }, + .pitches = { 4 * MAX_WIDTH, 0, 0 }, + } +}, +{ .buffer_created = 1, .name = "ABGR8888 Large buffer offset", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, + .pitches = { 4 * MAX_WIDTH, 0, 0 }, + } +}, + +/* + * All entries in members that represents per-plane values (@modifier, @handles, + * @pitches and @offsets) must be zero when unused. + */ +{ .buffer_created = 0, .name = "ABGR8888 Buffer offset for inexistent plane", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, UINT_MAX / 2, 0 }, + .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + } +}, + +{ .buffer_created = 0, .name = "ABGR8888 Invalid flag", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, + .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_INVALID, + } +}, +{ .buffer_created = 1, .name = "ABGR8888 Set DRM_MODE_FB_MODIFIERS without modifiers", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, + .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + } +}, +{ .buffer_created = 1, .name = "ABGR8888 Valid buffer modifier", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, + .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { AFBC_FORMAT_MOD_YTR, 0, 0 }, + } +}, +{ .buffer_created = 0, + .name = "ABGR8888 Invalid buffer modifier(DRM_FORMAT_MOD_SAMSUNG_64_32_TILE)", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, + .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 }, + } +}, +{ .buffer_created = 1, .name = "ABGR8888 Extra pitches without DRM_MODE_FB_MODIFIERS", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, + .pitches = { 4 * MAX_WIDTH, 4 * MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 0, .name = "ABGR8888 Extra pitches with DRM_MODE_FB_MODIFIERS", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, + .handles = { 1, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + .pitches = { 4 * MAX_WIDTH, 4 * MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 1, .name = "NV12 Normal sizes", + .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 0 }, .pitches = { 600, 600, 0 }, + } +}, +{ .buffer_created = 1, .name = "NV12 Max sizes", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 0 }, .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 0, .name = "NV12 Invalid pitch", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 0 }, .pitches = { MAX_WIDTH, MAX_WIDTH - 1, 0 }, + } +}, +{ .buffer_created = 0, .name = "NV12 Invalid modifier/missing DRM_MODE_FB_MODIFIERS flag", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 0 }, .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 }, + .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 0, .name = "NV12 different modifier per-plane", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 }, + .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 1, .name = "NV12 with DRM_FORMAT_MOD_SAMSUNG_64_32_TILE", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, + DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0 }, + .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 0, .name = "NV12 Valid modifiers without DRM_MODE_FB_MODIFIERS", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 0 }, .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, + DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0 }, + .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 0, .name = "NV12 Modifier for inexistent plane", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, + DRM_FORMAT_MOD_SAMSUNG_64_32_TILE }, + .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 0, .name = "NV12 Handle for inexistent plane", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, + .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, + } +}, +{ .buffer_created = 1, .name = "NV12 Handle for inexistent plane without DRM_MODE_FB_MODIFIERS", + .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_NV12, + .handles = { 1, 1, 1 }, .pitches = { 600, 600, 600 }, + } +}, +{ .buffer_created = 1, .name = "YVU420 DRM_MODE_FB_MODIFIERS set without modifier", + .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, + .pitches = { 600, 300, 300 }, + } +}, +{ .buffer_created = 1, .name = "YVU420 Normal sizes", + .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .pitches = { 600, 300, 300 }, + } +}, +{ .buffer_created = 1, .name = "YVU420 Max sizes", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), + DIV_ROUND_UP(MAX_WIDTH, 2) }, + } +}, +{ .buffer_created = 0, .name = "YVU420 Invalid pitch", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) - 1, + DIV_ROUND_UP(MAX_WIDTH, 2) }, + } +}, +{ .buffer_created = 1, .name = "YVU420 Different pitches", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) + 1, + DIV_ROUND_UP(MAX_WIDTH, 2) + 7 }, + } +}, +{ .buffer_created = 1, .name = "YVU420 Different buffer offsets/pitches", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .offsets = { MAX_WIDTH, MAX_WIDTH + + MAX_WIDTH * MAX_HEIGHT, MAX_WIDTH + 2 * MAX_WIDTH * MAX_HEIGHT }, + .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) + 1, + DIV_ROUND_UP(MAX_WIDTH, 2) + 7 }, + } +}, +{ .buffer_created = 0, + .name = "YVU420 Modifier set just for plane 0, without DRM_MODE_FB_MODIFIERS", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 }, + .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, + } +}, +{ .buffer_created = 0, + .name = "YVU420 Modifier set just for planes 0, 1, without DRM_MODE_FB_MODIFIERS", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, + .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 }, + .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, + } +}, +{ .buffer_created = 0, + .name = "YVU420 Modifier set just for plane 0, 1, with DRM_MODE_FB_MODIFIERS", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 }, + .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, + } +}, +{ .buffer_created = 1, .name = "YVU420 Valid modifier", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, + AFBC_FORMAT_MOD_SPARSE }, + .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, + } +}, +{ .buffer_created = 0, .name = "YVU420 Different modifiers per plane", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_YTR, + AFBC_FORMAT_MOD_SPARSE }, + .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, + } +}, +{ .buffer_created = 0, .name = "YVU420 Modifier for inexistent plane", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, + .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, + AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE }, + .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, + } +}, +{ .buffer_created = 0, .name = "YUV420_10BIT Invalid modifier(DRM_FORMAT_MOD_LINEAR)", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YUV420_10BIT, + .handles = { 1, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + .modifier = { DRM_FORMAT_MOD_LINEAR, 0, 0 }, + .pitches = { MAX_WIDTH, 0, 0 }, + } +}, +{ .buffer_created = 1, .name = "X0L2 Normal sizes", + .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_X0L2, + .handles = { 1, 0, 0 }, .pitches = { 1200, 0, 0 } + } +}, +{ .buffer_created = 1, .name = "X0L2 Max sizes", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, + .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH, 0, 0 } + } +}, +{ .buffer_created = 0, .name = "X0L2 Invalid pitch", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, + .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH - 1, 0, 0 } + } +}, +{ .buffer_created = 1, .name = "X0L2 Pitch greater than minimum required", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, + .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 } + } +}, +{ .buffer_created = 0, .name = "X0L2 Handle for inexistent plane", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, + .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + .pitches = { 2 * MAX_WIDTH + 1, 0, 0 } + } +}, +{ .buffer_created = 1, + .name = "X0L2 Offset for inexistent plane, without DRM_MODE_FB_MODIFIERS set", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, + .handles = { 1, 0, 0 }, .offsets = { 0, 0, 3 }, + .pitches = { 2 * MAX_WIDTH + 1, 0, 0 } + } +}, +{ .buffer_created = 0, .name = "X0L2 Modifier without DRM_MODE_FB_MODIFIERS set", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, + .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }, + .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 }, + } +}, +{ .buffer_created = 1, .name = "X0L2 Valid modifier", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, + .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }, + .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, + } +}, +{ .buffer_created = 0, .name = "X0L2 Modifier for inexistent plane", + .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, + .pixel_format = DRM_FORMAT_X0L2, .handles = { 1, 0, 0 }, + .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }, + .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 }, + .flags = DRM_MODE_FB_MODIFIERS, + } +}, +}; + +/* + * This struct is intended to provide a way to mocked functions communicate + * with the outer test when it can't be achieved by using its return value. In + * this way, the functions that receive the mocked drm_device, for example, can + * grab a reference to this and actually return something to be used on some + * expectation. + */ +struct drm_framebuffer_test_priv { + struct drm_device dev; + bool buffer_created; + bool buffer_freed; +}; + +static struct drm_framebuffer *fb_create_mock(struct drm_device *dev, + struct drm_file *file_priv, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_framebuffer_test_priv *priv = container_of(dev, typeof(*priv), dev); + + priv->buffer_created = true; + return ERR_PTR(-EINVAL); +} + +static struct drm_mode_config_funcs mock_config_funcs = { + .fb_create = fb_create_mock, +}; + +static int drm_framebuffer_test_init(struct kunit *test) +{ + struct device *parent; + struct drm_framebuffer_test_priv *priv; + struct drm_device *dev; + + parent = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent); + + priv = drm_kunit_helper_alloc_drm_device(test, parent, typeof(*priv), + dev, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + dev = &priv->dev; + + dev->mode_config.min_width = MIN_WIDTH; + dev->mode_config.max_width = MAX_WIDTH; + dev->mode_config.min_height = MIN_HEIGHT; + dev->mode_config.max_height = MAX_HEIGHT; + dev->mode_config.funcs = &mock_config_funcs; + + test->priv = priv; + return 0; +} + +static void drm_test_framebuffer_create(struct kunit *test) +{ + const struct drm_framebuffer_test *params = test->param_value; + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *dev = &priv->dev; + + priv->buffer_created = false; + drm_internal_framebuffer_create(dev, ¶ms->cmd, NULL); + KUNIT_EXPECT_EQ(test, params->buffer_created, priv->buffer_created); +} + +static void drm_framebuffer_test_to_desc(const struct drm_framebuffer_test *t, char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(drm_framebuffer_create, drm_framebuffer_create_cases, + drm_framebuffer_test_to_desc); + +/* Tries to create a framebuffer with modifiers without drm_device supporting it */ +static void drm_test_framebuffer_modifiers_not_supported(struct kunit *test) +{ + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *dev = &priv->dev; + struct drm_framebuffer *fb; + + /* A valid cmd with modifier */ + struct drm_mode_fb_cmd2 cmd = { + .width = MAX_WIDTH, .height = MAX_HEIGHT, + .pixel_format = DRM_FORMAT_ABGR8888, .handles = { 1, 0, 0 }, + .offsets = { UINT_MAX / 2, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, + .flags = DRM_MODE_FB_MODIFIERS, + }; + + priv->buffer_created = false; + dev->mode_config.fb_modifiers_not_supported = 1; + + fb = drm_internal_framebuffer_create(dev, &cmd, NULL); + KUNIT_EXPECT_EQ(test, priv->buffer_created, false); + KUNIT_EXPECT_EQ(test, PTR_ERR(fb), -EINVAL); +} + +/* Parameters for testing drm_framebuffer_check_src_coords function */ +struct drm_framebuffer_check_src_coords_case { + const char *name; + const int expect; + const unsigned int fb_size; + const uint32_t src_x; + const uint32_t src_y; + + /* Deltas to be applied on source */ + const uint32_t dsrc_w; + const uint32_t dsrc_h; +}; + +static const struct drm_framebuffer_check_src_coords_case +drm_framebuffer_check_src_coords_cases[] = { + { .name = "Success: source fits into fb", + .expect = 0, + }, + { .name = "Fail: overflowing fb with x-axis coordinate", + .expect = -ENOSPC, .src_x = 1, .fb_size = UINT_MAX, + }, + { .name = "Fail: overflowing fb with y-axis coordinate", + .expect = -ENOSPC, .src_y = 1, .fb_size = UINT_MAX, + }, + { .name = "Fail: overflowing fb with source width", + .expect = -ENOSPC, .dsrc_w = 1, .fb_size = UINT_MAX - 1, + }, + { .name = "Fail: overflowing fb with source height", + .expect = -ENOSPC, .dsrc_h = 1, .fb_size = UINT_MAX - 1, + }, +}; + +static void drm_test_framebuffer_check_src_coords(struct kunit *test) +{ + const struct drm_framebuffer_check_src_coords_case *params = test->param_value; + const uint32_t src_x = params->src_x; + const uint32_t src_y = params->src_y; + const uint32_t src_w = (params->fb_size << 16) + params->dsrc_w; + const uint32_t src_h = (params->fb_size << 16) + params->dsrc_h; + const struct drm_framebuffer fb = { + .width = params->fb_size, + .height = params->fb_size + }; + int ret; + + ret = drm_framebuffer_check_src_coords(src_x, src_y, src_w, src_h, &fb); + KUNIT_EXPECT_EQ(test, ret, params->expect); +} + +static void +check_src_coords_test_to_desc(const struct drm_framebuffer_check_src_coords_case *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(check_src_coords, drm_framebuffer_check_src_coords_cases, + check_src_coords_test_to_desc); + +/* + * Test if drm_framebuffer_cleanup() really pops out the framebuffer object + * from device's fb_list and decrement the number of framebuffers for that + * device, which is the only things it does. + */ +static void drm_test_framebuffer_cleanup(struct kunit *test) +{ + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *dev = &priv->dev; + struct list_head *fb_list = &dev->mode_config.fb_list; + struct drm_format_info format = { }; + struct drm_framebuffer fb1 = { .dev = dev, .format = &format }; + struct drm_framebuffer fb2 = { .dev = dev, .format = &format }; + + /* This will result on [fb_list] -> fb2 -> fb1 */ + drm_framebuffer_init(dev, &fb1, NULL); + drm_framebuffer_init(dev, &fb2, NULL); + + drm_framebuffer_cleanup(&fb1); + + /* Now fb2 is the only one element on fb_list */ + KUNIT_ASSERT_TRUE(test, list_is_singular(&fb2.head)); + KUNIT_ASSERT_EQ(test, dev->mode_config.num_fb, 1); + + drm_framebuffer_cleanup(&fb2); + + /* Now fb_list is empty */ + KUNIT_ASSERT_TRUE(test, list_empty(fb_list)); + KUNIT_ASSERT_EQ(test, dev->mode_config.num_fb, 0); +} + +/* + * Initialize a framebuffer, lookup its id and test if the returned reference + * matches. + */ +static void drm_test_framebuffer_lookup(struct kunit *test) +{ + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *dev = &priv->dev; + struct drm_format_info format = { }; + struct drm_framebuffer expected_fb = { .dev = dev, .format = &format }; + struct drm_framebuffer *returned_fb; + uint32_t id = 0; + int ret; + + ret = drm_framebuffer_init(dev, &expected_fb, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + id = expected_fb.base.id; + + /* Looking for expected_fb */ + returned_fb = drm_framebuffer_lookup(dev, NULL, id); + KUNIT_EXPECT_PTR_EQ(test, returned_fb, &expected_fb); + drm_framebuffer_put(returned_fb); + + drm_framebuffer_cleanup(&expected_fb); +} + +/* Try to lookup an id that is not linked to a framebuffer */ +static void drm_test_framebuffer_lookup_inexistent(struct kunit *test) +{ + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *dev = &priv->dev; + struct drm_framebuffer *fb; + uint32_t id = 0; + + /* Looking for an inexistent framebuffer */ + fb = drm_framebuffer_lookup(dev, NULL, id); + KUNIT_EXPECT_NULL(test, fb); +} + +/* + * Test if drm_framebuffer_init initializes the framebuffer successfully, + * asserting that its modeset object struct and its refcount are correctly + * set and that strictly one framebuffer is initialized. + */ +static void drm_test_framebuffer_init(struct kunit *test) +{ + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *dev = &priv->dev; + struct drm_format_info format = { }; + struct drm_framebuffer fb1 = { .dev = dev, .format = &format }; + struct drm_framebuffer_funcs funcs = { }; + int ret; + + ret = drm_framebuffer_init(dev, &fb1, &funcs); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* Check if fb->funcs is actually set to the drm_framebuffer_funcs passed on */ + KUNIT_EXPECT_PTR_EQ(test, fb1.funcs, &funcs); + + /* The fb->comm must be set to the current running process */ + KUNIT_EXPECT_STREQ(test, fb1.comm, current->comm); + + /* The fb->base must be successfully initialized */ + KUNIT_EXPECT_NE(test, fb1.base.id, 0); + KUNIT_EXPECT_EQ(test, fb1.base.type, DRM_MODE_OBJECT_FB); + KUNIT_EXPECT_EQ(test, kref_read(&fb1.base.refcount), 1); + KUNIT_EXPECT_PTR_EQ(test, fb1.base.free_cb, &drm_framebuffer_free); + + /* There must be just that one fb initialized */ + KUNIT_EXPECT_EQ(test, dev->mode_config.num_fb, 1); + KUNIT_EXPECT_PTR_EQ(test, dev->mode_config.fb_list.prev, &fb1.head); + KUNIT_EXPECT_PTR_EQ(test, dev->mode_config.fb_list.next, &fb1.head); + + drm_framebuffer_cleanup(&fb1); +} + +/* Try to init a framebuffer without setting its format */ +static void drm_test_framebuffer_init_bad_format(struct kunit *test) +{ + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *dev = &priv->dev; + struct drm_framebuffer fb1 = { .dev = dev, .format = NULL }; + struct drm_framebuffer_funcs funcs = { }; + int ret; + + /* Fails if fb.format isn't set */ + ret = drm_framebuffer_init(dev, &fb1, &funcs); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/* + * Test calling drm_framebuffer_init() passing a framebuffer linked to a + * different drm_device parent from the one passed on the first argument, which + * must fail. + */ +static void drm_test_framebuffer_init_dev_mismatch(struct kunit *test) +{ + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *right_dev = &priv->dev; + struct drm_device *wrong_dev; + struct device *wrong_dev_parent; + struct drm_format_info format = { }; + struct drm_framebuffer fb1 = { .dev = right_dev, .format = &format }; + struct drm_framebuffer_funcs funcs = { }; + int ret; + + wrong_dev_parent = kunit_device_register(test, "drm-kunit-wrong-device-mock"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wrong_dev_parent); + + wrong_dev = __drm_kunit_helper_alloc_drm_device(test, wrong_dev_parent, + sizeof(struct drm_device), + 0, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wrong_dev); + + /* Fails if fb->dev doesn't point to the drm_device passed on first arg */ + ret = drm_framebuffer_init(wrong_dev, &fb1, &funcs); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +static void destroy_free_mock(struct drm_framebuffer *fb) +{ + struct drm_framebuffer_test_priv *priv = container_of(fb->dev, typeof(*priv), dev); + + priv->buffer_freed = true; +} + +static struct drm_framebuffer_funcs framebuffer_funcs_free_mock = { + .destroy = destroy_free_mock, +}; + +/* + * In summary, the drm_framebuffer_free() function must implicitly call + * fb->funcs->destroy() and garantee that the framebufer object is unregistered + * from the drm_device idr pool. + */ +static void drm_test_framebuffer_free(struct kunit *test) +{ + struct drm_framebuffer_test_priv *priv = test->priv; + struct drm_device *dev = &priv->dev; + struct drm_mode_object *obj; + struct drm_framebuffer fb = { + .dev = dev, + .funcs = &framebuffer_funcs_free_mock, + }; + int id, ret; + + priv->buffer_freed = false; + + /* + * Mock a framebuffer that was not unregistered at the moment of the + * drm_framebuffer_free() call. + */ + ret = drm_mode_object_add(dev, &fb.base, DRM_MODE_OBJECT_FB); + KUNIT_ASSERT_EQ(test, ret, 0); + id = fb.base.id; + + drm_framebuffer_free(&fb.base.refcount); + + /* The framebuffer object must be unregistered */ + obj = drm_mode_object_find(dev, NULL, id, DRM_MODE_OBJECT_FB); + KUNIT_EXPECT_PTR_EQ(test, obj, NULL); + KUNIT_EXPECT_EQ(test, fb.base.id, 0); + + /* Test if fb->funcs->destroy() was called */ + KUNIT_EXPECT_EQ(test, priv->buffer_freed, true); +} + +static struct kunit_case drm_framebuffer_tests[] = { + KUNIT_CASE_PARAM(drm_test_framebuffer_check_src_coords, check_src_coords_gen_params), + KUNIT_CASE(drm_test_framebuffer_cleanup), + KUNIT_CASE_PARAM(drm_test_framebuffer_create, drm_framebuffer_create_gen_params), + KUNIT_CASE(drm_test_framebuffer_free), + KUNIT_CASE(drm_test_framebuffer_init), + KUNIT_CASE(drm_test_framebuffer_init_bad_format), + KUNIT_CASE(drm_test_framebuffer_init_dev_mismatch), + KUNIT_CASE(drm_test_framebuffer_lookup), + KUNIT_CASE(drm_test_framebuffer_lookup_inexistent), + KUNIT_CASE(drm_test_framebuffer_modifiers_not_supported), + { } +}; + +static struct kunit_suite drm_framebuffer_test_suite = { + .name = "drm_framebuffer", + .init = drm_framebuffer_test_init, + .test_cases = drm_framebuffer_tests, +}; + +kunit_test_suite(drm_framebuffer_test_suite); + +MODULE_DESCRIPTION("Test cases for the drm_framebuffer functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_gem_shmem_test.c b/drivers/gpu/drm/tests/drm_gem_shmem_test.c new file mode 100644 index 000000000..5e69ff1d1 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_gem_shmem_test.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test suite for GEM objects backed by shmem buffers + * + * Copyright (C) 2023 Red Hat, Inc. + * + * Author: Marco Pagani <marpagan@redhat.com> + */ + +#include <linux/dma-buf.h> +#include <linux/iosys-map.h> +#include <linux/sizes.h> + +#include <kunit/test.h> + +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_gem.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_kunit_helpers.h> + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +#define TEST_SIZE SZ_1M +#define TEST_BYTE 0xae + +/* + * Wrappers to avoid cast warnings when passing action functions + * directly to kunit_add_action(). + */ +KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *); + +KUNIT_DEFINE_ACTION_WRAPPER(sg_free_table_wrapper, sg_free_table, + struct sg_table *); + +KUNIT_DEFINE_ACTION_WRAPPER(drm_gem_shmem_free_wrapper, drm_gem_shmem_free, + struct drm_gem_shmem_object *); + +KUNIT_DEFINE_ACTION_WRAPPER(drm_gem_shmem_unpin_wrapper, drm_gem_shmem_unpin, + struct drm_gem_shmem_object *); + +/* + * Test creating a shmem GEM object backed by shmem buffer. The test + * case succeeds if the GEM object is successfully allocated with the + * shmem file node and object functions attributes set, and the size + * attribute is equal to the correct size. + */ +static void drm_gem_shmem_test_obj_create(struct kunit *test) +{ + struct drm_device *drm_dev = test->priv; + struct drm_gem_shmem_object *shmem; + + shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem); + KUNIT_EXPECT_EQ(test, shmem->base.size, TEST_SIZE); + KUNIT_EXPECT_NOT_NULL(test, shmem->base.filp); + KUNIT_EXPECT_NOT_NULL(test, shmem->base.funcs); + + drm_gem_shmem_free(shmem); +} + +/* + * Test creating a shmem GEM object from a scatter/gather table exported + * via a DMA-BUF. The test case succeed if the GEM object is successfully + * created with the shmem file node attribute equal to NULL and the sgt + * attribute pointing to the scatter/gather table that has been imported. + */ +static void drm_gem_shmem_test_obj_create_private(struct kunit *test) +{ + struct drm_device *drm_dev = test->priv; + struct drm_gem_shmem_object *shmem; + struct drm_gem_object *gem_obj; + struct dma_buf buf_mock; + struct dma_buf_attachment attach_mock; + struct sg_table *sgt; + char *buf; + int ret; + + /* Create a mock scatter/gather table */ + buf = kunit_kzalloc(test, TEST_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, buf); + + sgt = kzalloc_obj(*sgt); + KUNIT_ASSERT_NOT_NULL(test, sgt); + + ret = kunit_add_action_or_reset(test, kfree_wrapper, sgt); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = sg_alloc_table(sgt, 1, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_add_action_or_reset(test, sg_free_table_wrapper, sgt); + KUNIT_ASSERT_EQ(test, ret, 0); + + sg_init_one(sgt->sgl, buf, TEST_SIZE); + + /* + * Map the sgtables otherwise drm_gem_shmem_free will cause a warning on + * debug kernels. + */ + ret = dma_map_sgtable(drm_dev->dev, sgt, DMA_BIDIRECTIONAL, 0); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* Init a mock DMA-BUF */ + buf_mock.size = TEST_SIZE; + attach_mock.dmabuf = &buf_mock; + + gem_obj = drm_gem_shmem_prime_import_sg_table(drm_dev, &attach_mock, sgt); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gem_obj); + KUNIT_EXPECT_EQ(test, gem_obj->size, TEST_SIZE); + KUNIT_EXPECT_NULL(test, gem_obj->filp); + KUNIT_EXPECT_NOT_NULL(test, gem_obj->funcs); + + /* The scatter/gather table will be freed by drm_gem_shmem_free */ + kunit_remove_action(test, sg_free_table_wrapper, sgt); + kunit_remove_action(test, kfree_wrapper, sgt); + + shmem = to_drm_gem_shmem_obj(gem_obj); + KUNIT_EXPECT_PTR_EQ(test, shmem->sgt, sgt); + + drm_gem_shmem_free(shmem); +} + +/* + * Test pinning backing pages for a shmem GEM object. The test case + * succeeds if a suitable number of backing pages are allocated, and + * the pages table counter attribute is increased by one. + */ +static void drm_gem_shmem_test_pin_pages(struct kunit *test) +{ + struct drm_device *drm_dev = test->priv; + struct drm_gem_shmem_object *shmem; + int i, ret; + + shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem); + KUNIT_EXPECT_NULL(test, shmem->pages); + KUNIT_EXPECT_EQ(test, refcount_read(&shmem->pages_use_count), 0); + + ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_gem_shmem_pin(shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_NOT_NULL(test, shmem->pages); + KUNIT_EXPECT_EQ(test, refcount_read(&shmem->pages_use_count), 1); + + for (i = 0; i < (shmem->base.size >> PAGE_SHIFT); i++) + KUNIT_ASSERT_NOT_NULL(test, shmem->pages[i]); + + drm_gem_shmem_unpin(shmem); + KUNIT_EXPECT_NULL(test, shmem->pages); + KUNIT_EXPECT_EQ(test, refcount_read(&shmem->pages_use_count), 0); +} + +/* + * Test creating a virtual mapping for a shmem GEM object. The test + * case succeeds if the backing memory is mapped and the reference + * counter for virtual mapping is increased by one. Moreover, the test + * case writes and then reads a test pattern over the mapped memory. + */ +static void drm_gem_shmem_test_vmap(struct kunit *test) +{ + struct drm_device *drm_dev = test->priv; + struct drm_gem_shmem_object *shmem; + struct iosys_map map; + int ret, i; + + shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem); + KUNIT_EXPECT_NULL(test, shmem->vaddr); + KUNIT_EXPECT_EQ(test, refcount_read(&shmem->vmap_use_count), 0); + + ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_gem_shmem_vmap(shmem, &map); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_NOT_NULL(test, shmem->vaddr); + KUNIT_ASSERT_FALSE(test, iosys_map_is_null(&map)); + KUNIT_EXPECT_EQ(test, refcount_read(&shmem->vmap_use_count), 1); + + iosys_map_memset(&map, 0, TEST_BYTE, TEST_SIZE); + for (i = 0; i < TEST_SIZE; i++) + KUNIT_EXPECT_EQ(test, iosys_map_rd(&map, i, u8), TEST_BYTE); + + drm_gem_shmem_vunmap(shmem, &map); + KUNIT_EXPECT_NULL(test, shmem->vaddr); + KUNIT_EXPECT_EQ(test, refcount_read(&shmem->vmap_use_count), 0); +} + +/* + * Test exporting a scatter/gather table of pinned pages suitable for + * PRIME usage from a shmem GEM object. The test case succeeds if a + * scatter/gather table large enough to accommodate the backing memory + * is successfully exported. + */ +static void drm_gem_shmem_test_get_sg_table(struct kunit *test) +{ + struct drm_device *drm_dev = test->priv; + struct drm_gem_shmem_object *shmem; + struct sg_table *sgt; + struct scatterlist *sg; + unsigned int si, len = 0; + int ret; + + shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem); + + ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_gem_shmem_pin(shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_add_action_or_reset(test, drm_gem_shmem_unpin_wrapper, shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + sgt = drm_gem_shmem_get_sg_table(shmem); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sgt); + KUNIT_EXPECT_NULL(test, shmem->sgt); + + ret = kunit_add_action_or_reset(test, kfree_wrapper, sgt); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_add_action_or_reset(test, sg_free_table_wrapper, sgt); + KUNIT_ASSERT_EQ(test, ret, 0); + + for_each_sgtable_sg(sgt, sg, si) { + KUNIT_EXPECT_NOT_NULL(test, sg); + len += sg->length; + } + + KUNIT_EXPECT_GE(test, len, TEST_SIZE); +} + +/* + * Test pinning pages and exporting a scatter/gather table suitable for + * driver usage from a shmem GEM object. The test case succeeds if the + * backing pages are pinned and a scatter/gather table large enough to + * accommodate the backing memory is successfully exported. + */ +static void drm_gem_shmem_test_get_pages_sgt(struct kunit *test) +{ + struct drm_device *drm_dev = test->priv; + struct drm_gem_shmem_object *shmem; + struct sg_table *sgt; + struct scatterlist *sg; + unsigned int si, ret, len = 0; + + shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem); + + ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* The scatter/gather table will be freed by drm_gem_shmem_free */ + sgt = drm_gem_shmem_get_pages_sgt(shmem); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sgt); + KUNIT_ASSERT_NOT_NULL(test, shmem->pages); + KUNIT_EXPECT_EQ(test, refcount_read(&shmem->pages_use_count), 1); + KUNIT_EXPECT_PTR_EQ(test, sgt, shmem->sgt); + + for_each_sgtable_sg(sgt, sg, si) { + KUNIT_EXPECT_NOT_NULL(test, sg); + len += sg->length; + } + + KUNIT_EXPECT_GE(test, len, TEST_SIZE); +} + +/* + * Test updating the madvise state of a shmem GEM object. The test + * case checks that the function for setting madv updates it only if + * its current value is greater or equal than zero and returns false + * if it has a negative value. + */ +static void drm_gem_shmem_test_madvise(struct kunit *test) +{ + struct drm_device *drm_dev = test->priv; + struct drm_gem_shmem_object *shmem; + int ret; + + shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem); + KUNIT_ASSERT_EQ(test, shmem->madv, 0); + + ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_gem_shmem_madvise(shmem, 1); + KUNIT_EXPECT_TRUE(test, ret); + KUNIT_ASSERT_EQ(test, shmem->madv, 1); + + /* Set madv to a negative value */ + ret = drm_gem_shmem_madvise(shmem, -1); + KUNIT_EXPECT_FALSE(test, ret); + KUNIT_ASSERT_EQ(test, shmem->madv, -1); + + /* Check that madv cannot be set back to a positive value */ + ret = drm_gem_shmem_madvise(shmem, 0); + KUNIT_EXPECT_FALSE(test, ret); + KUNIT_ASSERT_EQ(test, shmem->madv, -1); +} + +/* + * Test purging a shmem GEM object. First, assert that a newly created + * shmem GEM object is not purgeable. Then, set madvise to a positive + * value and call drm_gem_shmem_get_pages_sgt() to pin and dma-map the + * backing pages. Finally, assert that the shmem GEM object is now + * purgeable and purge it. + */ +static void drm_gem_shmem_test_purge(struct kunit *test) +{ + struct drm_device *drm_dev = test->priv; + struct drm_gem_shmem_object *shmem; + struct sg_table *sgt; + int ret; + + shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem); + + ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_gem_shmem_is_purgeable(shmem); + KUNIT_EXPECT_FALSE(test, ret); + + ret = drm_gem_shmem_madvise(shmem, 1); + KUNIT_EXPECT_TRUE(test, ret); + + /* The scatter/gather table will be freed by drm_gem_shmem_free */ + sgt = drm_gem_shmem_get_pages_sgt(shmem); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sgt); + + ret = drm_gem_shmem_is_purgeable(shmem); + KUNIT_EXPECT_TRUE(test, ret); + + ret = drm_gem_shmem_purge(shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_NULL(test, shmem->pages); + KUNIT_EXPECT_NULL(test, shmem->sgt); + KUNIT_EXPECT_EQ(test, shmem->madv, -1); +} + +static int drm_gem_shmem_test_init(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm_dev; + int ret; + + /* Allocate a parent device */ + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + /* + * Set the DMA mask to 64-bits to avoid intermittent failures calling + * drm_gem_shmem_get_pages_sgt(). + */ + ret = dma_set_mask(dev, DMA_BIT_MASK(64)); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* + * The DRM core will automatically initialize the GEM core and create + * a DRM Memory Manager object which provides an address space pool + * for GEM objects allocation. + */ + drm_dev = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm_dev), + 0, DRIVER_GEM); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm_dev); + + test->priv = drm_dev; + + return 0; +} + +static struct kunit_case drm_gem_shmem_test_cases[] = { + KUNIT_CASE(drm_gem_shmem_test_obj_create), + KUNIT_CASE(drm_gem_shmem_test_obj_create_private), + KUNIT_CASE(drm_gem_shmem_test_pin_pages), + KUNIT_CASE(drm_gem_shmem_test_vmap), + KUNIT_CASE(drm_gem_shmem_test_get_sg_table), + KUNIT_CASE(drm_gem_shmem_test_get_pages_sgt), + KUNIT_CASE(drm_gem_shmem_test_madvise), + KUNIT_CASE(drm_gem_shmem_test_purge), + {} +}; + +static struct kunit_suite drm_gem_shmem_suite = { + .name = "drm_gem_shmem", + .init = drm_gem_shmem_test_init, + .test_cases = drm_gem_shmem_test_cases +}; + +kunit_test_suite(drm_gem_shmem_suite); + +MODULE_DESCRIPTION("KUnit test suite for GEM objects backed by shmem buffers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c new file mode 100644 index 000000000..353a261d4 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -0,0 +1,3489 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Kunit test for drm_hdmi_state_helper functions + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_connector.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include <drm/display/drm_hdmi_helper.h> +#include <drm/display/drm_hdmi_state_helper.h> + +#include "../drm_crtc_internal.h" + +#include <kunit/test.h> + +#include "drm_kunit_edid.h" + +struct drm_atomic_helper_connector_hdmi_priv { + struct drm_device drm; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_encoder encoder; + struct drm_connector connector; + + const void *current_edid; + size_t current_edid_len; + + int hdmi_update_failures; +}; + +#define connector_to_priv(c) \ + container_of_const(c, struct drm_atomic_helper_connector_hdmi_priv, connector) + +#define encoder_to_priv(e) \ + container_of_const(e, struct drm_atomic_helper_connector_hdmi_priv, encoder) + +static struct drm_display_mode *find_preferred_mode(struct drm_connector *connector) +{ + struct drm_device *drm = connector->dev; + struct drm_display_mode *mode, *preferred; + + mutex_lock(&drm->mode_config.mutex); + preferred = list_first_entry_or_null(&connector->modes, struct drm_display_mode, head); + list_for_each_entry(mode, &connector->modes, head) + if (mode->type & DRM_MODE_TYPE_PREFERRED) + preferred = mode; + mutex_unlock(&drm->mode_config.mutex); + + return preferred; +} + +static struct drm_display_mode *find_420_only_mode(struct drm_connector *connector) +{ + struct drm_device *drm = connector->dev; + struct drm_display_mode *mode; + + mutex_lock(&drm->mode_config.mutex); + list_for_each_entry(mode, &connector->modes, head) { + if (drm_mode_is_420_only(&connector->display_info, mode)) { + mutex_unlock(&drm->mode_config.mutex); + return mode; + } + } + mutex_unlock(&drm->mode_config.mutex); + + return NULL; +} + +static struct drm_display_mode *find_420_also_mode(struct drm_connector *connector) +{ + struct drm_device *drm = connector->dev; + struct drm_display_mode *mode; + + mutex_lock(&drm->mode_config.mutex); + list_for_each_entry(mode, &connector->modes, head) { + if (drm_mode_is_420_also(&connector->display_info, mode)) { + mutex_unlock(&drm->mode_config.mutex); + return mode; + } + } + mutex_unlock(&drm->mode_config.mutex); + + return NULL; +} + +static int set_connector_edid(struct kunit *test, struct drm_connector *connector, + const void *edid, size_t edid_len) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv = + connector_to_priv(connector); + struct drm_device *drm = connector->dev; + int ret; + + priv->current_edid = edid; + priv->current_edid_len = edid_len; + + mutex_lock(&drm->mode_config.mutex); + ret = connector->funcs->fill_modes(connector, 4096, 4096); + mutex_unlock(&drm->mode_config.mutex); + + return ret; +} + +static int accept_infoframe_clear_infoframe(struct drm_connector *connector) +{ + return 0; +} + +static int accept_infoframe_write_infoframe(struct drm_connector *connector, + const u8 *buffer, size_t len) +{ + return 0; +} + +static const struct drm_connector_hdmi_funcs dummy_connector_hdmi_funcs = { + .avi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, + .hdmi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, +}; + +static enum drm_mode_status +reject_connector_tmds_char_rate_valid(const struct drm_connector *connector, + const struct drm_display_mode *mode, + unsigned long long tmds_rate) +{ + return MODE_BAD; +} + +static const struct drm_connector_hdmi_funcs reject_connector_hdmi_funcs = { + .tmds_char_rate_valid = reject_connector_tmds_char_rate_valid, + .avi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, + .hdmi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, +}; + +static enum drm_mode_status +reject_100mhz_connector_tmds_char_rate_valid(const struct drm_connector *connector, + const struct drm_display_mode *mode, + unsigned long long tmds_rate) +{ + return (tmds_rate > 100ULL * 1000 * 1000) ? MODE_BAD : MODE_OK; +} + +static const struct drm_connector_hdmi_funcs reject_100mhz_connector_hdmi_funcs = { + .tmds_char_rate_valid = reject_100mhz_connector_tmds_char_rate_valid, + .avi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, + .hdmi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, +}; + +static int dummy_connector_get_modes(struct drm_connector *connector) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv = + connector_to_priv(connector); + const struct drm_edid *edid; + unsigned int num_modes; + + edid = drm_edid_alloc(priv->current_edid, priv->current_edid_len); + if (!edid) + return -EINVAL; + + drm_edid_connector_update(connector, edid); + num_modes = drm_edid_connector_add_modes(connector); + + drm_edid_free(edid); + + return num_modes; +} + +static const struct drm_connector_helper_funcs dummy_connector_helper_funcs = { + .atomic_check = drm_atomic_helper_connector_hdmi_check, + .get_modes = dummy_connector_get_modes, + .mode_valid = drm_hdmi_connector_mode_valid, +}; + +static void dummy_hdmi_connector_reset(struct drm_connector *connector) +{ + drm_atomic_helper_connector_reset(connector); + __drm_atomic_helper_connector_hdmi_state_init(connector, connector->state); +} + +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, + .fill_modes = drm_helper_probe_single_connector_modes, + .reset = dummy_hdmi_connector_reset, +}; + +static void test_encoder_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_commit *state) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv = + encoder_to_priv(encoder); + int ret; + + ret = drm_atomic_helper_connector_hdmi_update_infoframes(&priv->connector, state); + if (ret) + priv->hdmi_update_failures++; +} + +static const struct drm_encoder_helper_funcs test_encoder_helper_funcs = { + .atomic_enable = test_encoder_atomic_enable, +}; + +static +struct drm_atomic_helper_connector_hdmi_priv * +__connector_hdmi_init(struct kunit *test, + unsigned int formats, + unsigned int max_bpc, + const struct drm_connector_hdmi_funcs *hdmi_funcs, + const void *edid_data, size_t edid_len) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector *conn; + struct drm_encoder *enc; + 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_helper_connector_hdmi_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); + + enc = &priv->encoder; + ret = drmm_encoder_init(drm, enc, NULL, DRM_MODE_ENCODER_TMDS, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + enc->possible_crtcs = drm_crtc_mask(priv->crtc); + + conn = &priv->connector; + conn->ycbcr_420_allowed = !!(formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420)); + + ret = drmm_connector_hdmi_init(drm, conn, + "Vendor", "Product", + &dummy_connector_funcs, + hdmi_funcs, + DRM_MODE_CONNECTOR_HDMIA, + NULL, + formats, + max_bpc); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_connector_helper_add(conn, &dummy_connector_helper_funcs); + drm_connector_attach_encoder(conn, enc); + + drm_mode_config_reset(drm); + + if (edid_data && edid_len) { + ret = set_connector_edid(test, &priv->connector, edid_data, edid_len); + KUNIT_ASSERT_GT(test, ret, 0); + } + + return priv; +} + +#define drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, formats, max_bpc, funcs, edid) \ + __connector_hdmi_init(test, formats, max_bpc, funcs, edid, ARRAY_SIZE(edid)) + +static +struct drm_atomic_helper_connector_hdmi_priv * +drm_kunit_helper_connector_hdmi_init(struct kunit *test, + unsigned int formats, + unsigned int max_bpc) +{ + return drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + formats, + max_bpc, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz); +} + +/* + * Test that if we change the RGB quantization property to a different + * value, we trigger a mode change on the connector's CRTC, which will + * in turn disable/enable the connector. + */ +static void drm_test_check_broadcast_rgb_crtc_mode_changed(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + 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, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + old_conn_state = drm_atomic_get_old_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state); + + new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_FULL; + + KUNIT_ASSERT_NE(test, + old_conn_state->hdmi.broadcast_rgb, + new_conn_state->hdmi.broadcast_rgb); + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + new_conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + KUNIT_EXPECT_EQ(test, new_conn_state->hdmi.broadcast_rgb, DRM_HDMI_BROADCAST_RGB_FULL); + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + KUNIT_EXPECT_TRUE(test, crtc_state->mode_changed); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if we set the RGB quantization property to the same value, + * we don't trigger a mode change on the connector's CRTC and leave the + * connector unaffected. + */ +static void drm_test_check_broadcast_rgb_crtc_mode_not_changed(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + 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, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + old_conn_state = drm_atomic_get_old_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state); + + new_conn_state->hdmi.broadcast_rgb = old_conn_state->hdmi.broadcast_rgb; + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + old_conn_state = drm_atomic_get_old_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state); + + new_conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + KUNIT_EXPECT_EQ(test, + old_conn_state->hdmi.broadcast_rgb, + new_conn_state->hdmi.broadcast_rgb); + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + KUNIT_EXPECT_FALSE(test, crtc_state->mode_changed); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that for an HDMI connector, with an HDMI monitor, if the + * Broadcast RGB property is set to auto with a mode that isn't the + * VIC-1 mode, we will get a limited RGB Quantization Range. + */ +static void drm_test_check_broadcast_rgb_auto_cea_mode(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_NE(test, drm_match_cea_mode(preferred), 1); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + conn_state = drm_atomic_get_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, + conn_state->hdmi.broadcast_rgb, + DRM_HDMI_BROADCAST_RGB_AUTO); + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_EXPECT_TRUE(test, conn_state->hdmi.is_limited_range); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that for an HDMI connector, with an HDMI monitor, if the + * Broadcast RGB property is set to auto with a VIC-1 mode, we will get + * a full RGB Quantization Range. + */ +static void drm_test_check_broadcast_rgb_auto_cea_mode_vic_1(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + conn = &priv->connector; + KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi); + + drm_modeset_acquire_init(&ctx, 0); + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 1); + KUNIT_ASSERT_NOT_NULL(test, mode); + + crtc = priv->crtc; + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + mode, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + conn_state = drm_atomic_get_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, + conn_state->hdmi.broadcast_rgb, + DRM_HDMI_BROADCAST_RGB_AUTO); + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_EXPECT_FALSE(test, conn_state->hdmi.is_limited_range); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that for an HDMI connector, with an HDMI monitor, if the + * Broadcast RGB property is set to full with a mode that isn't the + * VIC-1 mode, we will get a full RGB Quantization Range. + */ +static void drm_test_check_broadcast_rgb_full_cea_mode(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_NE(test, drm_match_cea_mode(preferred), 1); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + conn_state = drm_atomic_get_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_FULL; + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, + conn_state->hdmi.broadcast_rgb, + DRM_HDMI_BROADCAST_RGB_FULL); + + KUNIT_EXPECT_FALSE(test, conn_state->hdmi.is_limited_range); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that for an HDMI connector, with an HDMI monitor, if the + * Broadcast RGB property is set to full with a VIC-1 mode, we will get + * a full RGB Quantization Range. + */ +static void drm_test_check_broadcast_rgb_full_cea_mode_vic_1(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + conn = &priv->connector; + KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi); + + drm_modeset_acquire_init(&ctx, 0); + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 1); + KUNIT_ASSERT_NOT_NULL(test, mode); + + crtc = priv->crtc; + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + mode, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + conn_state = drm_atomic_get_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_FULL; + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, + conn_state->hdmi.broadcast_rgb, + DRM_HDMI_BROADCAST_RGB_FULL); + + KUNIT_EXPECT_FALSE(test, conn_state->hdmi.is_limited_range); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that for an HDMI connector, with an HDMI monitor, if the + * Broadcast RGB property is set to limited with a mode that isn't the + * VIC-1 mode, we will get a limited RGB Quantization Range. + */ +static void drm_test_check_broadcast_rgb_limited_cea_mode(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_NE(test, drm_match_cea_mode(preferred), 1); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + conn_state = drm_atomic_get_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED; + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, + conn_state->hdmi.broadcast_rgb, + DRM_HDMI_BROADCAST_RGB_LIMITED); + + KUNIT_EXPECT_TRUE(test, conn_state->hdmi.is_limited_range); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that for an HDMI connector, with an HDMI monitor, if the + * Broadcast RGB property is set to limited with a VIC-1 mode, we will + * get a limited RGB Quantization Range. + */ +static void drm_test_check_broadcast_rgb_limited_cea_mode_vic_1(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + conn = &priv->connector; + KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi); + + drm_modeset_acquire_init(&ctx, 0); + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 1); + KUNIT_ASSERT_NOT_NULL(test, mode); + + crtc = priv->crtc; + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + mode, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + conn_state = drm_atomic_get_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED; + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, + conn_state->hdmi.broadcast_rgb, + DRM_HDMI_BROADCAST_RGB_LIMITED); + + KUNIT_EXPECT_TRUE(test, conn_state->hdmi.is_limited_range); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that for an HDMI connector, with an HDMI monitor, we will + * get a limited RGB Quantization Range with a YUV420 mode, no + * matter what the value of the Broadcast RGB property is set to. + */ +static void drm_test_check_broadcast_rgb_cea_mode_yuv420(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + enum drm_hdmi_broadcast_rgb broadcast_rgb; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_atomic_commit *state; + struct drm_display_mode *mode; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + broadcast_rgb = *(enum drm_hdmi_broadcast_rgb *)test->param_value; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), + 8, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi); + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 95); + KUNIT_ASSERT_NOT_NULL(test, mode); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, + mode, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_conn_state: + conn_state = drm_atomic_get_connector_state(state, conn); + if (PTR_ERR(conn_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + conn_state->hdmi.broadcast_rgb = broadcast_rgb; + + ret = drm_atomic_check_only(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, conn_state->hdmi.broadcast_rgb, broadcast_rgb); + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + + KUNIT_EXPECT_TRUE(test, conn_state->hdmi.is_limited_range); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static const enum drm_hdmi_broadcast_rgb check_broadcast_rgb_cea_mode_yuv420_tests[] = { + DRM_HDMI_BROADCAST_RGB_AUTO, + DRM_HDMI_BROADCAST_RGB_FULL, + DRM_HDMI_BROADCAST_RGB_LIMITED, +}; + +static void +check_broadcast_rgb_cea_mode_yuv420_desc(const enum drm_hdmi_broadcast_rgb *broadcast_rgb, + char *desc) +{ + sprintf(desc, "%s", drm_hdmi_connector_get_broadcast_rgb_name(*broadcast_rgb)); +} + +KUNIT_ARRAY_PARAM(check_broadcast_rgb_cea_mode_yuv420, + check_broadcast_rgb_cea_mode_yuv420_tests, + check_broadcast_rgb_cea_mode_yuv420_desc); + +/* + * Test that if we change the maximum bpc property to a different value, + * we trigger a mode change on the connector's CRTC, which will in turn + * disable/enable the connector. + */ +static void drm_test_check_output_bpc_crtc_mode_changed(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 10, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + 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, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + old_conn_state = drm_atomic_get_old_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state); + + new_conn_state->max_requested_bpc = 8; + + KUNIT_ASSERT_NE(test, + old_conn_state->max_requested_bpc, + new_conn_state->max_requested_bpc); + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + old_conn_state = drm_atomic_get_old_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state); + + new_conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + KUNIT_ASSERT_NE(test, + old_conn_state->hdmi.output_bpc, + new_conn_state->hdmi.output_bpc); + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + KUNIT_EXPECT_TRUE(test, crtc_state->mode_changed); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if we set the output bpc property to the same value, we + * don't trigger a mode change on the connector's CRTC and leave the + * connector unaffected. + */ +static void drm_test_check_output_bpc_crtc_mode_not_changed(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 10, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + 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, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + old_conn_state = drm_atomic_get_old_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state); + + KUNIT_ASSERT_EQ(test, + new_conn_state->hdmi.output_bpc, + old_conn_state->hdmi.output_bpc); + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + old_conn_state = drm_atomic_get_old_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_conn_state); + + new_conn_state = drm_atomic_get_new_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + KUNIT_EXPECT_EQ(test, + old_conn_state->hdmi.output_bpc, + new_conn_state->hdmi.output_bpc); + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + KUNIT_EXPECT_FALSE(test, crtc_state->mode_changed); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if we have an HDMI connector but a !HDMI display, we always + * output RGB with 8 bpc. + */ +static void drm_test_check_output_bpc_dvi(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_dvi_1080p); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_FALSE(test, info->is_hdmi); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that when doing a commit which would use RGB 8bpc, the TMDS + * clock rate stored in the connector state is equal to the mode clock + */ +static void drm_test_check_tmds_char_rate_rgb_8bpc(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_bpc, 8); + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1000); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that when doing a commit which would use RGB 10bpc, the TMDS + * clock rate stored in the connector state is equal to 1.25 times the + * mode pixel clock + */ +static void drm_test_check_tmds_char_rate_rgb_10bpc(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 10, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_bpc, 10); + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1250); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that when doing a commit which would use RGB 12bpc, the TMDS + * clock rate stored in the connector state is equal to 1.5 times the + * mode pixel clock + */ +static void drm_test_check_tmds_char_rate_rgb_12bpc(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_bpc, 12); + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1500); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if we filter a rate through our hook, it's indeed rejected + * by the whole atomic_check logic. + * + * We do so by first doing a commit on the pipeline to make sure that it + * works, change the HDMI helpers pointer, and then try the same commit + * again to see if it fails as it should. + */ +static void drm_test_check_hdmi_funcs_reject_rate(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_crtc_state *crtc_state; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + /* You shouldn't be doing that at home. */ + conn->hdmi.funcs = &reject_connector_hdmi_funcs; + + 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, crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + crtc_state->connectors_changed = true; + + ret = drm_atomic_check_only(state); + KUNIT_EXPECT_LT(test, ret, 0); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if: + * - We have an HDMI connector supporting RGB only + * - The chosen mode has a TMDS character rate higher than the display + * supports in RGB/12bpc + * - The chosen mode has a TMDS character rate lower than the display + * supports in RGB/10bpc. + * + * Then we will pick the latter, and the computed TMDS character rate + * will be equal to 1.25 times the mode pixel clock. + */ +static void drm_test_check_max_tmds_rate_bpc_fallback_rgb(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); + + rate = drm_hdmi_compute_mode_clock(preferred, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 10); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1250); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if: + * - We have an HDMI connector and a display supporting both RGB and YUV420 + * - The chosen mode can be supported in YUV420 output format only + * - The chosen mode has a TMDS character rate higher than the display + * supports in YUV420/12bpc + * - The chosen mode has a TMDS character rate lower than the display + * supports in YUV420/10bpc. + * + * Then we will pick the latter, and the computed TMDS character rate + * will be equal to 1.25 * 0.5 times the mode pixel clock. + */ +static void drm_test_check_max_tmds_rate_bpc_fallback_yuv420(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *yuv420_only_mode; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + KUNIT_ASSERT_TRUE(test, conn->ycbcr_420_allowed); + + yuv420_only_mode = drm_kunit_display_mode_from_cea_vic(test, drm, 95); + KUNIT_ASSERT_NOT_NULL(test, yuv420_only_mode); + KUNIT_ASSERT_TRUE(test, drm_mode_is_420_only(info, yuv420_only_mode)); + + rate = drm_hdmi_compute_mode_clock(yuv420_only_mode, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); + + rate = drm_hdmi_compute_mode_clock(yuv420_only_mode, 10, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, + yuv420_only_mode, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 10); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, yuv420_only_mode->clock * 625); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if: + * - We have an HDMI connector supporting both RGB and YUV422 and up to + * 12 bpc + * - The chosen mode has a TMDS character rate higher than the display + * supports in RGB/12bpc but lower than the display supports in + * RGB/10bpc + * - The chosen mode has a TMDS character rate lower than the display + * supports in YUV422/12bpc. + * - The HDMI connector state's color format property is unset (i.e. AUTO) + * + * Then we will prefer to keep the RGB format with a lower bpc over + * picking YUV422. + */ +static void drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv422(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); + + rate = drm_hdmi_compute_mode_clock(preferred, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); + + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + KUNIT_ASSERT_EQ(test, conn_state->color_format, DRM_CONNECTOR_COLOR_FORMAT_AUTO); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 10); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if: + * - We have an HDMI connector supporting both RGB and YUV420 and up to + * 12 bpc + * - The chosen mode has a TMDS character rate higher than the display + * supports in RGB/10bpc but lower than the display supports in + * RGB/8bpc + * - The chosen mode has a TMDS character rate lower than the display + * supports in YUV420/12bpc. + * - The HDMI connector state's color format property is unset (i.e. AUTO) + * + * Then we will prefer to keep the RGB format with a lower bpc over + * picking YUV420. + */ +static void drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv420(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + KUNIT_ASSERT_TRUE(test, conn->ycbcr_420_allowed); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); + KUNIT_ASSERT_TRUE(test, drm_mode_is_420_also(info, preferred)); + + rate = drm_hdmi_compute_mode_clock(preferred, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + rate = drm_hdmi_compute_mode_clock(preferred, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); + + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, + preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + KUNIT_ASSERT_EQ(test, conn_state->color_format, DRM_CONNECTOR_COLOR_FORMAT_AUTO); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if a driver supports only RGB, but the chosen mode can be + * supported by the screen only in YUV420 output format, we end up with + * unsuccessful fallback attempts. + */ +static void drm_test_check_driver_unsupported_fallback_yuv420(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_info *info; + struct drm_display_mode *preferred, *yuv420_only_mode; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_FALSE(test, conn->ycbcr_420_allowed); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_ASSERT_FALSE(test, drm_mode_is_420_also(info, preferred)); + + yuv420_only_mode = drm_kunit_display_mode_from_cea_vic(test, drm, 95); + KUNIT_ASSERT_NOT_NULL(test, yuv420_only_mode); + KUNIT_ASSERT_TRUE(test, drm_mode_is_420_only(info, yuv420_only_mode)); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, + preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_crtc_state: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, yuv420_only_mode); + KUNIT_EXPECT_EQ(test, ret, 0); + + ret = drm_atomic_check_only(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_LT(test, ret, 0); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if a driver and screen supports RGB and YUV formats, and we + * try to set the VIC 1 mode, we end up with 8bpc RGB even if we could + * have had a higher bpc. + */ +static void drm_test_check_output_bpc_format_vic_1(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *mode; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + + mode = drm_kunit_display_mode_from_cea_vic(test, drm, 1); + KUNIT_ASSERT_NOT_NULL(test, mode); + + /* + * NOTE: We can't use drm_hdmi_compute_mode_clock() + * here because we're trying to get the rate of an invalid + * configuration. + * + * Thus, we have to calculate the rate by hand. + */ + rate = mode->clock * 1500; + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + + crtc = priv->crtc; + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + mode, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if a driver supports only RGB but the screen also supports + * YUV formats, we only end up with an RGB format. + */ +static void drm_test_check_output_bpc_format_driver_rgb_only(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + /* + * We're making sure that YUV422 would be the preferred option + * here: we're always favouring higher bpc, we can't have RGB + * because the TMDS character rate exceeds the maximum supported + * by the display, and YUV422 works for that display. + * + * But since the driver only supports RGB, we should fallback to + * a lower bpc with RGB. + */ + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); + + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_EXPECT_LT(test, conn_state->hdmi.output_bpc, 12); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if a screen supports only RGB but the driver also supports + * YUV formats, we only end up with an RGB format. + */ +static void drm_test_check_output_bpc_format_display_rgb_only(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + /* + * We're making sure that YUV422 would be the preferred option + * here: we're always favouring higher bpc, we can't have RGB + * because the TMDS character rate exceeds the maximum supported + * by the display, and YUV422 works for that display. + * + * But since the display only supports RGB, we should fallback to + * a lower bpc with RGB. + */ + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); + + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_EXPECT_LT(test, conn_state->hdmi.output_bpc, 12); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if a display supports higher bpc but the driver only + * supports 8 bpc, we only end up with 8 bpc even if we could have had a + * higher bpc. + */ +static void drm_test_check_output_bpc_format_driver_8bpc_only(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + /* + * We're making sure that we have headroom on the TMDS character + * clock to actually use 12bpc. + */ + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that if a driver supports higher bpc but the display only + * supports 8 bpc, we only end up with 8 bpc even if we could have had a + * higher bpc. + */ +static void drm_test_check_output_bpc_format_display_8bpc_only(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + unsigned long long rate; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_340mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + info = &conn->display_info; + KUNIT_ASSERT_TRUE(test, info->is_hdmi); + KUNIT_ASSERT_GT(test, info->max_tmds_clock, 0); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + /* + * We're making sure that we have headroom on the TMDS character + * clock to actually use 12bpc. + */ + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); + KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* Test that atomic check succeeds when disabling a connector. */ +static void drm_test_check_disable_connector(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + conn = &priv->connector; + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm = &priv->drm; + crtc = priv->crtc; + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + 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, crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + crtc_state->active = false; + ret = drm_atomic_set_mode_for_crtc(crtc_state, NULL); + KUNIT_EXPECT_EQ(test, ret, 0); + + conn_state = drm_atomic_get_connector_state(state, conn); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + ret = drm_atomic_set_crtc_for_connector(conn_state, NULL); + KUNIT_EXPECT_EQ(test, ret, 0); + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +struct color_format_test_param { + enum drm_connector_color_format fmt; + enum drm_output_color_format expected; + int expected_ret; + const char *desc; +}; + +/* Test that if: + * - an HDMI connector supports RGB, YUV444, YUV422, and YUV420 + * - the display supports RGB, YUV444, YUV422, and YUV420 + * - the "color format" property is set + * then, for the preferred mode, for a given "color format" option: + * - DRM_CONNECTOR_COLOR_FORMAT_AUTO results in an output format of RGB + * - DRM_CONNECTOR_COLOR_FORMAT_YCBCR422 results in an output format of YUV422 + * - DRM_CONNECTOR_COLOR_FORMAT_YCBCR420 results in an output format of YUV420 + * - DRM_CONNECTOR_COLOR_FORMAT_YCBCR444 results in an output format of YUV444 + * - DRM_CONNECTOR_COLOR_FORMAT_RGB results in an HDMI output format of RGB + */ +static void drm_test_check_hdmi_color_format(struct kunit *test) +{ + const struct color_format_test_param *param = test->param_value; + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector_state *conn_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_info *info; + struct drm_display_mode *preferred; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + KUNIT_ASSERT_TRUE(test, priv->connector.ycbcr_420_allowed); + + info = &priv->connector.display_info; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); + preferred = find_preferred_mode(&priv->connector); + KUNIT_ASSERT_TRUE(test, drm_mode_is_420(info, preferred)); + + state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + conn_state = drm_atomic_get_connector_state(state, &priv->connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + conn_state->color_format = param->fmt; + + ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc); + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, preferred); + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state->enable = true; + crtc_state->active = true; + + ret = drm_atomic_check_only(state); + KUNIT_EXPECT_EQ(test, ret, param->expected_ret); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, param->expected); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static const struct color_format_test_param hdmi_color_format_params[] = { + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_AUTO, + .expected = DRM_OUTPUT_COLOR_FORMAT_RGB444, + .expected_ret = 0, + .desc = "AUTO -> RGB" + }, + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_YCBCR422, + .expected = DRM_OUTPUT_COLOR_FORMAT_YCBCR422, + .expected_ret = 0, + .desc = "YCBCR422 -> YUV422" + }, + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_YCBCR420, + .expected = DRM_OUTPUT_COLOR_FORMAT_YCBCR420, + .expected_ret = 0, + .desc = "YCBCR420 -> YUV420" + }, + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_YCBCR444, + .expected = DRM_OUTPUT_COLOR_FORMAT_YCBCR444, + .expected_ret = 0, + .desc = "YCBCR444 -> YUV444" + }, + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_RGB444, + .expected = DRM_OUTPUT_COLOR_FORMAT_RGB444, + .expected_ret = 0, + .desc = "RGB -> RGB" + }, +}; + +KUNIT_ARRAY_PARAM_DESC(check_hdmi_color_format, hdmi_color_format_params, desc); + +/* Test that if: + * - the HDMI connector supports RGB, YUV422, YUV420, and YUV444 + * - the display has a YUV420-only mode + * - the "color format" property is explicitly set (i.e. !AUTO) + * then: + * - color format DRM_CONNECTOR_COLOR_FORMAT_RGB444 will fail + * drm_atomic_check_only for the YUV420-only mode with -EINVAL + * - color format DRM_CONNECTOR_COLOR_FORMAT_YCBCR444 will fail + * drm_atomic_check_only for the YUV420-only mode with -EINVAL + * - color format DRM_CONNECTOR_COLOR_FORMAT_YCBCR422 will fail + * drm_atomic_check_only for the YUV420-only mode with -EINVAL + * - color format DRM_CONNECTOR_COLOR_FORMAT_YCBCR420 passes + * drm_atomic_check_only for the YUV420-only mode + */ +static void drm_test_check_hdmi_color_format_420_only(struct kunit *test) +{ + const struct color_format_test_param *param = test->param_value; + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector_state *conn_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *dank; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 12, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm_modeset_acquire_init(&ctx, 0); + + dank = find_420_only_mode(&priv->connector); + KUNIT_ASSERT_NOT_NULL(test, dank); + + state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + conn_state = drm_atomic_get_connector_state(state, &priv->connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + conn_state->color_format = param->fmt; + + ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc); + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, dank); + KUNIT_ASSERT_EQ(test, ret, 0); + + crtc_state->enable = true; + crtc_state->active = true; + + ret = drm_atomic_check_only(state); + KUNIT_EXPECT_EQ(test, ret, param->expected_ret); + if (!param->expected_ret) + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, param->expected); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +}; + +static const struct color_format_test_param hdmi_color_format_420_only_params[] = { + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_RGB444, + .expected = DRM_OUTPUT_COLOR_FORMAT_RGB444, + .expected_ret = -EINVAL, + .desc = "RGB should fail" + }, + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_YCBCR444, + .expected = DRM_OUTPUT_COLOR_FORMAT_YCBCR444, + .expected_ret = -EINVAL, + .desc = "YUV444 should fail" + }, + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_YCBCR422, + .expected = DRM_OUTPUT_COLOR_FORMAT_YCBCR422, + .expected_ret = -EINVAL, + .desc = "YUV422 should fail" + }, + { + .fmt = DRM_CONNECTOR_COLOR_FORMAT_YCBCR420, + .expected = DRM_OUTPUT_COLOR_FORMAT_YCBCR420, + .expected_ret = 0, + .desc = "YUV420 should work" + }, +}; + +KUNIT_ARRAY_PARAM_DESC(check_hdmi_color_format_420_only, + hdmi_color_format_420_only_params, desc); + +static struct kunit_case drm_atomic_helper_connector_hdmi_check_tests[] = { + KUNIT_CASE(drm_test_check_broadcast_rgb_auto_cea_mode), + KUNIT_CASE(drm_test_check_broadcast_rgb_auto_cea_mode_vic_1), + KUNIT_CASE(drm_test_check_broadcast_rgb_full_cea_mode), + KUNIT_CASE(drm_test_check_broadcast_rgb_full_cea_mode_vic_1), + KUNIT_CASE(drm_test_check_broadcast_rgb_limited_cea_mode), + KUNIT_CASE(drm_test_check_broadcast_rgb_limited_cea_mode_vic_1), + KUNIT_CASE_PARAM(drm_test_check_broadcast_rgb_cea_mode_yuv420, + check_broadcast_rgb_cea_mode_yuv420_gen_params), + KUNIT_CASE(drm_test_check_broadcast_rgb_crtc_mode_changed), + KUNIT_CASE(drm_test_check_broadcast_rgb_crtc_mode_not_changed), + KUNIT_CASE(drm_test_check_disable_connector), + KUNIT_CASE(drm_test_check_hdmi_funcs_reject_rate), + KUNIT_CASE(drm_test_check_max_tmds_rate_bpc_fallback_rgb), + KUNIT_CASE(drm_test_check_max_tmds_rate_bpc_fallback_yuv420), + KUNIT_CASE(drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv422), + KUNIT_CASE(drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv420), + KUNIT_CASE(drm_test_check_driver_unsupported_fallback_yuv420), + KUNIT_CASE(drm_test_check_output_bpc_crtc_mode_changed), + KUNIT_CASE(drm_test_check_output_bpc_crtc_mode_not_changed), + KUNIT_CASE(drm_test_check_output_bpc_dvi), + KUNIT_CASE(drm_test_check_output_bpc_format_vic_1), + KUNIT_CASE(drm_test_check_output_bpc_format_display_8bpc_only), + KUNIT_CASE(drm_test_check_output_bpc_format_display_rgb_only), + KUNIT_CASE(drm_test_check_output_bpc_format_driver_8bpc_only), + KUNIT_CASE(drm_test_check_output_bpc_format_driver_rgb_only), + KUNIT_CASE(drm_test_check_tmds_char_rate_rgb_8bpc), + KUNIT_CASE(drm_test_check_tmds_char_rate_rgb_10bpc), + KUNIT_CASE(drm_test_check_tmds_char_rate_rgb_12bpc), + KUNIT_CASE_PARAM(drm_test_check_hdmi_color_format, + check_hdmi_color_format_gen_params), + KUNIT_CASE_PARAM(drm_test_check_hdmi_color_format_420_only, + check_hdmi_color_format_420_only_gen_params), + /* + * TODO: We should have tests to check that a change in the + * format triggers a CRTC mode change just like we do for the + * RGB Quantization and BPC. + * + * However, we don't have any way to control which format gets + * picked up aside from changing the BPC or mode which would + * already trigger a mode change. + */ + { } +}; + +static struct kunit_suite drm_atomic_helper_connector_hdmi_check_test_suite = { + .name = "drm_atomic_helper_connector_hdmi_check", + .test_cases = drm_atomic_helper_connector_hdmi_check_tests, +}; + +/* + * Test that the value of the Broadcast RGB property out of reset is set + * to auto. + */ +static void drm_test_check_broadcast_rgb_value(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector_state *conn_state; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + conn_state = conn->state; + KUNIT_EXPECT_EQ(test, conn_state->hdmi.broadcast_rgb, DRM_HDMI_BROADCAST_RGB_AUTO); +} + +/* + * Test that if the connector was initialised with a maximum bpc of 8, + * the value of the max_bpc and max_requested_bpc properties out of + * reset are also set to 8, and output_bpc is set to 0 and will be + * filled at atomic_check time. + */ +static void drm_test_check_bpc_8_value(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector_state *conn_state; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + conn_state = conn->state; + KUNIT_EXPECT_EQ(test, conn_state->max_bpc, 8); + KUNIT_EXPECT_EQ(test, conn_state->max_requested_bpc, 8); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 0); +} + +/* + * Test that if the connector was initialised with a maximum bpc of 10, + * the value of the max_bpc and max_requested_bpc properties out of + * reset are also set to 10, and output_bpc is set to 0 and will be + * filled at atomic_check time. + */ +static void drm_test_check_bpc_10_value(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector_state *conn_state; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 10); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + conn_state = conn->state; + KUNIT_EXPECT_EQ(test, conn_state->max_bpc, 10); + KUNIT_EXPECT_EQ(test, conn_state->max_requested_bpc, 10); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 0); +} + +/* + * Test that if the connector was initialised with a maximum bpc of 12, + * the value of the max_bpc and max_requested_bpc properties out of + * reset are also set to 12, and output_bpc is set to 0 and will be + * filled at atomic_check time. + */ +static void drm_test_check_bpc_12_value(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector_state *conn_state; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 12); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + conn_state = conn->state; + KUNIT_EXPECT_EQ(test, conn_state->max_bpc, 12); + KUNIT_EXPECT_EQ(test, conn_state->max_requested_bpc, 12); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 0); +} + +/* + * Test that the value of the output format property out of reset is set + * to RGB, even if the driver supports more than that. + */ +static void drm_test_check_format_value(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector_state *conn_state; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + conn_state = conn->state; + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, 0); +} + +/* + * Test that the value of the output format property out of reset is set + * to 0, and will be computed at atomic_check time. + */ +static void drm_test_check_tmds_char_value(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector_state *conn_state; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), + 12); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + conn_state = conn->state; + KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, 0); +} + +static struct kunit_case drm_atomic_helper_connector_hdmi_reset_tests[] = { + KUNIT_CASE(drm_test_check_broadcast_rgb_value), + KUNIT_CASE(drm_test_check_bpc_8_value), + KUNIT_CASE(drm_test_check_bpc_10_value), + KUNIT_CASE(drm_test_check_bpc_12_value), + KUNIT_CASE(drm_test_check_format_value), + KUNIT_CASE(drm_test_check_tmds_char_value), + { } +}; + +static struct kunit_suite drm_atomic_helper_connector_hdmi_reset_test_suite = { + .name = "drm_atomic_helper_connector_hdmi_reset", + .test_cases = drm_atomic_helper_connector_hdmi_reset_tests, +}; + +/* + * Test that the default behaviour for drm_hdmi_connector_mode_valid() is not + * to reject any modes. Pass a correct EDID and verify that preferred mode + * matches the expectations (1080p). + */ +static void drm_test_check_mode_valid(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector *conn; + struct drm_display_mode *preferred; + + priv = drm_kunit_helper_connector_hdmi_init(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + KUNIT_EXPECT_EQ(test, preferred->hdisplay, 1920); + KUNIT_EXPECT_EQ(test, preferred->vdisplay, 1080); + KUNIT_EXPECT_EQ(test, preferred->clock, 148500); +} + +/* + * Test that the drm_hdmi_connector_mode_valid() will reject modes depending on + * the .tmds_char_rate_valid() behaviour. + * Pass a correct EDID and verify that high-rate modes are filtered. + */ +static void drm_test_check_mode_valid_reject_rate(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_display_mode *preferred; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &reject_100mhz_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + /* + * Unlike the drm_test_check_mode_valid() here 1080p is rejected, but + * 480p is allowed. + */ + preferred = find_preferred_mode(&priv->connector); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_EXPECT_EQ(test, preferred->hdisplay, 640); + KUNIT_EXPECT_EQ(test, preferred->vdisplay, 480); + KUNIT_EXPECT_EQ(test, preferred->clock, 25200); +} + +/* + * Test that the drm_hdmi_connector_mode_valid() will not mark any modes as + * valid if .tmds_char_rate_valid() rejects all of them. Pass a correct EDID + * and verify that there is no preferred mode and no modes were set for the + * connector. + */ +static void drm_test_check_mode_valid_reject(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector *conn; + struct drm_display_mode *preferred; + unsigned char no_edid[] = {}; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &reject_connector_hdmi_funcs, + no_edid); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + + /* should reject all modes */ + ret = set_connector_edid(test, conn, + test_edid_hdmi_1080p_rgb_max_200mhz, + ARRAY_SIZE(test_edid_hdmi_1080p_rgb_max_200mhz)); + KUNIT_ASSERT_EQ(test, ret, 0); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NULL(test, preferred); +} + +/* + * Test that the drm_hdmi_connector_mode_valid() will reject modes that don't + * pass the info.max_tmds_clock filter. Pass crafted EDID and verify that + * high-rate modes are filtered. + */ +static void drm_test_check_mode_valid_reject_max_clock(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_connector *conn; + struct drm_display_mode *preferred; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_100mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + KUNIT_ASSERT_EQ(test, conn->display_info.max_tmds_clock, 100 * 1000); + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + KUNIT_EXPECT_EQ(test, preferred->hdisplay, 640); + KUNIT_EXPECT_EQ(test, preferred->vdisplay, 480); + KUNIT_EXPECT_EQ(test, preferred->clock, 25200); +} + +/* + * Test that drm_hdmi_connector_mode_valid() will accept modes that require a + * 4:2:0 chroma subsampling, even if said mode would violate maximum clock + * constraints if it used RGB 4:4:4. + */ +static void drm_test_check_mode_valid_yuv420_only_max_clock(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_display_mode *dank; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(HDMI_COLORSPACE_RGB) | + BIT(HDMI_COLORSPACE_YUV420), + 8, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + KUNIT_ASSERT_EQ(test, conn->display_info.max_tmds_clock, 200 * 1000); + + dank = find_420_only_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, dank); + KUNIT_EXPECT_EQ(test, dank->hdisplay, 3840); + KUNIT_EXPECT_EQ(test, dank->vdisplay, 2160); + + /* + * Note: The mode's "clock" here is not accurate to the actual TMDS + * clock that HDMI will use for a subsampled mode. Hence, why the mode's + * clock is above the .max_tmds_clock of 200MHz. + */ + KUNIT_EXPECT_EQ(test, dank->clock, 297000); +} + +/* + * Test that drm_hdmi_connector_mode_valid() will reject modes that require + * 4:2:0 chroma subsampling, if the connector does not support 4:2:0. + */ +static void +drm_test_check_mode_valid_reject_yuv420_only_connector(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_display_mode *dank; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(HDMI_COLORSPACE_RGB), + 8, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + KUNIT_ASSERT_EQ(test, conn->display_info.max_tmds_clock, 200 * 1000); + + dank = find_420_only_mode(conn); + KUNIT_EXPECT_NULL(test, dank); +} + +/* + * Test that drm_hdmi_connector_mode_valid() will accept modes that allow (among + * other color formats) 4:2:0 chroma subsampling, even if the connector does not + * support 4:2:0, but the mode's clock works for RGB 4:4:4. + */ +static void +drm_test_check_mode_valid_accept_yuv420_also_connector_rgb(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_display_mode *mode; + struct drm_connector *conn; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(HDMI_COLORSPACE_RGB), + 8, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + conn = &priv->connector; + KUNIT_ASSERT_EQ(test, conn->display_info.max_tmds_clock, 340 * 1000); + + mode = find_420_also_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, mode); + KUNIT_EXPECT_EQ(test, mode->hdisplay, 3840); + KUNIT_EXPECT_EQ(test, mode->vdisplay, 2160); + KUNIT_EXPECT_EQ(test, mode->clock, 297000); +} + +static struct kunit_case drm_atomic_helper_connector_hdmi_mode_valid_tests[] = { + KUNIT_CASE(drm_test_check_mode_valid), + KUNIT_CASE(drm_test_check_mode_valid_reject), + KUNIT_CASE(drm_test_check_mode_valid_reject_rate), + KUNIT_CASE(drm_test_check_mode_valid_reject_max_clock), + KUNIT_CASE(drm_test_check_mode_valid_yuv420_only_max_clock), + KUNIT_CASE(drm_test_check_mode_valid_reject_yuv420_only_connector), + KUNIT_CASE(drm_test_check_mode_valid_accept_yuv420_also_connector_rgb), + { } +}; + +static struct kunit_suite drm_atomic_helper_connector_hdmi_mode_valid_test_suite = { + .name = "drm_atomic_helper_connector_hdmi_mode_valid", + .test_cases = drm_atomic_helper_connector_hdmi_mode_valid_tests, +}; + +/* + * Test that the default behaviour works without errors. We expect that + * infoframe-related hooks are called and there are no errors raised. + */ +static void drm_test_check_infoframes(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int old_hdmi_update_failures; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &dummy_connector_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_crtc_state: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + crtc_state->mode_changed = true; + + old_hdmi_update_failures = priv->hdmi_update_failures; + + ret = drm_atomic_check_only(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_GE(test, old_hdmi_update_failures, priv->hdmi_update_failures); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static int reject_infoframe_write_infoframe(struct drm_connector *connector, + const u8 *buffer, size_t len) +{ + return -EOPNOTSUPP; +} + +static const struct drm_connector_hdmi_funcs reject_avi_infoframe_hdmi_funcs = { + .avi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = reject_infoframe_write_infoframe, + }, + .hdmi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, +}; + +/* + * Test that the rejection of AVI InfoFrame results in the failure of + * drm_atomic_helper_connector_hdmi_update_infoframes(). + */ +static void drm_test_check_reject_avi_infoframe(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *state; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int old_hdmi_update_failures; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &reject_avi_infoframe_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_encoder_helper_add(&priv->encoder, &test_encoder_helper_funcs); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_crtc_state: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + crtc_state->mode_changed = true; + + old_hdmi_update_failures = priv->hdmi_update_failures; + + ret = drm_atomic_check_only(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_NE(test, old_hdmi_update_failures, priv->hdmi_update_failures); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static const struct drm_connector_hdmi_funcs reject_hdr_infoframe_hdmi_funcs = { + .avi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, + .hdmi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, + .hdr_drm = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = reject_infoframe_write_infoframe, + }, +}; + +/* + * Test that the HDR InfoFrame isn't programmed in + * drm_atomic_helper_connector_hdmi_update_infoframes() if the max_bpc is 8. + */ +static void drm_test_check_reject_hdr_infoframe_bpc_8(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *state; + struct drm_connector_state *new_conn_state; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int old_hdmi_update_failures; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &reject_hdr_infoframe_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz_hdr); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_encoder_helper_add(&priv->encoder, &test_encoder_helper_funcs); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_conn_state: + new_conn_state = drm_atomic_get_connector_state(state, conn); + if (PTR_ERR(new_conn_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + /* Verify that there is no HDR property, so "userspace" can't set it. */ + for (int i = 0; i < conn->base.properties->count; i++) + KUNIT_ASSERT_PTR_NE(test, + drm->mode_config.hdr_output_metadata_property, + conn->base.properties->properties[i]); + + crtc_state->mode_changed = true; + + old_hdmi_update_failures = priv->hdmi_update_failures; + + ret = drm_atomic_check_only(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, old_hdmi_update_failures, priv->hdmi_update_failures); + + new_conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + + KUNIT_ASSERT_EQ(test, new_conn_state->hdmi.output_bpc, 8); + KUNIT_ASSERT_EQ(test, new_conn_state->hdmi.infoframes.hdr_drm.set, false); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that the rejection of HDR InfoFrame results in the failure of + * drm_atomic_helper_connector_hdmi_update_infoframes() in the high bpc is + * supported. + */ +static void drm_test_check_reject_hdr_infoframe_bpc_10(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *state; + struct drm_connector_state *new_conn_state; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int old_hdmi_update_failures; + struct hdr_output_metadata hdr_data; + struct drm_property_blob *hdr_blob; + bool replaced; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 10, + &reject_hdr_infoframe_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz_hdr); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_encoder_helper_add(&priv->encoder, &test_encoder_helper_funcs); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_conn_state: + new_conn_state = drm_atomic_get_connector_state(state, conn); + if (PTR_ERR(new_conn_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_conn_state); + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + hdr_data.metadata_type = HDMI_STATIC_METADATA_TYPE1; + hdr_data.hdmi_metadata_type1.eotf = HDMI_EOTF_TRADITIONAL_GAMMA_SDR; + hdr_data.hdmi_metadata_type1.metadata_type = HDMI_STATIC_METADATA_TYPE1; + + hdr_blob = drm_property_create_blob(drm, sizeof(hdr_data), &hdr_data); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hdr_blob); + + ret = drm_property_replace_blob_from_id(drm, + &new_conn_state->hdr_output_metadata, + hdr_blob->base.id, + -1, sizeof(struct hdr_output_metadata), -1, + &replaced); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_EQ(test, replaced, true); + + crtc_state->mode_changed = true; + + old_hdmi_update_failures = priv->hdmi_update_failures; + + ret = drm_atomic_check_only(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_LE(test, old_hdmi_update_failures, priv->hdmi_update_failures); + + new_conn_state = conn->state; + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + + KUNIT_ASSERT_EQ(test, new_conn_state->hdmi.output_bpc, 10); + KUNIT_ASSERT_EQ(test, new_conn_state->hdmi.infoframes.hdr_drm.set, true); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static const struct drm_connector_hdmi_funcs reject_audio_infoframe_hdmi_funcs = { + .avi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, + .hdmi = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = accept_infoframe_write_infoframe, + }, + .audio = { + .clear_infoframe = accept_infoframe_clear_infoframe, + .write_infoframe = reject_infoframe_write_infoframe, + }, +}; + +/* + * Test that Audio InfoFrame is only programmed if we call a corresponding API, + * thus the drivers can safely assume that they won't get Audio InfoFrames if + * they don't call it. + */ +static void drm_test_check_reject_audio_infoframe(struct kunit *test) +{ + struct drm_atomic_helper_connector_hdmi_priv *priv; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *state; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *preferred; + struct drm_connector *conn; + struct drm_device *drm; + struct drm_crtc *crtc; + int old_hdmi_update_failures; + struct hdmi_audio_infoframe cea; + int ret; + + priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + 8, + &reject_audio_infoframe_hdmi_funcs, + test_edid_hdmi_1080p_rgb_max_200mhz); + KUNIT_ASSERT_NOT_NULL(test, priv); + + drm = &priv->drm; + crtc = priv->crtc; + conn = &priv->connector; + + preferred = find_preferred_mode(conn); + KUNIT_ASSERT_NOT_NULL(test, preferred); + + drm_modeset_acquire_init(&ctx, 0); + +retry_conn_enable: + ret = drm_kunit_helper_enable_crtc_connector(test, drm, + crtc, conn, + preferred, + &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_encoder_helper_add(&priv->encoder, &test_encoder_helper_funcs); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_crtc_state: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + crtc_state->mode_changed = true; + + old_hdmi_update_failures = priv->hdmi_update_failures; + + ret = drm_atomic_check_only(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, old_hdmi_update_failures, priv->hdmi_update_failures); + + /* + * So, it works without Audio InfoFrame, let's fail with it in place, + * checking that writing the infofraem actually gets triggered. + */ + + hdmi_audio_infoframe_init(&cea); + cea.channels = 2; + cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; + cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; + cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; + + ret = drm_atomic_helper_connector_hdmi_update_audio_infoframe(conn, &cea); + KUNIT_ASSERT_EQ(test, ret, -EOPNOTSUPP); + + state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + +retry_crtc_state_2: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state_2; + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + crtc_state->mode_changed = true; + + old_hdmi_update_failures = priv->hdmi_update_failures; + + ret = drm_atomic_check_only(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state_2; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = drm_atomic_commit(state); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_crtc_state_2; + } + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_LE(test, old_hdmi_update_failures, priv->hdmi_update_failures); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + + +static struct kunit_case drm_atomic_helper_connector_hdmi_infoframes_tests[] = { + KUNIT_CASE(drm_test_check_infoframes), + KUNIT_CASE(drm_test_check_reject_avi_infoframe), + KUNIT_CASE(drm_test_check_reject_hdr_infoframe_bpc_8), + KUNIT_CASE(drm_test_check_reject_hdr_infoframe_bpc_10), + KUNIT_CASE(drm_test_check_reject_audio_infoframe), + { } +}; + +static struct kunit_suite drm_atomic_helper_connector_hdmi_infoframes_test_suite = { + .name = "drm_atomic_helper_connector_hdmi_infoframes", + .test_cases = drm_atomic_helper_connector_hdmi_infoframes_tests, +}; + +kunit_test_suites( + &drm_atomic_helper_connector_hdmi_check_test_suite, + &drm_atomic_helper_connector_hdmi_reset_test_suite, + &drm_atomic_helper_connector_hdmi_mode_valid_test_suite, + &drm_atomic_helper_connector_hdmi_infoframes_test_suite, +); + +MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>"); +MODULE_DESCRIPTION("Kunit test for drm_hdmi_state_helper functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_kunit_edid.c b/drivers/gpu/drm/tests/drm_kunit_edid.c new file mode 100644 index 000000000..c807ae0d9 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_kunit_edid.c @@ -0,0 +1,995 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/module.h> + +#include "drm_kunit_edid.h" + +/* + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 2a 00 00 00 00 00 + * 00 21 01 03 81 a0 5a 78 0a 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c + * 45 00 40 84 63 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 32 + * 46 1e 46 0f 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ab + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 42 + * Made in: 2023 + * Basic Display Parameters & Features: + * Digital display + * DFP 1.x compatible TMDS + * Maximum image size: 160 cm x 90 cm + * Gamma: 2.20 + * RGB color display + * First detailed timing is the preferred timing + * Color Characteristics: + * Red : 0.0000, 0.0000 + * Green: 0.0000, 0.0000 + * Blue : 0.0000, 0.0000 + * White: 0.0000, 0.0000 + * Established Timings I & II: none + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (1600 mm x 900 mm) + * Hfront 88 Hsync 44 Hback 148 Hpol P + * Vfront 4 Vsync 5 Vback 36 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 50-70 Hz V, 30-70 kHz H, max dotclock 150 MHz + * Dummy Descriptor: + * Checksum: 0xab + * + * ---------------- + * + * edid-decode 1.30.0-5367 + * edid-decode SHA: 41ebf7135691 2025-05-01 10:19:22 + * + * EDID conformity: PASS + */ +const unsigned char test_edid_dvi_1080p[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x46, 0x1e, 0x46, 0x0f, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab +}; +EXPORT_SYMBOL(test_edid_dvi_1080p); + +/* + * + * This edid is intentionally broken with the 100MHz limit. It's meant + * to be used only with tests in unusual situations. + * + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 2a 00 00 00 00 00 + * 00 21 01 03 81 a0 5a 78 02 00 00 00 00 00 00 00 + * 00 00 00 20 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c + * 45 00 40 84 63 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 32 + * 46 1e 46 0f 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 92 + * + * 02 03 15 81 e3 05 00 20 41 10 e2 00 4a 67 03 0c + * 00 12 34 00 14 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 42 + * Made in: 2023 + * Basic Display Parameters & Features: + * Digital display + * DFP 1.x compatible TMDS + * Maximum image size: 160 cm x 90 cm + * Gamma: 2.20 + * Monochrome or grayscale display + * First detailed timing is the preferred timing + * Color Characteristics: + * Red : 0.0000, 0.0000 + * Green: 0.0000, 0.0000 + * Blue : 0.0000, 0.0000 + * White: 0.0000, 0.0000 + * Established Timings I & II: + * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (1600 mm x 900 mm) + * Hfront 88 Hsync 44 Hback 148 Hpol P + * Vfront 4 Vsync 5 Vback 36 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 50-70 Hz V, 30-70 kHz H, max dotclock 150 MHz + * Dummy Descriptor: + * Extension blocks: 1 + * Checksum: 0x92 + * + * ---------------- + * + * Block 1, CTA-861 Extension Block: + * Revision: 3 + * Underscans IT Video Formats by default + * Native detailed modes: 1 + * Colorimetry Data Block: + * sRGB + * Video Data Block: + * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz + * Video Capability Data Block: + * YCbCr quantization: No Data + * RGB quantization: Selectable (via AVI Q) + * PT scan behavior: No Data + * IT scan behavior: Always Underscanned + * CE scan behavior: Always Underscanned + * Vendor-Specific Data Block (HDMI), OUI 00-0C-03: + * Source physical address: 1.2.3.4 + * Maximum TMDS clock: 100 MHz + * Checksum: 0x10 Unused space in Extension Block: 106 bytes + * + * ---------------- + * + * edid-decode 1.30.0-5367 + * edid-decode SHA: 41ebf7135691 2025-05-01 10:19:22 + * + * Failures: + * + * EDID: + * CTA-861: The maximum HDMI TMDS clock is 100000 kHz, but one or more video timings go up to 148500 kHz. + * + * EDID conformity: FAIL + */ +const unsigned char test_edid_hdmi_1080p_rgb_max_100mhz[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x46, 0x1e, 0x46, 0x0f, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x92, 0x02, 0x03, 0x15, 0x81, + 0xe3, 0x05, 0x00, 0x20, 0x41, 0x10, 0xe2, 0x00, 0x4a, 0x67, 0x03, 0x0c, + 0x00, 0x12, 0x34, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10 +}; +EXPORT_SYMBOL(test_edid_hdmi_1080p_rgb_max_100mhz); + +/* + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 2a 00 00 00 00 00 + * 00 21 01 03 81 a0 5a 78 02 00 00 00 00 00 00 00 + * 00 00 00 20 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c + * 45 00 40 84 63 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 32 + * 46 1e 46 0f 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 92 + * + * 02 03 15 81 e3 05 00 20 41 10 e2 00 4a 67 03 0c + * 00 12 34 00 28 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 42 + * Made in: 2023 + * Basic Display Parameters & Features: + * Digital display + * DFP 1.x compatible TMDS + * Maximum image size: 160 cm x 90 cm + * Gamma: 2.20 + * Monochrome or grayscale display + * First detailed timing is the preferred timing + * Color Characteristics: + * Red : 0.0000, 0.0000 + * Green: 0.0000, 0.0000 + * Blue : 0.0000, 0.0000 + * White: 0.0000, 0.0000 + * Established Timings I & II: + * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (1600 mm x 900 mm) + * Hfront 88 Hsync 44 Hback 148 Hpol P + * Vfront 4 Vsync 5 Vback 36 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 50-70 Hz V, 30-70 kHz H, max dotclock 150 MHz + * Dummy Descriptor: + * Extension blocks: 1 + * Checksum: 0x92 + * + * ---------------- + * + * Block 1, CTA-861 Extension Block: + * Revision: 3 + * Underscans IT Video Formats by default + * Native detailed modes: 1 + * Colorimetry Data Block: + * sRGB + * Video Data Block: + * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz + * Video Capability Data Block: + * YCbCr quantization: No Data + * RGB quantization: Selectable (via AVI Q) + * PT scan behavior: No Data + * IT scan behavior: Always Underscanned + * CE scan behavior: Always Underscanned + * Vendor-Specific Data Block (HDMI), OUI 00-0C-03: + * Source physical address: 1.2.3.4 + * Maximum TMDS clock: 200 MHz + * Checksum: 0xfc Unused space in Extension Block: 106 bytes + * + * ---------------- + * + * edid-decode 1.30.0-5367 + * edid-decode SHA: 41ebf7135691 2025-05-01 10:19:22 + * + * EDID conformity: PASS + */ +const unsigned char test_edid_hdmi_1080p_rgb_max_200mhz[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x46, 0x1e, 0x46, 0x0f, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x92, 0x02, 0x03, 0x15, 0x81, + 0xe3, 0x05, 0x00, 0x20, 0x41, 0x10, 0xe2, 0x00, 0x4a, 0x67, 0x03, 0x0c, + 0x00, 0x12, 0x34, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfc +}; +EXPORT_SYMBOL(test_edid_hdmi_1080p_rgb_max_200mhz); + +/* + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 2a 00 00 00 00 00 + * 00 21 01 03 81 a0 5a 78 02 00 00 00 00 00 00 00 + * 00 00 00 20 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c + * 45 00 40 84 63 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 32 + * 46 1e 46 0f 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 92 + * + * 02 03 1c 81 e3 05 c0 20 41 10 e2 00 4a 67 03 0c + * 00 12 34 00 28 e6 06 05 01 52 52 51 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4e + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 42 + * Made in: 2023 + * Basic Display Parameters & Features: + * Digital display + * DFP 1.x compatible TMDS + * Maximum image size: 160 cm x 90 cm + * Gamma: 2.20 + * Monochrome or grayscale display + * First detailed timing is the preferred timing + * Color Characteristics: + * Red : 0.0000, 0.0000 + * Green: 0.0000, 0.0000 + * Blue : 0.0000, 0.0000 + * White: 0.0000, 0.0000 + * Established Timings I & II: + * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (1600 mm x 900 mm) + * Hfront 88 Hsync 44 Hback 148 Hpol P + * Vfront 4 Vsync 5 Vback 36 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 50-70 Hz V, 30-70 kHz H, max dotclock 150 MHz + * Dummy Descriptor: + * Extension blocks: 1 + * Checksum: 0x92 + * + * ---------------- + * + * Block 1, CTA-861 Extension Block: + * Revision: 3 + * Underscans IT Video Formats by default + * Native detailed modes: 1 + * Colorimetry Data Block: + * BT2020YCC + * BT2020RGB + * sRGB + * Video Data Block: + * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz + * Video Capability Data Block: + * YCbCr quantization: No Data + * RGB quantization: Selectable (via AVI Q) + * PT scan behavior: No Data + * IT scan behavior: Always Underscanned + * CE scan behavior: Always Underscanned + * Vendor-Specific Data Block (HDMI), OUI 00-0C-03: + * Source physical address: 1.2.3.4 + * Maximum TMDS clock: 200 MHz + * HDR Static Metadata Data Block: + * Electro optical transfer functions: + * Traditional gamma - SDR luminance range + * SMPTE ST2084 + * Supported static metadata descriptors: + * Static metadata type 1 + * Desired content max luminance: 82 (295.365 cd/m^2) + * Desired content max frame-average luminance: 82 (295.365 cd/m^2) + * Desired content min luminance: 81 (0.298 cd/m^2) + * Checksum: 0x4e Unused space in Extension Block: 99 bytes + * + * ---------------- + * + * edid-decode 1.31.0-5387 + * edid-decode SHA: 5508bc4301ac 2025-08-25 08:14:22 + * + * EDID conformity: PASS + */ +const unsigned char test_edid_hdmi_1080p_rgb_max_200mhz_hdr[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x46, 0x1e, 0x46, 0x0f, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x92, 0x02, 0x03, 0x1c, 0x81, + 0xe3, 0x05, 0xc0, 0x20, 0x41, 0x10, 0xe2, 0x00, 0x4a, 0x67, 0x03, 0x0c, + 0x00, 0x12, 0x34, 0x78, 0x28, 0xe6, 0x06, 0x05, 0x01, 0x52, 0x52, 0x51, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xd6, +}; +EXPORT_SYMBOL(test_edid_hdmi_1080p_rgb_max_200mhz_hdr); + +/* + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 2a 00 00 00 00 00 + * 00 21 01 03 81 a0 5a 78 02 00 00 00 00 00 00 00 + * 00 00 00 20 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c + * 45 00 40 84 63 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 32 + * 46 1e 46 0f 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 92 + * + * 02 03 15 81 e3 05 00 20 41 10 e2 00 4a 67 03 0c + * 00 12 34 00 44 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e0 + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 42 + * Made in: 2023 + * Basic Display Parameters & Features: + * Digital display + * DFP 1.x compatible TMDS + * Maximum image size: 160 cm x 90 cm + * Gamma: 2.20 + * Monochrome or grayscale display + * First detailed timing is the preferred timing + * Color Characteristics: + * Red : 0.0000, 0.0000 + * Green: 0.0000, 0.0000 + * Blue : 0.0000, 0.0000 + * White: 0.0000, 0.0000 + * Established Timings I & II: + * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (1600 mm x 900 mm) + * Hfront 88 Hsync 44 Hback 148 Hpol P + * Vfront 4 Vsync 5 Vback 36 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 50-70 Hz V, 30-70 kHz H, max dotclock 150 MHz + * Dummy Descriptor: + * Extension blocks: 1 + * Checksum: 0x92 + * + * ---------------- + * + * Block 1, CTA-861 Extension Block: + * Revision: 3 + * Underscans IT Video Formats by default + * Native detailed modes: 1 + * Colorimetry Data Block: + * sRGB + * Video Data Block: + * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz + * Video Capability Data Block: + * YCbCr quantization: No Data + * RGB quantization: Selectable (via AVI Q) + * PT scan behavior: No Data + * IT scan behavior: Always Underscanned + * CE scan behavior: Always Underscanned + * Vendor-Specific Data Block (HDMI), OUI 00-0C-03: + * Source physical address: 1.2.3.4 + * Maximum TMDS clock: 340 MHz + * Checksum: 0xe0 Unused space in Extension Block: 106 bytes + * + * ---------------- + * + * edid-decode 1.30.0-5367 + * edid-decode SHA: 41ebf7135691 2025-05-01 10:19:22 + * + * EDID conformity: PASS + */ +const unsigned char test_edid_hdmi_1080p_rgb_max_340mhz[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x46, 0x1e, 0x46, 0x0f, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x92, 0x02, 0x03, 0x15, 0x81, + 0xe3, 0x05, 0x00, 0x20, 0x41, 0x10, 0xe2, 0x00, 0x4a, 0x67, 0x03, 0x0c, + 0x00, 0x12, 0x34, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xe0 +}; +EXPORT_SYMBOL(test_edid_hdmi_1080p_rgb_max_340mhz); + +/* + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 2a 00 00 00 00 00 + * 00 21 01 03 81 a0 5a 78 1a 00 00 00 00 00 00 00 + * 00 00 00 20 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c + * 45 00 40 84 63 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 32 + * 46 1e 46 0f 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 7a + * + * 02 03 15 b1 e3 05 00 20 41 10 e2 00 ca 67 03 0c + * 00 12 34 78 28 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 d4 + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 42 + * Made in: 2023 + * Basic Display Parameters & Features: + * Digital display + * DFP 1.x compatible TMDS + * Maximum image size: 160 cm x 90 cm + * Gamma: 2.20 + * Undefined display color type + * First detailed timing is the preferred timing + * Color Characteristics: + * Red : 0.0000, 0.0000 + * Green: 0.0000, 0.0000 + * Blue : 0.0000, 0.0000 + * White: 0.0000, 0.0000 + * Established Timings I & II: + * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (1600 mm x 900 mm) + * Hfront 88 Hsync 44 Hback 148 Hpol P + * Vfront 4 Vsync 5 Vback 36 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 50-70 Hz V, 30-70 kHz H, max dotclock 150 MHz + * Dummy Descriptor: + * Extension blocks: 1 + * Checksum: 0x7a + * + * ---------------- + * + * Block 1, CTA-861 Extension Block: + * Revision: 3 + * Underscans IT Video Formats by default + * Supports YCbCr 4:4:4 + * Supports YCbCr 4:2:2 + * Native detailed modes: 1 + * Colorimetry Data Block: + * sRGB + * Video Data Block: + * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz + * Video Capability Data Block: + * YCbCr quantization: Selectable (via AVI YQ) + * RGB quantization: Selectable (via AVI Q) + * PT scan behavior: No Data + * IT scan behavior: Always Underscanned + * CE scan behavior: Always Underscanned + * Vendor-Specific Data Block (HDMI), OUI 00-0C-03: + * Source physical address: 1.2.3.4 + * DC_48bit + * DC_36bit + * DC_30bit + * DC_Y444 + * Maximum TMDS clock: 200 MHz + * Checksum: 0xd4 Unused space in Extension Block: 106 bytes + * + * ---------------- + * + * edid-decode 1.30.0-5367 + * edid-decode SHA: 41ebf7135691 2025-05-01 10:19:22 + * + * EDID conformity: PASS + */ +const unsigned char test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78, + 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x46, 0x1e, 0x46, 0x0f, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7a, 0x02, 0x03, 0x15, 0xb1, + 0xe3, 0x05, 0x00, 0x20, 0x41, 0x10, 0xe2, 0x00, 0xca, 0x67, 0x03, 0x0c, + 0x00, 0x12, 0x34, 0x78, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xd4 +}; +EXPORT_SYMBOL(test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); + +/* + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 2a 00 00 00 00 00 + * 00 21 01 03 81 a0 5a 78 0a 00 00 00 00 00 00 00 + * 00 00 00 20 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c + * 45 00 40 84 63 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 32 + * 46 1e 46 0f 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 8a + * + * 02 03 15 b1 e3 05 00 20 41 10 e2 00 ca 67 03 0c + * 00 12 34 78 44 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b8 + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 42 + * Made in: 2023 + * Basic Display Parameters & Features: + * Digital display + * DFP 1.x compatible TMDS + * Maximum image size: 160 cm x 90 cm + * Gamma: 2.20 + * RGB color display + * First detailed timing is the preferred timing + * Color Characteristics: + * Red : 0.0000, 0.0000 + * Green: 0.0000, 0.0000 + * Blue : 0.0000, 0.0000 + * White: 0.0000, 0.0000 + * Established Timings I & II: + * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (1600 mm x 900 mm) + * Hfront 88 Hsync 44 Hback 148 Hpol P + * Vfront 4 Vsync 5 Vback 36 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 50-70 Hz V, 30-70 kHz H, max dotclock 150 MHz + * Dummy Descriptor: + * Extension blocks: 1 + * Checksum: 0x8a + * + * ---------------- + * + * Block 1, CTA-861 Extension Block: + * Revision: 3 + * Underscans IT Video Formats by default + * Supports YCbCr 4:4:4 + * Supports YCbCr 4:2:2 + * Native detailed modes: 1 + * Colorimetry Data Block: + * sRGB + * Video Data Block: + * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz + * Video Capability Data Block: + * YCbCr quantization: Selectable (via AVI YQ) + * RGB quantization: Selectable (via AVI Q) + * PT scan behavior: No Data + * IT scan behavior: Always Underscanned + * CE scan behavior: Always Underscanned + * Vendor-Specific Data Block (HDMI), OUI 00-0C-03: + * Source physical address: 1.2.3.4 + * DC_48bit + * DC_36bit + * DC_30bit + * DC_Y444 + * Maximum TMDS clock: 340 MHz + * Checksum: 0xb8 Unused space in Extension Block: 106 bytes + * + * ---------------- + * + * edid-decode 1.30.0-5367 + * edid-decode SHA: 41ebf7135691 2025-05-01 10:19:22 + * + * EDID conformity: PASS + */ +const unsigned char test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x46, 0x1e, 0x46, 0x0f, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x8a, 0x02, 0x03, 0x15, 0xb1, + 0xe3, 0x05, 0x00, 0x20, 0x41, 0x10, 0xe2, 0x00, 0xca, 0x67, 0x03, 0x0c, + 0x00, 0x12, 0x34, 0x78, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xb8 +}; +EXPORT_SYMBOL(test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz); + +/* + * Max resolution: + * - 1920x1080@60Hz with RGB, YUV444, YUV422 + * - 3840x2160@30Hz with YUV420 only + * Max BPC: 16 for all modes + * Max TMDS clock: 200 MHz + * + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 34 00 00 00 00 00 + * ff 23 01 03 80 60 36 78 0f ee 91 a3 54 4c 99 26 + * 0f 50 54 20 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c + * 45 00 c0 1c 32 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 18 + * 55 18 5e 11 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 bb + * + * 02 03 29 31 42 90 5f 6c 03 0c 00 10 00 78 28 20 + * 00 00 01 03 6d d8 5d c4 01 28 80 07 00 00 00 00 + * 00 00 e3 0f 00 00 e2 0e 5f 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ca + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 52 + * Model year: 2025 + * Basic Display Parameters & Features: + * Digital display + * Maximum image size: 96 cm x 54 cm + * Gamma: 2.20 + * RGB color display + * Default (sRGB) color space is primary color space + * First detailed timing is the preferred timing + * Supports GTF timings within operating range + * Color Characteristics: + * Red : 0.6396, 0.3300 + * Green: 0.2998, 0.5996 + * Blue : 0.1503, 0.0595 + * White: 0.3125, 0.3291 + * Established Timings I & II: + * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (960 mm x 540 mm) + * Hfront 88 Hsync 44 Hback 148 Hpol P + * Vfront 4 Vsync 5 Vback 36 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 24-85 Hz V, 24-94 kHz H, max dotclock 170 MHz + * Dummy Descriptor: + * Extension blocks: 1 + * Checksum: 0xbb + * + * ---------------- + * + * Block 1, CTA-861 Extension Block: + * Revision: 3 + * Supports YCbCr 4:4:4 + * Supports YCbCr 4:2:2 + * Native detailed modes: 1 + * Video Data Block: + * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (native) + * VIC 95: 3840x2160 30.000000 Hz 16:9 67.500 kHz 297.000000 MHz + * Vendor-Specific Data Block (HDMI), OUI 00-0C-03: + * Source physical address: 1.0.0.0 + * DC_48bit + * DC_36bit + * DC_30bit + * DC_Y444 + * Maximum TMDS clock: 200 MHz + * Extended HDMI video details: + * Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8: + * Version: 1 + * Maximum TMDS Character Rate: 200 MHz + * SCDC Present + * Supports 16-bits/component Deep Color 4:2:0 Pixel Encoding + * Supports 12-bits/component Deep Color 4:2:0 Pixel Encoding + * Supports 10-bits/component Deep Color 4:2:0 Pixel Encoding + * YCbCr 4:2:0 Capability Map Data Block: + * Empty Capability Map + * YCbCr 4:2:0 Video Data Block: + * VIC 95: 3840x2160 30.000000 Hz 16:9 67.500 kHz 297.000000 MHz + * Checksum: 0xca + */ +const unsigned char test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x34, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x23, 0x01, 0x03, 0x80, 0x60, 0x36, 0x78, + 0x0f, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26, 0x0f, 0x50, 0x54, 0x20, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, + 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0xc0, 0x1c, 0x32, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x18, + 0x55, 0x18, 0x5e, 0x11, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xbb, 0x02, 0x03, 0x29, 0x31, + 0x42, 0x90, 0x5f, 0x6c, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x78, 0x28, 0x20, + 0x00, 0x00, 0x01, 0x03, 0x6d, 0xd8, 0x5d, 0xc4, 0x01, 0x28, 0x80, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x0f, 0x00, 0x00, 0xe2, 0x0e, + 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xca +}; +EXPORT_SYMBOL(test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); + +/* + * Max resolution: 3840x2160@30Hz with RGB, YUV444, YUV422, YUV420 + * Max BPC: 16 for all modes + * Max TMDS clock: 340 MHz + * + * edid-decode (hex): + * + * 00 ff ff ff ff ff ff 00 31 d8 34 00 00 00 00 00 + * ff 23 01 03 80 60 36 78 0f ee 91 a3 54 4c 99 26 + * 0f 50 54 20 00 00 01 01 01 01 01 01 01 01 01 01 + * 01 01 01 01 01 01 04 74 00 30 f2 70 5a 80 b0 58 + * 8a 00 40 84 63 00 00 1e 00 00 00 fc 00 54 65 73 + * 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd 00 18 + * 55 18 5e 22 00 0a 20 20 20 20 20 20 00 00 00 10 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ce + * + * 02 03 27 31 41 5f 6c 03 0c 00 10 00 78 44 20 00 + * 00 01 03 6d d8 5d c4 01 44 80 07 00 00 00 00 00 + * 00 e3 0f 01 00 e1 0e 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 84 + * + * ---------------- + * + * Block 0, Base EDID: + * EDID Structure Version & Revision: 1.3 + * Vendor & Product Identification: + * Manufacturer: LNX + * Model: 52 + * Model year: 2025 + * Basic Display Parameters & Features: + * Digital display + * Maximum image size: 96 cm x 54 cm + * Gamma: 2.20 + * RGB color display + * Default (sRGB) color space is primary color space + * First detailed timing is the preferred timing + * Supports GTF timings within operating range + * Color Characteristics: + * Red : 0.6396, 0.3300 + * Green: 0.2998, 0.5996 + * Blue : 0.1503, 0.0595 + * White: 0.3125, 0.3291 + * Established Timings I & II: + * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz + * Standard Timings: none + * Detailed Timing Descriptors: + * DTD 1: 3840x2160 30.000000 Hz 16:9 67.500 kHz 297.000000 MHz (1600 mm x 900 mm) + * Hfront 176 Hsync 88 Hback 296 Hpol P + * Vfront 8 Vsync 10 Vback 72 Vpol P + * Display Product Name: 'Test EDID' + * Display Range Limits: + * Monitor ranges (GTF): 24-85 Hz V, 24-94 kHz H, max dotclock 340 MHz + * Dummy Descriptor: + * Extension blocks: 1 + * Checksum: 0xce + * + * ---------------- + * + * Block 1, CTA-861 Extension Block: + * Revision: 3 + * Supports YCbCr 4:4:4 + * Supports YCbCr 4:2:2 + * Native detailed modes: 1 + * Video Data Block: + * VIC 95: 3840x2160 30.000000 Hz 16:9 67.500 kHz 297.000000 MHz + * Vendor-Specific Data Block (HDMI), OUI 00-0C-03: + * Source physical address: 1.0.0.0 + * DC_48bit + * DC_36bit + * DC_30bit + * DC_Y444 + * Maximum TMDS clock: 340 MHz + * Extended HDMI video details: + * Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8: + * Version: 1 + * Maximum TMDS Character Rate: 340 MHz + * SCDC Present + * Supports 16-bits/component Deep Color 4:2:0 Pixel Encoding + * Supports 12-bits/component Deep Color 4:2:0 Pixel Encoding + * Supports 10-bits/component Deep Color 4:2:0 Pixel Encoding + * YCbCr 4:2:0 Capability Map Data Block: + * VIC 95: 3840x2160 30.000000 Hz 16:9 67.500 kHz 297.000000 MHz + * YCbCr 4:2:0 Video Data Block: + * Checksum: 0x84 + */ +const unsigned char test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x34, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x23, 0x01, 0x03, 0x80, 0x60, 0x36, 0x78, + 0x0f, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26, 0x0f, 0x50, 0x54, 0x20, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x74, 0x00, 0x30, 0xf2, 0x70, + 0x5a, 0x80, 0xb0, 0x58, 0x8a, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x44, + 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x18, + 0x55, 0x18, 0x5e, 0x22, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xce, 0x02, 0x03, 0x27, 0x31, + 0x41, 0x5f, 0x6c, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x78, 0x44, 0x20, 0x00, + 0x00, 0x01, 0x03, 0x6d, 0xd8, 0x5d, 0xc4, 0x01, 0x44, 0x80, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x0f, 0x01, 0x00, 0xe1, 0x0e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x84 +}; +EXPORT_SYMBOL(test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz); + +MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>"); +MODULE_DESCRIPTION("EDID Definitions for KUnit tests"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_kunit_edid.h b/drivers/gpu/drm/tests/drm_kunit_edid.h new file mode 100644 index 000000000..28b4df93a --- /dev/null +++ b/drivers/gpu/drm/tests/drm_kunit_edid.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef DRM_KUNIT_EDID_H_ +#define DRM_KUNIT_EDID_H_ + +extern const unsigned char test_edid_dvi_1080p[128]; +extern const unsigned char test_edid_hdmi_1080p_rgb_max_100mhz[256]; +extern const unsigned char test_edid_hdmi_1080p_rgb_max_200mhz[256]; +extern const unsigned char test_edid_hdmi_1080p_rgb_max_200mhz_hdr[256]; +extern const unsigned char test_edid_hdmi_1080p_rgb_max_340mhz[256]; +extern const unsigned char test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz[256]; +extern const unsigned char test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz[256]; +extern const unsigned char test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz[256]; +extern const unsigned char test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz[256]; + +#endif // DRM_KUNIT_EDID_H_ diff --git a/drivers/gpu/drm/tests/drm_kunit_helpers.c b/drivers/gpu/drm/tests/drm_kunit_helpers.c new file mode 100644 index 000000000..727766444 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_kunit_helpers.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_managed.h> + +#include <kunit/device.h> +#include <kunit/resource.h> + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/platform_device.h> + +#define KUNIT_DEVICE_NAME "drm-kunit-mock-device" + +static const struct drm_mode_config_funcs drm_mode_config_funcs = { + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +/** + * drm_kunit_helper_alloc_device - Allocate a mock device for a KUnit test + * @test: The test context object + * + * This allocates a fake struct &device to create a mock for a KUnit + * test. The device will also be bound to a fake driver. It will thus be + * able to leverage the usual infrastructure and most notably the + * device-managed resources just like a "real" device. + * + * Resources will be cleaned up automatically, but the removal can be + * forced using @drm_kunit_helper_free_device. + * + * Returns: + * A pointer to the new device, or an ERR_PTR() otherwise. + */ +struct device *drm_kunit_helper_alloc_device(struct kunit *test) +{ + return kunit_device_register(test, KUNIT_DEVICE_NAME); +} +EXPORT_SYMBOL_GPL(drm_kunit_helper_alloc_device); + +/** + * drm_kunit_helper_free_device - Frees a mock device + * @test: The test context object + * @dev: The device to free + * + * Frees a device allocated with drm_kunit_helper_alloc_device(). + */ +void drm_kunit_helper_free_device(struct kunit *test, struct device *dev) +{ + kunit_device_unregister(test, dev); +} +EXPORT_SYMBOL_GPL(drm_kunit_helper_free_device); + +struct drm_device * +__drm_kunit_helper_alloc_drm_device_with_driver(struct kunit *test, + struct device *dev, + size_t size, size_t offset, + const struct drm_driver *driver) +{ + struct drm_device *drm; + void *container; + int ret; + + container = __devm_drm_dev_alloc(dev, driver, size, offset); + if (IS_ERR(container)) + return ERR_CAST(container); + + drm = container + offset; + drm->mode_config.funcs = &drm_mode_config_funcs; + + ret = drmm_mode_config_init(drm); + if (ret) + return ERR_PTR(ret); + + return drm; +} +EXPORT_SYMBOL_GPL(__drm_kunit_helper_alloc_drm_device_with_driver); + +static void kunit_action_drm_atomic_commit_put(void *ptr) +{ + struct drm_atomic_commit *state = ptr; + + drm_atomic_commit_put(state); +} + +/** + * drm_kunit_helper_atomic_state_alloc - Allocates an atomic state + * @test: The test context object + * @drm: The device to alloc the state for + * @ctx: Locking context for that atomic update + * + * Allocates a empty atomic state. + * + * The state is tied to the kunit test context, so we must not call + * drm_atomic_commit_put() on it, it will be done so automatically. + * + * Returns: + * An ERR_PTR on error, a pointer to the newly allocated state otherwise + */ +struct drm_atomic_commit * +drm_kunit_helper_atomic_state_alloc(struct kunit *test, + struct drm_device *drm, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_atomic_commit *state; + int ret; + + state = drm_atomic_commit_alloc(drm); + if (!state) + return ERR_PTR(-ENOMEM); + + ret = kunit_add_action_or_reset(test, + kunit_action_drm_atomic_commit_put, + state); + if (ret) + return ERR_PTR(ret); + + state->acquire_ctx = ctx; + + return state; +} +EXPORT_SYMBOL_GPL(drm_kunit_helper_atomic_state_alloc); + +static const uint32_t default_plane_formats[] = { + DRM_FORMAT_XRGB8888, +}; + +static const uint64_t default_plane_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static const struct drm_plane_helper_funcs default_plane_helper_funcs = { +}; + +static const struct drm_plane_funcs default_plane_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .reset = drm_atomic_helper_plane_reset, +}; + +/** + * drm_kunit_helper_create_primary_plane - Creates a mock primary plane for a KUnit test + * @test: The test context object + * @drm: The device to alloc the plane for + * @funcs: Callbacks for the new plane. Optional. + * @helper_funcs: Helpers callbacks for the new plane. Optional. + * @formats: array of supported formats (DRM_FORMAT\_\*). Optional. + * @num_formats: number of elements in @formats + * @modifiers: array of struct drm_format modifiers terminated by + * DRM_FORMAT_MOD_INVALID. Optional. + * + * This allocates and initializes a mock struct &drm_plane meant to be + * part of a mock device for a KUnit test. + * + * Resources will be cleaned up automatically. + * + * @funcs will default to the default helpers implementations. + * @helper_funcs will default to an empty implementation. @formats will + * default to XRGB8888 only. @modifiers will default to a linear + * modifier only. + * + * Returns: + * A pointer to the new plane, or an ERR_PTR() otherwise. + */ +struct drm_plane * +drm_kunit_helper_create_primary_plane(struct kunit *test, + struct drm_device *drm, + const struct drm_plane_funcs *funcs, + const struct drm_plane_helper_funcs *helper_funcs, + const uint32_t *formats, + unsigned int num_formats, + const uint64_t *modifiers) +{ + struct drm_plane *plane; + + if (!funcs) + funcs = &default_plane_funcs; + + if (!helper_funcs) + helper_funcs = &default_plane_helper_funcs; + + if (!formats || !num_formats) { + formats = default_plane_formats; + num_formats = ARRAY_SIZE(default_plane_formats); + } + + if (!modifiers) + modifiers = default_plane_modifiers; + + plane = __drmm_universal_plane_alloc(drm, + sizeof(struct drm_plane), 0, + 0, + funcs, + formats, + num_formats, + default_plane_modifiers, + DRM_PLANE_TYPE_PRIMARY, + NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane); + + drm_plane_helper_add(plane, helper_funcs); + + return plane; +} +EXPORT_SYMBOL_GPL(drm_kunit_helper_create_primary_plane); + +static const struct drm_crtc_helper_funcs default_crtc_helper_funcs = { +}; + +static const struct drm_crtc_funcs default_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .reset = drm_atomic_helper_crtc_reset, +}; + +/** + * drm_kunit_helper_create_crtc - Creates a mock CRTC for a KUnit test + * @test: The test context object + * @drm: The device to alloc the plane for + * @primary: Primary plane for CRTC + * @cursor: Cursor plane for CRTC. Optional. + * @funcs: Callbacks for the new plane. Optional. + * @helper_funcs: Helpers callbacks for the new plane. Optional. + * + * This allocates and initializes a mock struct &drm_crtc meant to be + * part of a mock device for a KUnit test. + * + * Resources will be cleaned up automatically. + * + * @funcs will default to the default helpers implementations. + * @helper_funcs will default to an empty implementation. + * + * Returns: + * A pointer to the new CRTC, or an ERR_PTR() otherwise. + */ +struct drm_crtc * +drm_kunit_helper_create_crtc(struct kunit *test, + struct drm_device *drm, + struct drm_plane *primary, + struct drm_plane *cursor, + const struct drm_crtc_funcs *funcs, + const struct drm_crtc_helper_funcs *helper_funcs) +{ + struct drm_crtc *crtc; + int ret; + + if (!funcs) + funcs = &default_crtc_funcs; + + if (!helper_funcs) + helper_funcs = &default_crtc_helper_funcs; + + crtc = drmm_kzalloc(drm, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + ret = drmm_crtc_init_with_planes(drm, crtc, + primary, + cursor, + funcs, + NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_crtc_helper_add(crtc, helper_funcs); + + return crtc; +} +EXPORT_SYMBOL_GPL(drm_kunit_helper_create_crtc); + +/** + * drm_kunit_helper_enable_crtc_connector - Enables a CRTC -> Connector output + * @test: The test context object + * @drm: The device to alloc the plane for + * @crtc: The CRTC to enable + * @connector: The Connector to enable + * @mode: The display mode to configure the CRTC with + * @ctx: Locking context + * + * This function creates an atomic update to enable the route from @crtc + * to @connector, with the given @mode. + * + * Returns: + * + * A pointer to the new CRTC, or an ERR_PTR() otherwise. If the error + * returned is EDEADLK, the entire atomic sequence must be restarted. + */ +int drm_kunit_helper_enable_crtc_connector(struct kunit *test, + struct drm_device *drm, + struct drm_crtc *crtc, + struct drm_connector *connector, + const struct drm_display_mode *mode, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_atomic_commit *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); + if (IS_ERR(state)) + return PTR_ERR(state); + + conn_state = drm_atomic_get_connector_state(state, connector); + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + + ret = drm_atomic_set_crtc_for_connector(conn_state, crtc); + if (ret) + return ret; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret) + return ret; + + crtc_state->enable = true; + crtc_state->active = true; + + ret = drm_atomic_commit(state); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(drm_kunit_helper_enable_crtc_connector); + +static void kunit_action_drm_mode_destroy(void *ptr) +{ + struct drm_display_mode *mode = ptr; + + drm_mode_destroy(NULL, mode); +} + +/** + * drm_kunit_add_mode_destroy_action() - Add a drm_destroy_mode kunit action + * @test: The test context object + * @mode: The drm_display_mode to destroy eventually + * + * Registers a kunit action that will destroy the drm_display_mode at + * the end of the test. + * + * If an error occurs, the drm_display_mode will be destroyed. + * + * Returns: + * 0 on success, an error code otherwise. + */ +int drm_kunit_add_mode_destroy_action(struct kunit *test, + struct drm_display_mode *mode) +{ + return kunit_add_action_or_reset(test, + kunit_action_drm_mode_destroy, + mode); +} +EXPORT_SYMBOL_GPL(drm_kunit_add_mode_destroy_action); + +/** + * drm_kunit_display_mode_from_cea_vic() - return a mode for CEA VIC for a KUnit test + * @test: The test context object + * @dev: DRM device + * @video_code: CEA VIC of the mode + * + * Creates a new mode matching the specified CEA VIC for a KUnit test. + * + * Resources will be cleaned up automatically. + * + * Returns: A new drm_display_mode on success or NULL on failure + */ +struct drm_display_mode * +drm_kunit_display_mode_from_cea_vic(struct kunit *test, struct drm_device *dev, + u8 video_code) +{ + struct drm_display_mode *mode; + int ret; + + mode = drm_display_mode_from_cea_vic(dev, video_code); + if (!mode) + return NULL; + + ret = kunit_add_action_or_reset(test, + kunit_action_drm_mode_destroy, + mode); + if (ret) + return NULL; + + return mode; +} +EXPORT_SYMBOL_GPL(drm_kunit_display_mode_from_cea_vic); + +MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>"); +MODULE_DESCRIPTION("KUnit test suite helper functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_managed_test.c b/drivers/gpu/drm/tests/drm_managed_test.c new file mode 100644 index 000000000..d40c7ef7f --- /dev/null +++ b/drivers/gpu/drm/tests/drm_managed_test.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <drm/drm_drv.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_managed.h> + +#include <kunit/resource.h> + +#include <linux/device.h> + +/* Ought to be enough for anybody */ +#define TEST_TIMEOUT_MS 100 + +struct managed_test_priv { + struct drm_device *drm; + bool action_done; + wait_queue_head_t action_wq; +}; + +static void drm_action(struct drm_device *drm, void *ptr) +{ + struct managed_test_priv *priv = ptr; + + priv->action_done = true; + wake_up_interruptible(&priv->action_wq); +} + +/* + * The test verifies that the release action is called when + * drmm_release_action is called. + */ +static void drm_test_managed_release_action(struct kunit *test) +{ + struct managed_test_priv *priv = test->priv; + int ret; + + ret = drmm_add_action_or_reset(priv->drm, drm_action, priv); + KUNIT_EXPECT_EQ(test, ret, 0); + + ret = drm_dev_register(priv->drm, 0); + KUNIT_ASSERT_EQ(test, ret, 0); + + drmm_release_action(priv->drm, drm_action, priv); + ret = wait_event_interruptible_timeout(priv->action_wq, priv->action_done, + msecs_to_jiffies(TEST_TIMEOUT_MS)); + KUNIT_EXPECT_GT(test, ret, 0); + + drm_dev_unregister(priv->drm); + drm_kunit_helper_free_device(test, priv->drm->dev); +} + +/* + * The test verifies that the release action is called automatically when the + * device is released. + */ +static void drm_test_managed_run_action(struct kunit *test) +{ + struct managed_test_priv *priv = test->priv; + int ret; + + ret = drmm_add_action_or_reset(priv->drm, drm_action, priv); + KUNIT_EXPECT_EQ(test, ret, 0); + + ret = drm_dev_register(priv->drm, 0); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_dev_unregister(priv->drm); + drm_kunit_helper_free_device(test, priv->drm->dev); + + ret = wait_event_interruptible_timeout(priv->action_wq, priv->action_done, + msecs_to_jiffies(TEST_TIMEOUT_MS)); + KUNIT_EXPECT_GT(test, ret, 0); +} + +static int drm_managed_test_init(struct kunit *test) +{ + struct managed_test_priv *priv; + struct device *dev; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + init_waitqueue_head(&priv->action_wq); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + /* + * DRM device can't be embedded in priv, since priv->action_done needs + * to remain allocated beyond both parent device and drm_device + * lifetime. + */ + priv->drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*priv->drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->drm); + + test->priv = priv; + + return 0; +} + +static struct kunit_case drm_managed_tests[] = { + KUNIT_CASE(drm_test_managed_release_action), + KUNIT_CASE(drm_test_managed_run_action), + {} +}; + +static struct kunit_suite drm_managed_test_suite = { + .name = "drm_managed", + .init = drm_managed_test_init, + .test_cases = drm_managed_tests +}; + +kunit_test_suite(drm_managed_test_suite); + +MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>"); +MODULE_DESCRIPTION("KUnit DRM managed test suite"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_mm_test.c b/drivers/gpu/drm/tests/drm_mm_test.c new file mode 100644 index 000000000..b12aa26e1 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_mm_test.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test cases for the drm_mm range manager + * + * Copyright (c) 2022 Arthur Grillo <arthur.grillo@usp.br> + */ + +#include <kunit/test.h> + +#include <linux/prime_numbers.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/vmalloc.h> +#include <linux/ktime.h> + +#include <drm/drm_mm.h> +#include <drm/drm_print.h> + +enum { + BEST, + BOTTOMUP, + TOPDOWN, + EVICT, +}; + +static const struct insert_mode { + const char *name; + enum drm_mm_insert_mode mode; +} insert_modes[] = { + [BEST] = { "best", DRM_MM_INSERT_BEST }, + [BOTTOMUP] = { "bottom-up", DRM_MM_INSERT_LOW }, + [TOPDOWN] = { "top-down", DRM_MM_INSERT_HIGH }, + [EVICT] = { "evict", DRM_MM_INSERT_EVICT }, + {} +}; + +static bool assert_no_holes(struct kunit *test, const struct drm_mm *mm) +{ + struct drm_mm_node *hole; + u64 hole_start, __always_unused hole_end; + unsigned long count; + + count = 0; + drm_mm_for_each_hole(hole, mm, hole_start, hole_end) + count++; + if (count) { + KUNIT_FAIL(test, + "Expected to find no holes (after reserve), found %lu instead\n", count); + return false; + } + + drm_mm_for_each_node(hole, mm) { + if (drm_mm_hole_follows(hole)) { + KUNIT_FAIL(test, "Hole follows node, expected none!\n"); + return false; + } + } + + return true; +} + +static bool assert_one_hole(struct kunit *test, const struct drm_mm *mm, u64 start, u64 end) +{ + struct drm_mm_node *hole; + u64 hole_start, hole_end; + unsigned long count; + bool ok = true; + + if (end <= start) + return true; + + count = 0; + drm_mm_for_each_hole(hole, mm, hole_start, hole_end) { + if (start != hole_start || end != hole_end) { + if (ok) + KUNIT_FAIL(test, + "empty mm has incorrect hole, found (%llx, %llx), expect (%llx, %llx)\n", + hole_start, hole_end, start, end); + ok = false; + } + count++; + } + if (count != 1) { + KUNIT_FAIL(test, "Expected to find one hole, found %lu instead\n", count); + ok = false; + } + + return ok; +} + +static u64 misalignment(struct drm_mm_node *node, u64 alignment) +{ + u64 rem; + + if (!alignment) + return 0; + + div64_u64_rem(node->start, alignment, &rem); + return rem; +} + +static bool assert_node(struct kunit *test, struct drm_mm_node *node, struct drm_mm *mm, + u64 size, u64 alignment, unsigned long color) +{ + bool ok = true; + + if (!drm_mm_node_allocated(node) || node->mm != mm) { + KUNIT_FAIL(test, "node not allocated\n"); + ok = false; + } + + if (node->size != size) { + KUNIT_FAIL(test, "node has wrong size, found %llu, expected %llu\n", + node->size, size); + ok = false; + } + + if (misalignment(node, alignment)) { + KUNIT_FAIL(test, + "node is misaligned, start %llx rem %llu, expected alignment %llu\n", + node->start, misalignment(node, alignment), alignment); + ok = false; + } + + if (node->color != color) { + KUNIT_FAIL(test, "node has wrong color, found %lu, expected %lu\n", + node->color, color); + ok = false; + } + + return ok; +} + +static void drm_test_mm_init(struct kunit *test) +{ + const unsigned int size = 4096; + struct drm_mm mm; + struct drm_mm_node tmp; + + /* Start with some simple checks on initialising the struct drm_mm */ + memset(&mm, 0, sizeof(mm)); + KUNIT_ASSERT_FALSE_MSG(test, drm_mm_initialized(&mm), + "zeroed mm claims to be initialized\n"); + + memset(&mm, 0xff, sizeof(mm)); + drm_mm_init(&mm, 0, size); + if (!drm_mm_initialized(&mm)) { + KUNIT_FAIL(test, "mm claims not to be initialized\n"); + goto out; + } + + if (!drm_mm_clean(&mm)) { + KUNIT_FAIL(test, "mm not empty on creation\n"); + goto out; + } + + /* After creation, it should all be one massive hole */ + if (!assert_one_hole(test, &mm, 0, size)) { + KUNIT_FAIL(test, "mm not one hole on creation"); + goto out; + } + + memset(&tmp, 0, sizeof(tmp)); + tmp.start = 0; + tmp.size = size; + if (drm_mm_reserve_node(&mm, &tmp)) { + KUNIT_FAIL(test, "failed to reserve whole drm_mm\n"); + goto out; + } + + /* After filling the range entirely, there should be no holes */ + if (!assert_no_holes(test, &mm)) { + KUNIT_FAIL(test, "mm has holes when filled"); + goto out; + } + + /* And then after emptying it again, the massive hole should be back */ + drm_mm_remove_node(&tmp); + if (!assert_one_hole(test, &mm, 0, size)) { + KUNIT_FAIL(test, "mm does not have single hole after emptying"); + goto out; + } + +out: + drm_mm_takedown(&mm); +} + +static void drm_test_mm_debug(struct kunit *test) +{ + struct drm_printer p = drm_dbg_printer(NULL, DRM_UT_CORE, test->name); + struct drm_mm mm; + struct drm_mm_node nodes[2]; + + /* Create a small drm_mm with a couple of nodes and a few holes, and + * check that the debug iterator doesn't explode over a trivial drm_mm. + */ + drm_mm_init(&mm, 0, 4096); + + memset(nodes, 0, sizeof(nodes)); + nodes[0].start = 512; + nodes[0].size = 1024; + KUNIT_ASSERT_FALSE_MSG(test, drm_mm_reserve_node(&mm, &nodes[0]), + "failed to reserve node[0] {start=%lld, size=%lld)\n", + nodes[0].start, nodes[0].size); + + nodes[1].size = 1024; + nodes[1].start = 4096 - 512 - nodes[1].size; + KUNIT_ASSERT_FALSE_MSG(test, drm_mm_reserve_node(&mm, &nodes[1]), + "failed to reserve node[0] {start=%lld, size=%lld)\n", + nodes[0].start, nodes[0].size); + + drm_mm_print(&mm, &p); + KUNIT_SUCCEED(test); +} + +static bool expect_insert(struct kunit *test, struct drm_mm *mm, + struct drm_mm_node *node, u64 size, u64 alignment, unsigned long color, + const struct insert_mode *mode) +{ + int err; + + err = drm_mm_insert_node_generic(mm, node, + size, alignment, color, + mode->mode); + if (err) { + KUNIT_FAIL(test, + "insert (size=%llu, alignment=%llu, color=%lu, mode=%s) failed with err=%d\n", + size, alignment, color, mode->name, err); + return false; + } + + if (!assert_node(test, node, mm, size, alignment, color)) { + drm_mm_remove_node(node); + return false; + } + + return true; +} + +static void drm_test_mm_align_pot(struct kunit *test, int max) +{ + struct drm_mm mm; + struct drm_mm_node *node, *next; + int bit; + + /* Check that we can align to the full u64 address space */ + + drm_mm_init(&mm, 1, U64_MAX - 2); + + for (bit = max - 1; bit; bit--) { + u64 align, size; + + node = kzalloc_obj(*node); + if (!node) { + KUNIT_FAIL(test, "failed to allocate node"); + goto out; + } + + align = BIT_ULL(bit); + size = BIT_ULL(bit - 1) + 1; + if (!expect_insert(test, &mm, node, size, align, bit, &insert_modes[0])) { + KUNIT_FAIL(test, "insert failed with alignment=%llx [%d]", align, bit); + goto out; + } + + cond_resched(); + } + +out: + drm_mm_for_each_node_safe(node, next, &mm) { + drm_mm_remove_node(node); + kfree(node); + } + drm_mm_takedown(&mm); +} + +static void drm_test_mm_align32(struct kunit *test) +{ + drm_test_mm_align_pot(test, 32); +} + +static void drm_test_mm_align64(struct kunit *test) +{ + drm_test_mm_align_pot(test, 64); +} + +static void drm_test_mm_once(struct kunit *test, unsigned int mode) +{ + struct drm_mm mm; + struct drm_mm_node rsvd_lo, rsvd_hi, node; + + drm_mm_init(&mm, 0, 7); + + memset(&rsvd_lo, 0, sizeof(rsvd_lo)); + rsvd_lo.start = 1; + rsvd_lo.size = 1; + if (drm_mm_reserve_node(&mm, &rsvd_lo)) { + KUNIT_FAIL(test, "Could not reserve low node\n"); + goto err; + } + + memset(&rsvd_hi, 0, sizeof(rsvd_hi)); + rsvd_hi.start = 5; + rsvd_hi.size = 1; + if (drm_mm_reserve_node(&mm, &rsvd_hi)) { + KUNIT_FAIL(test, "Could not reserve low node\n"); + goto err_lo; + } + + if (!drm_mm_hole_follows(&rsvd_lo) || !drm_mm_hole_follows(&rsvd_hi)) { + KUNIT_FAIL(test, "Expected a hole after lo and high nodes!\n"); + goto err_hi; + } + + memset(&node, 0, sizeof(node)); + if (drm_mm_insert_node_generic(&mm, &node, 2, 0, 0, mode)) { + KUNIT_FAIL(test, "Could not insert the node into the available hole!\n"); + goto err_hi; + } + + drm_mm_remove_node(&node); +err_hi: + drm_mm_remove_node(&rsvd_hi); +err_lo: + drm_mm_remove_node(&rsvd_lo); +err: + drm_mm_takedown(&mm); +} + +static void drm_test_mm_lowest(struct kunit *test) +{ + drm_test_mm_once(test, DRM_MM_INSERT_LOW); +} + +static void drm_test_mm_highest(struct kunit *test) +{ + drm_test_mm_once(test, DRM_MM_INSERT_HIGH); +} + +static struct kunit_case drm_mm_tests[] = { + KUNIT_CASE(drm_test_mm_init), + KUNIT_CASE(drm_test_mm_debug), + KUNIT_CASE(drm_test_mm_align32), + KUNIT_CASE(drm_test_mm_align64), + KUNIT_CASE(drm_test_mm_lowest), + KUNIT_CASE(drm_test_mm_highest), + {} +}; + +static struct kunit_suite drm_mm_test_suite = { + .name = "drm_mm", + .test_cases = drm_mm_tests, +}; + +kunit_test_suite(drm_mm_test_suite); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Test cases for the drm_mm range manager"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_modes_test.c b/drivers/gpu/drm/tests/drm_modes_test.c new file mode 100644 index 000000000..f5b20f92d --- /dev/null +++ b/drivers/gpu/drm/tests/drm_modes_test.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Kunit test for drm_modes functions + */ + +#include <drm/drm_drv.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_modes.h> + +#include <kunit/test.h> + +#include <linux/units.h> + +struct drm_test_modes_priv { + struct drm_device *drm; + struct device *dev; +}; + +static int drm_test_modes_init(struct kunit *test) +{ + struct drm_test_modes_priv *priv; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv); + + priv->dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->dev); + + priv->drm = __drm_kunit_helper_alloc_drm_device(test, priv->dev, + sizeof(*priv->drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->drm); + + test->priv = priv; + + return 0; +} + +static void drm_test_modes_analog_tv_ntsc_480i(struct kunit *test) +{ + struct drm_test_modes_priv *priv = test->priv; + struct drm_display_mode *mode; + int ret; + + mode = drm_analog_tv_mode(priv->drm, + DRM_MODE_TV_MODE_NTSC, + 13500 * HZ_PER_KHZ, 720, 480, + true); + KUNIT_ASSERT_NOT_NULL(test, mode); + + ret = drm_kunit_add_mode_destroy_action(test, mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, drm_mode_vrefresh(mode), 60); + KUNIT_EXPECT_EQ(test, mode->hdisplay, 720); + + /* BT.601 defines hsync_start at 736 for 480i */ + KUNIT_EXPECT_EQ(test, mode->hsync_start, 736); + + /* + * The NTSC standard expects a line to take 63.556us. With a + * pixel clock of 13.5 MHz, a pixel takes around 74ns, so we + * need to have 63556ns / 74ns = 858. + * + * This is also mandated by BT.601. + */ + KUNIT_EXPECT_EQ(test, mode->htotal, 858); + + KUNIT_EXPECT_EQ(test, mode->vdisplay, 480); + KUNIT_EXPECT_EQ(test, mode->vtotal, 525); +} + +static void drm_test_modes_analog_tv_ntsc_480i_inlined(struct kunit *test) +{ + struct drm_test_modes_priv *priv = test->priv; + struct drm_display_mode *expected, *mode; + int ret; + + expected = drm_analog_tv_mode(priv->drm, + DRM_MODE_TV_MODE_NTSC, + 13500 * HZ_PER_KHZ, 720, 480, + true); + KUNIT_ASSERT_NOT_NULL(test, expected); + + ret = drm_kunit_add_mode_destroy_action(test, expected); + KUNIT_ASSERT_EQ(test, ret, 0); + + mode = drm_mode_analog_ntsc_480i(priv->drm); + KUNIT_ASSERT_NOT_NULL(test, mode); + + ret = drm_kunit_add_mode_destroy_action(test, mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_TRUE(test, drm_mode_equal(expected, mode)); +} + +static void drm_test_modes_analog_tv_pal_576i(struct kunit *test) +{ + struct drm_test_modes_priv *priv = test->priv; + struct drm_display_mode *mode; + int ret; + + mode = drm_analog_tv_mode(priv->drm, + DRM_MODE_TV_MODE_PAL, + 13500 * HZ_PER_KHZ, 720, 576, + true); + KUNIT_ASSERT_NOT_NULL(test, mode); + + ret = drm_kunit_add_mode_destroy_action(test, mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, drm_mode_vrefresh(mode), 50); + KUNIT_EXPECT_EQ(test, mode->hdisplay, 720); + + /* BT.601 defines hsync_start at 732 for 576i */ + KUNIT_EXPECT_EQ(test, mode->hsync_start, 732); + + /* + * The PAL standard expects a line to take 64us. With a pixel + * clock of 13.5 MHz, a pixel takes around 74ns, so we need to + * have 64000ns / 74ns = 864. + * + * This is also mandated by BT.601. + */ + KUNIT_EXPECT_EQ(test, mode->htotal, 864); + + KUNIT_EXPECT_EQ(test, mode->vdisplay, 576); + KUNIT_EXPECT_EQ(test, mode->vtotal, 625); +} + +static void drm_test_modes_analog_tv_pal_576i_inlined(struct kunit *test) +{ + struct drm_test_modes_priv *priv = test->priv; + struct drm_display_mode *expected, *mode; + int ret; + + expected = drm_analog_tv_mode(priv->drm, + DRM_MODE_TV_MODE_PAL, + 13500 * HZ_PER_KHZ, 720, 576, + true); + KUNIT_ASSERT_NOT_NULL(test, expected); + + ret = drm_kunit_add_mode_destroy_action(test, expected); + KUNIT_ASSERT_EQ(test, ret, 0); + + mode = drm_mode_analog_pal_576i(priv->drm); + KUNIT_ASSERT_NOT_NULL(test, mode); + + ret = drm_kunit_add_mode_destroy_action(test, mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_TRUE(test, drm_mode_equal(expected, mode)); +} + +static void drm_test_modes_analog_tv_mono_576i(struct kunit *test) +{ + struct drm_test_modes_priv *priv = test->priv; + struct drm_display_mode *mode; + int ret; + + mode = drm_analog_tv_mode(priv->drm, + DRM_MODE_TV_MODE_MONOCHROME, + 13500 * HZ_PER_KHZ, 720, 576, + true); + KUNIT_ASSERT_NOT_NULL(test, mode); + + ret = drm_kunit_add_mode_destroy_action(test, mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, drm_mode_vrefresh(mode), 50); + KUNIT_EXPECT_EQ(test, mode->hdisplay, 720); + + /* BT.601 defines hsync_start at 732 for 576i */ + KUNIT_EXPECT_EQ(test, mode->hsync_start, 732); + + /* + * The PAL standard expects a line to take 64us. With a pixel + * clock of 13.5 MHz, a pixel takes around 74ns, so we need to + * have 64000ns / 74ns = 864. + * + * This is also mandated by BT.601. + */ + KUNIT_EXPECT_EQ(test, mode->htotal, 864); + + KUNIT_EXPECT_EQ(test, mode->vdisplay, 576); + KUNIT_EXPECT_EQ(test, mode->vtotal, 625); +} + +static struct kunit_case drm_modes_analog_tv_tests[] = { + KUNIT_CASE(drm_test_modes_analog_tv_mono_576i), + KUNIT_CASE(drm_test_modes_analog_tv_ntsc_480i), + KUNIT_CASE(drm_test_modes_analog_tv_ntsc_480i_inlined), + KUNIT_CASE(drm_test_modes_analog_tv_pal_576i), + KUNIT_CASE(drm_test_modes_analog_tv_pal_576i_inlined), + { } +}; + +static struct kunit_suite drm_modes_analog_tv_test_suite = { + .name = "drm_modes_analog_tv", + .init = drm_test_modes_init, + .test_cases = drm_modes_analog_tv_tests, +}; + +kunit_test_suite(drm_modes_analog_tv_test_suite); + +MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>"); +MODULE_DESCRIPTION("Kunit test for drm_modes functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_panic_test.c b/drivers/gpu/drm/tests/drm_panic_test.c new file mode 100644 index 000000000..ad2f3a2f9 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_panic_test.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 or MIT +/* + * Copyright (c) 2025 Red Hat. + * Author: Jocelyn Falempe <jfalempe@redhat.com> + * + * KUNIT tests for drm panic + */ + +#include <drm/drm_fourcc.h> +#include <drm/drm_panic.h> + +#include <kunit/test.h> + +#include <linux/units.h> +#include <linux/vmalloc.h> + +/* Check the framebuffer color only if the panic colors are the default */ +#if (CONFIG_DRM_PANIC_BACKGROUND_COLOR == 0 && \ + CONFIG_DRM_PANIC_FOREGROUND_COLOR == 0xffffff) + +static void drm_panic_check_color_byte(struct kunit *test, u8 b) +{ + KUNIT_EXPECT_TRUE(test, (b == 0 || b == 0xff)); +} +#else +static void drm_panic_check_color_byte(struct kunit *test, u8 b) {} +#endif + +struct drm_test_mode { + const int width; + const int height; + const u32 format; + void (*draw_screen)(struct drm_scanout_buffer *sb); + const char *fname; +}; + +/* + * Run all tests for the 3 panic screens: user, kmsg and qr_code + */ +#define DRM_TEST_MODE_LIST(func) \ + DRM_PANIC_TEST_MODE(1024, 768, DRM_FORMAT_XRGB8888, func) \ + DRM_PANIC_TEST_MODE(300, 200, DRM_FORMAT_XRGB8888, func) \ + DRM_PANIC_TEST_MODE(1920, 1080, DRM_FORMAT_XRGB8888, func) \ + DRM_PANIC_TEST_MODE(1024, 768, DRM_FORMAT_RGB565, func) \ + DRM_PANIC_TEST_MODE(1024, 768, DRM_FORMAT_RGB888, func) \ + +#define DRM_PANIC_TEST_MODE(w, h, f, name) { \ + .width = w, \ + .height = h, \ + .format = f, \ + .draw_screen = draw_panic_screen_##name, \ + .fname = #name, \ + }, \ + +static const struct drm_test_mode drm_test_modes_cases[] = { + DRM_TEST_MODE_LIST(user) + DRM_TEST_MODE_LIST(kmsg) +#if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE) + DRM_TEST_MODE_LIST(qr_code) +#endif +}; + +#undef DRM_PANIC_TEST_MODE + +static int drm_test_panic_init(struct kunit *test) +{ + struct drm_scanout_buffer *priv; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv); + + test->priv = priv; + + drm_panic_set_description("Kunit testing"); + + return 0; +} + +/* + * Test drawing the panic screen, using a memory mapped framebuffer + * Set the whole buffer to 0xa5, and then check that all pixels have been + * written. + */ +static void drm_test_panic_screen_user_map(struct kunit *test) +{ + struct drm_scanout_buffer *sb = test->priv; + const struct drm_test_mode *params = test->param_value; + char *fb; + int fb_size; + int i; + + sb->format = drm_format_info(params->format); + fb_size = params->width * params->height * sb->format->cpp[0]; + + fb = vmalloc(fb_size); + KUNIT_ASSERT_NOT_NULL(test, fb); + + memset(fb, 0xa5, fb_size); + + iosys_map_set_vaddr(&sb->map[0], fb); + sb->width = params->width; + sb->height = params->height; + sb->pitch[0] = params->width * sb->format->cpp[0]; + + params->draw_screen(sb); + + for (i = 0; i < fb_size; i++) + drm_panic_check_color_byte(test, fb[i]); + + vfree(fb); +} + +/* + * Test drawing the panic screen, using a list of pages framebuffer + * Set the whole buffer to 0xa5, and then check that all pixels have been + * written. + */ +static void drm_test_panic_screen_user_page(struct kunit *test) +{ + struct drm_scanout_buffer *sb = test->priv; + const struct drm_test_mode *params = test->param_value; + int fb_size, p, i, npages; + struct page **pages; + u8 *vaddr; + + sb->format = drm_format_info(params->format); + fb_size = params->width * params->height * sb->format->cpp[0]; + npages = DIV_ROUND_UP(fb_size, PAGE_SIZE); + + pages = kmalloc_objs(struct page *, npages); + KUNIT_ASSERT_NOT_NULL(test, pages); + + for (p = 0; p < npages; p++) { + pages[p] = alloc_page(GFP_KERNEL); + if (!pages[p]) { + npages = p - 1; + KUNIT_FAIL(test, "Can't allocate page\n"); + goto free_pages; + } + vaddr = kmap_local_page(pages[p]); + memset(vaddr, 0xa5, PAGE_SIZE); + kunmap_local(vaddr); + } + sb->pages = pages; + sb->width = params->width; + sb->height = params->height; + sb->pitch[0] = params->width * sb->format->cpp[0]; + + params->draw_screen(sb); + + for (p = 0; p < npages; p++) { + int bytes_in_page = (p == npages - 1) ? fb_size - p * PAGE_SIZE : PAGE_SIZE; + + vaddr = kmap_local_page(pages[p]); + for (i = 0; i < bytes_in_page; i++) + drm_panic_check_color_byte(test, vaddr[i]); + + kunmap_local(vaddr); + } + +free_pages: + for (p = 0; p < npages; p++) + __free_page(pages[p]); + kfree(pages); +} + +static void drm_test_panic_set_pixel(struct drm_scanout_buffer *sb, + unsigned int x, + unsigned int y, + u32 color) +{ + struct kunit *test = (struct kunit *)sb->private; + + KUNIT_ASSERT_TRUE(test, x < sb->width && y < sb->height); +} + +/* + * Test drawing the panic screen, using the set_pixel callback + * Check that all calls to set_pixel() are within the framebuffer + */ +static void drm_test_panic_screen_user_set_pixel(struct kunit *test) +{ + struct drm_scanout_buffer *sb = test->priv; + const struct drm_test_mode *params = test->param_value; + + sb->format = drm_format_info(params->format); + sb->set_pixel = drm_test_panic_set_pixel; + sb->width = params->width; + sb->height = params->height; + sb->private = test; + + params->draw_screen(sb); +} + +static void drm_test_panic_desc(const struct drm_test_mode *t, char *desc) +{ + sprintf(desc, "Panic screen %s, mode: %d x %d \t%p4cc", + t->fname, t->width, t->height, &t->format); +} + +KUNIT_ARRAY_PARAM(drm_test_panic_screen_user_map, drm_test_modes_cases, drm_test_panic_desc); +KUNIT_ARRAY_PARAM(drm_test_panic_screen_user_page, drm_test_modes_cases, drm_test_panic_desc); +KUNIT_ARRAY_PARAM(drm_test_panic_screen_user_set_pixel, drm_test_modes_cases, drm_test_panic_desc); + +static struct kunit_case drm_panic_screen_user_test[] = { + KUNIT_CASE_PARAM(drm_test_panic_screen_user_map, + drm_test_panic_screen_user_map_gen_params), + KUNIT_CASE_PARAM(drm_test_panic_screen_user_page, + drm_test_panic_screen_user_page_gen_params), + KUNIT_CASE_PARAM(drm_test_panic_screen_user_set_pixel, + drm_test_panic_screen_user_set_pixel_gen_params), + { } +}; + +static struct kunit_suite drm_panic_suite = { + .name = "drm_panic", + .init = drm_test_panic_init, + .test_cases = drm_panic_screen_user_test, +}; + +kunit_test_suite(drm_panic_suite); diff --git a/drivers/gpu/drm/tests/drm_plane_helper_test.c b/drivers/gpu/drm/tests/drm_plane_helper_test.c new file mode 100644 index 000000000..7e975a3f4 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_plane_helper_test.c @@ -0,0 +1,319 @@ +// 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"); diff --git a/drivers/gpu/drm/tests/drm_probe_helper_test.c b/drivers/gpu/drm/tests/drm_probe_helper_test.c new file mode 100644 index 000000000..db0e4f5df --- /dev/null +++ b/drivers/gpu/drm/tests/drm_probe_helper_test.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Kunit test for drm_probe_helper functions + */ + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_mode.h> +#include <drm/drm_modes.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_probe_helper.h> + +#include <kunit/test.h> + +struct drm_probe_helper_test_priv { + struct drm_device *drm; + struct device *dev; + struct drm_connector connector; +}; + +static const struct drm_connector_helper_funcs drm_probe_helper_connector_helper_funcs = { +}; + +static const struct drm_connector_funcs drm_probe_helper_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_probe_helper_test_init(struct kunit *test) +{ + struct drm_probe_helper_test_priv *priv; + struct drm_connector *connector; + int ret; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv); + test->priv = priv; + + priv->dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->dev); + + priv->drm = __drm_kunit_helper_alloc_drm_device(test, priv->dev, + sizeof(*priv->drm), 0, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->drm); + + connector = &priv->connector; + ret = drmm_connector_init(priv->drm, connector, + &drm_probe_helper_connector_funcs, + DRM_MODE_CONNECTOR_Unknown, + NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_connector_helper_add(connector, &drm_probe_helper_connector_helper_funcs); + + return 0; +} + +typedef struct drm_display_mode *(*expected_mode_func_t)(struct drm_device *); + +struct drm_connector_helper_tv_get_modes_test { + const char *name; + unsigned int supported_tv_modes; + enum drm_connector_tv_mode default_mode; + bool cmdline; + enum drm_connector_tv_mode cmdline_mode; + expected_mode_func_t *expected_modes; + unsigned int num_expected_modes; +}; + +#define _TV_MODE_TEST(_name, _supported, _default, _cmdline, _cmdline_mode, ...) \ + { \ + .name = _name, \ + .supported_tv_modes = _supported, \ + .default_mode = _default, \ + .cmdline = _cmdline, \ + .cmdline_mode = _cmdline_mode, \ + .expected_modes = (expected_mode_func_t[]) { __VA_ARGS__ }, \ + .num_expected_modes = sizeof((expected_mode_func_t[]) { __VA_ARGS__ }) / \ + (sizeof(expected_mode_func_t)), \ + } + +#define TV_MODE_TEST(_name, _supported, _default, ...) \ + _TV_MODE_TEST(_name, _supported, _default, false, 0, __VA_ARGS__) + +#define TV_MODE_TEST_CMDLINE(_name, _supported, _default, _cmdline, ...) \ + _TV_MODE_TEST(_name, _supported, _default, true, _cmdline, __VA_ARGS__) + +static void +drm_test_connector_helper_tv_get_modes_check(struct kunit *test) +{ + const struct drm_connector_helper_tv_get_modes_test *params = test->param_value; + struct drm_probe_helper_test_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + struct drm_cmdline_mode *cmdline = &connector->cmdline_mode; + struct drm_display_mode *mode; + struct drm_display_mode *expected; + size_t len; + int ret; + + if (params->cmdline) { + cmdline->tv_mode_specified = true; + cmdline->tv_mode = params->cmdline_mode; + } + + ret = drm_mode_create_tv_properties(priv->drm, params->supported_tv_modes); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_object_attach_property(&connector->base, + priv->drm->mode_config.tv_mode_property, + params->default_mode); + + mutex_lock(&priv->drm->mode_config.mutex); + + ret = drm_connector_helper_tv_get_modes(connector); + KUNIT_EXPECT_EQ(test, ret, params->num_expected_modes); + + len = 0; + list_for_each_entry(mode, &connector->probed_modes, head) + len++; + KUNIT_EXPECT_EQ(test, len, params->num_expected_modes); + + if (params->num_expected_modes >= 1) { + mode = list_first_entry_or_null(&connector->probed_modes, + struct drm_display_mode, head); + KUNIT_ASSERT_NOT_NULL(test, mode); + + expected = params->expected_modes[0](priv->drm); + KUNIT_ASSERT_NOT_NULL(test, expected); + + KUNIT_EXPECT_TRUE(test, drm_mode_equal(mode, expected)); + KUNIT_EXPECT_TRUE(test, mode->type & DRM_MODE_TYPE_PREFERRED); + + ret = drm_kunit_add_mode_destroy_action(test, expected); + KUNIT_ASSERT_EQ(test, ret, 0); + } + + if (params->num_expected_modes >= 2) { + mode = list_next_entry(mode, head); + KUNIT_ASSERT_NOT_NULL(test, mode); + + expected = params->expected_modes[1](priv->drm); + KUNIT_ASSERT_NOT_NULL(test, expected); + + KUNIT_EXPECT_TRUE(test, drm_mode_equal(mode, expected)); + KUNIT_EXPECT_FALSE(test, mode->type & DRM_MODE_TYPE_PREFERRED); + + ret = drm_kunit_add_mode_destroy_action(test, expected); + KUNIT_ASSERT_EQ(test, ret, 0); + } + + mutex_unlock(&priv->drm->mode_config.mutex); +} + +static const +struct drm_connector_helper_tv_get_modes_test drm_connector_helper_tv_get_modes_tests[] = { + { .name = "None" }, + TV_MODE_TEST("PAL", + BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_PAL, + drm_mode_analog_pal_576i), + TV_MODE_TEST("NTSC", + BIT(DRM_MODE_TV_MODE_NTSC), + DRM_MODE_TV_MODE_NTSC, + drm_mode_analog_ntsc_480i), + TV_MODE_TEST("Both, NTSC Default", + BIT(DRM_MODE_TV_MODE_NTSC) | BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_NTSC, + drm_mode_analog_ntsc_480i, drm_mode_analog_pal_576i), + TV_MODE_TEST("Both, PAL Default", + BIT(DRM_MODE_TV_MODE_NTSC) | BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_PAL, + drm_mode_analog_pal_576i, drm_mode_analog_ntsc_480i), + TV_MODE_TEST_CMDLINE("Both, NTSC Default, with PAL on command-line", + BIT(DRM_MODE_TV_MODE_NTSC) | BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_NTSC, + DRM_MODE_TV_MODE_PAL, + drm_mode_analog_pal_576i, drm_mode_analog_ntsc_480i), + TV_MODE_TEST_CMDLINE("Both, PAL Default, with NTSC on command-line", + BIT(DRM_MODE_TV_MODE_NTSC) | BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_PAL, + DRM_MODE_TV_MODE_NTSC, + drm_mode_analog_ntsc_480i, drm_mode_analog_pal_576i), +}; + +static void +drm_connector_helper_tv_get_modes_desc(const struct drm_connector_helper_tv_get_modes_test *t, + char *desc) +{ + sprintf(desc, "%s", t->name); +} + +KUNIT_ARRAY_PARAM(drm_connector_helper_tv_get_modes, + drm_connector_helper_tv_get_modes_tests, + drm_connector_helper_tv_get_modes_desc); + +static struct kunit_case drm_test_connector_helper_tv_get_modes_tests[] = { + KUNIT_CASE_PARAM(drm_test_connector_helper_tv_get_modes_check, + drm_connector_helper_tv_get_modes_gen_params), + { } +}; + +static struct kunit_suite drm_test_connector_helper_tv_get_modes_suite = { + .name = "drm_connector_helper_tv_get_modes", + .init = drm_probe_helper_test_init, + .test_cases = drm_test_connector_helper_tv_get_modes_tests, +}; + +kunit_test_suite(drm_test_connector_helper_tv_get_modes_suite); + +MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>"); +MODULE_DESCRIPTION("Kunit test for drm_probe_helper functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_rect_test.c b/drivers/gpu/drm/tests/drm_rect_test.c new file mode 100644 index 000000000..3402f993d --- /dev/null +++ b/drivers/gpu/drm/tests/drm_rect_test.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test cases for the drm_rect functions + * + * Copyright (c) 2022 Maíra Canal <mairacanal@riseup.net> + */ + +#include <kunit/test.h> + +#include <drm/drm_rect.h> +#include <drm/drm_mode.h> + +#include <linux/limits.h> +#include <linux/string_helpers.h> +#include <linux/errno.h> + +static void drm_rect_compare(struct kunit *test, const struct drm_rect *r, + const struct drm_rect *expected) +{ + KUNIT_EXPECT_EQ(test, r->x1, expected->x1); + KUNIT_EXPECT_EQ(test, r->y1, expected->y1); + KUNIT_EXPECT_EQ(test, drm_rect_width(r), drm_rect_width(expected)); + KUNIT_EXPECT_EQ(test, drm_rect_height(r), drm_rect_height(expected)); +} + +static void drm_test_rect_clip_scaled_div_by_zero(struct kunit *test) +{ + struct drm_rect src, dst, clip; + bool visible; + + /* + * Make sure we don't divide by zero when dst + * width/height is zero and dst and clip do not intersect. + */ + drm_rect_init(&src, 0, 0, 0, 0); + drm_rect_init(&dst, 0, 0, 0, 0); + drm_rect_init(&clip, 1, 1, 1, 1); + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, visible, "Destination not be visible\n"); + KUNIT_EXPECT_FALSE_MSG(test, drm_rect_visible(&src), "Source should not be visible\n"); + + drm_rect_init(&src, 0, 0, 0, 0); + drm_rect_init(&dst, 3, 3, 0, 0); + drm_rect_init(&clip, 1, 1, 1, 1); + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, visible, "Destination not be visible\n"); + KUNIT_EXPECT_FALSE_MSG(test, drm_rect_visible(&src), "Source should not be visible\n"); +} + +static void drm_test_rect_clip_scaled_not_clipped(struct kunit *test) +{ + struct drm_rect src, dst, clip; + bool visible; + + /* 1:1 scaling */ + drm_rect_init(&src, 0, 0, 1 << 16, 1 << 16); + drm_rect_init(&dst, 0, 0, 1, 1); + drm_rect_init(&clip, 0, 0, 1, 1); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 0 || src.x2 != 1 << 16 || + src.y1 != 0 || src.y2 != 1 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 0 || dst.x2 != 1 || + dst.y1 != 0 || dst.y2 != 1, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); + + /* 2:1 scaling */ + drm_rect_init(&src, 0, 0, 2 << 16, 2 << 16); + drm_rect_init(&dst, 0, 0, 1, 1); + drm_rect_init(&clip, 0, 0, 1, 1); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 0 || src.x2 != 2 << 16 || + src.y1 != 0 || src.y2 != 2 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 0 || dst.x2 != 1 || + dst.y1 != 0 || dst.y2 != 1, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); + + /* 1:2 scaling */ + drm_rect_init(&src, 0, 0, 1 << 16, 1 << 16); + drm_rect_init(&dst, 0, 0, 2, 2); + drm_rect_init(&clip, 0, 0, 2, 2); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 0 || src.x2 != 1 << 16 || + src.y1 != 0 || src.y2 != 1 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 0 || dst.x2 != 2 || + dst.y1 != 0 || dst.y2 != 2, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); +} + +static void drm_test_rect_clip_scaled_clipped(struct kunit *test) +{ + struct drm_rect src, dst, clip; + bool visible; + + /* 1:1 scaling top/left clip */ + drm_rect_init(&src, 0, 0, 2 << 16, 2 << 16); + drm_rect_init(&dst, 0, 0, 2, 2); + drm_rect_init(&clip, 0, 0, 1, 1); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 0 || src.x2 != 1 << 16 || + src.y1 != 0 || src.y2 != 1 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 0 || dst.x2 != 1 || + dst.y1 != 0 || dst.y2 != 1, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); + + /* 1:1 scaling bottom/right clip */ + drm_rect_init(&src, 0, 0, 2 << 16, 2 << 16); + drm_rect_init(&dst, 0, 0, 2, 2); + drm_rect_init(&clip, 1, 1, 1, 1); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 1 << 16 || src.x2 != 2 << 16 || + src.y1 != 1 << 16 || src.y2 != 2 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 1 || dst.x2 != 2 || dst.y1 != 1 || + dst.y2 != 2, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); + + /* 2:1 scaling top/left clip */ + drm_rect_init(&src, 0, 0, 4 << 16, 4 << 16); + drm_rect_init(&dst, 0, 0, 2, 2); + drm_rect_init(&clip, 0, 0, 1, 1); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 0 || src.x2 != 2 << 16 || + src.y1 != 0 || src.y2 != 2 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 0 || dst.x2 != 1 || dst.y1 != 0 || + dst.y2 != 1, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); + + /* 2:1 scaling bottom/right clip */ + drm_rect_init(&src, 0, 0, 4 << 16, 4 << 16); + drm_rect_init(&dst, 0, 0, 2, 2); + drm_rect_init(&clip, 1, 1, 1, 1); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 2 << 16 || src.x2 != 4 << 16 || + src.y1 != 2 << 16 || src.y2 != 4 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 1 || dst.x2 != 2 || dst.y1 != 1 || + dst.y2 != 2, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); + + /* 1:2 scaling top/left clip */ + drm_rect_init(&src, 0, 0, 2 << 16, 2 << 16); + drm_rect_init(&dst, 0, 0, 4, 4); + drm_rect_init(&clip, 0, 0, 2, 2); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 0 || src.x2 != 1 << 16 || + src.y1 != 0 || src.y2 != 1 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 0 || dst.x2 != 2 || dst.y1 != 0 || + dst.y2 != 2, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); + + /* 1:2 scaling bottom/right clip */ + drm_rect_init(&src, 0, 0, 2 << 16, 2 << 16); + drm_rect_init(&dst, 0, 0, 4, 4); + drm_rect_init(&clip, 2, 2, 2, 2); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, src.x1 != 1 << 16 || src.x2 != 2 << 16 || + src.y1 != 1 << 16 || src.y2 != 2 << 16, "Source badly clipped\n"); + KUNIT_EXPECT_FALSE_MSG(test, dst.x1 != 2 || dst.x2 != 4 || dst.y1 != 2 || + dst.y2 != 4, "Destination badly clipped\n"); + KUNIT_EXPECT_TRUE_MSG(test, visible, "Destination should be visible\n"); + KUNIT_EXPECT_TRUE_MSG(test, drm_rect_visible(&src), "Source should be visible\n"); +} + +static void drm_test_rect_clip_scaled_signed_vs_unsigned(struct kunit *test) +{ + struct drm_rect src, dst, clip; + bool visible; + + /* + * 'clip.x2 - dst.x1 >= dst width' could result a negative + * src rectangle width which is no longer expected by the + * code as it's using unsigned types. This could lead to + * the clipped source rectangle appering visible when it + * should have been fully clipped. Make sure both rectangles + * end up invisible. + */ + drm_rect_init(&src, 0, 0, INT_MAX, INT_MAX); + drm_rect_init(&dst, 0, 0, 2, 2); + drm_rect_init(&clip, 3, 3, 1, 1); + + visible = drm_rect_clip_scaled(&src, &dst, &clip); + + KUNIT_EXPECT_FALSE_MSG(test, visible, "Destination should not be visible\n"); + KUNIT_EXPECT_FALSE_MSG(test, drm_rect_visible(&src), "Source should not be visible\n"); +} + +struct drm_rect_intersect_case { + const char *description; + struct drm_rect r1, r2; + bool should_be_visible; + struct drm_rect expected_intersection; +}; + +static const struct drm_rect_intersect_case drm_rect_intersect_cases[] = { + { + .description = "top-left x bottom-right", + .r1 = DRM_RECT_INIT(1, 1, 2, 2), + .r2 = DRM_RECT_INIT(0, 0, 2, 2), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(1, 1, 1, 1), + }, + { + .description = "top-right x bottom-left", + .r1 = DRM_RECT_INIT(0, 0, 2, 2), + .r2 = DRM_RECT_INIT(1, -1, 2, 2), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(1, 0, 1, 1), + }, + { + .description = "bottom-left x top-right", + .r1 = DRM_RECT_INIT(1, -1, 2, 2), + .r2 = DRM_RECT_INIT(0, 0, 2, 2), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(1, 0, 1, 1), + }, + { + .description = "bottom-right x top-left", + .r1 = DRM_RECT_INIT(0, 0, 2, 2), + .r2 = DRM_RECT_INIT(1, 1, 2, 2), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(1, 1, 1, 1), + }, + { + .description = "right x left", + .r1 = DRM_RECT_INIT(0, 0, 2, 1), + .r2 = DRM_RECT_INIT(1, 0, 3, 1), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(1, 0, 1, 1), + }, + { + .description = "left x right", + .r1 = DRM_RECT_INIT(1, 0, 3, 1), + .r2 = DRM_RECT_INIT(0, 0, 2, 1), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(1, 0, 1, 1), + }, + { + .description = "up x bottom", + .r1 = DRM_RECT_INIT(0, 0, 1, 2), + .r2 = DRM_RECT_INIT(0, -1, 1, 3), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(0, 0, 1, 2), + }, + { + .description = "bottom x up", + .r1 = DRM_RECT_INIT(0, -1, 1, 3), + .r2 = DRM_RECT_INIT(0, 0, 1, 2), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(0, 0, 1, 2), + }, + { + .description = "touching corner", + .r1 = DRM_RECT_INIT(0, 0, 1, 1), + .r2 = DRM_RECT_INIT(1, 1, 2, 2), + .should_be_visible = false, + .expected_intersection = DRM_RECT_INIT(1, 1, 0, 0), + }, + { + .description = "touching side", + .r1 = DRM_RECT_INIT(0, 0, 1, 1), + .r2 = DRM_RECT_INIT(1, 0, 1, 1), + .should_be_visible = false, + .expected_intersection = DRM_RECT_INIT(1, 0, 0, 1), + }, + { + .description = "equal rects", + .r1 = DRM_RECT_INIT(0, 0, 2, 2), + .r2 = DRM_RECT_INIT(0, 0, 2, 2), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(0, 0, 2, 2), + }, + { + .description = "inside another", + .r1 = DRM_RECT_INIT(0, 0, 2, 2), + .r2 = DRM_RECT_INIT(1, 1, 1, 1), + .should_be_visible = true, + .expected_intersection = DRM_RECT_INIT(1, 1, 1, 1), + }, + { + .description = "far away", + .r1 = DRM_RECT_INIT(0, 0, 1, 1), + .r2 = DRM_RECT_INIT(3, 6, 1, 1), + .should_be_visible = false, + .expected_intersection = DRM_RECT_INIT(3, 6, -2, -5), + }, + { + .description = "points intersecting", + .r1 = DRM_RECT_INIT(5, 10, 0, 0), + .r2 = DRM_RECT_INIT(5, 10, 0, 0), + .should_be_visible = false, + .expected_intersection = DRM_RECT_INIT(5, 10, 0, 0), + }, + { + .description = "points not intersecting", + .r1 = DRM_RECT_INIT(0, 0, 0, 0), + .r2 = DRM_RECT_INIT(5, 10, 0, 0), + .should_be_visible = false, + .expected_intersection = DRM_RECT_INIT(5, 10, -5, -10), + }, +}; + +static void drm_rect_intersect_case_desc(const struct drm_rect_intersect_case *t, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, + "%s: " DRM_RECT_FMT " x " DRM_RECT_FMT, + t->description, DRM_RECT_ARG(&t->r1), DRM_RECT_ARG(&t->r2)); +} + +KUNIT_ARRAY_PARAM(drm_rect_intersect, drm_rect_intersect_cases, drm_rect_intersect_case_desc); + +static void drm_test_rect_intersect(struct kunit *test) +{ + const struct drm_rect_intersect_case *params = test->param_value; + struct drm_rect r1_aux = params->r1; + bool visible; + + visible = drm_rect_intersect(&r1_aux, ¶ms->r2); + + KUNIT_EXPECT_EQ(test, visible, params->should_be_visible); + drm_rect_compare(test, &r1_aux, ¶ms->expected_intersection); +} + +struct drm_rect_scale_case { + const char *name; + struct drm_rect src, dst; + int min_range, max_range; + int expected_scaling_factor; +}; + +static const struct drm_rect_scale_case drm_rect_scale_cases[] = { + { + .name = "normal use", + .src = DRM_RECT_INIT(0, 0, 2 << 16, 2 << 16), + .dst = DRM_RECT_INIT(0, 0, 1 << 16, 1 << 16), + .min_range = 0, .max_range = INT_MAX, + .expected_scaling_factor = 2, + }, + { + .name = "out of max range", + .src = DRM_RECT_INIT(0, 0, 10 << 16, 10 << 16), + .dst = DRM_RECT_INIT(0, 0, 1 << 16, 1 << 16), + .min_range = 3, .max_range = 5, + .expected_scaling_factor = -ERANGE, + }, + { + .name = "out of min range", + .src = DRM_RECT_INIT(0, 0, 2 << 16, 2 << 16), + .dst = DRM_RECT_INIT(0, 0, 1 << 16, 1 << 16), + .min_range = 3, .max_range = 5, + .expected_scaling_factor = -ERANGE, + }, + { + .name = "zero dst", + .src = DRM_RECT_INIT(0, 0, 2 << 16, 2 << 16), + .dst = DRM_RECT_INIT(0, 0, 0 << 16, 0 << 16), + .min_range = 0, .max_range = INT_MAX, + .expected_scaling_factor = 0, + }, + { + .name = "negative src", + .src = DRM_RECT_INIT(0, 0, -(1 << 16), -(1 << 16)), + .dst = DRM_RECT_INIT(0, 0, 1 << 16, 1 << 16), + .min_range = 0, .max_range = INT_MAX, + .expected_scaling_factor = -EINVAL, + }, + { + .name = "negative dst", + .src = DRM_RECT_INIT(0, 0, 1 << 16, 1 << 16), + .dst = DRM_RECT_INIT(0, 0, -(1 << 16), -(1 << 16)), + .min_range = 0, .max_range = INT_MAX, + .expected_scaling_factor = -EINVAL, + }, +}; + +static void drm_rect_scale_case_desc(const struct drm_rect_scale_case *t, char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(drm_rect_scale, drm_rect_scale_cases, drm_rect_scale_case_desc); + +static void drm_test_rect_calc_hscale(struct kunit *test) +{ + const struct drm_rect_scale_case *params = test->param_value; + int expected_warnings = params->expected_scaling_factor == -EINVAL; + int scaling_factor = INT_MIN; + + /* + * Without CONFIG_BUG, WARN_ON() is a no-op and the suppressed warning + * count stays zero, failing the assertion. + */ + if (expected_warnings && !IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + /* + * drm_rect_calc_hscale() generates a warning backtrace whenever bad + * parameters are passed to it. This affects unit tests with -EINVAL + * error code in expected_scaling_factor. + */ + kunit_warning_suppress(test) { + scaling_factor = drm_rect_calc_hscale(¶ms->src, ¶ms->dst, + params->min_range, + params->max_range); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected_warnings); + } + + KUNIT_EXPECT_EQ(test, scaling_factor, params->expected_scaling_factor); +} + +static void drm_test_rect_calc_vscale(struct kunit *test) +{ + const struct drm_rect_scale_case *params = test->param_value; + int expected_warnings = params->expected_scaling_factor == -EINVAL; + int scaling_factor = INT_MIN; + + /* + * Without CONFIG_BUG, WARN_ON() is a no-op and the suppressed warning + * count stays zero, failing the assertion. + */ + if (expected_warnings && !IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + /* + * drm_rect_calc_vscale() generates a warning backtrace whenever bad + * parameters are passed to it. This affects unit tests with -EINVAL + * error code in expected_scaling_factor. + */ + kunit_warning_suppress(test) { + scaling_factor = drm_rect_calc_vscale(¶ms->src, ¶ms->dst, + params->min_range, params->max_range); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected_warnings); + } + + KUNIT_EXPECT_EQ(test, scaling_factor, params->expected_scaling_factor); +} + +struct drm_rect_rotate_case { + const char *name; + unsigned int rotation; + struct drm_rect rect; + int width, height; + struct drm_rect expected; +}; + +static const struct drm_rect_rotate_case drm_rect_rotate_cases[] = { + { + .name = "reflect-x", + .rotation = DRM_MODE_REFLECT_X, + .rect = DRM_RECT_INIT(0, 0, 5, 5), + .width = 5, .height = 10, + .expected = DRM_RECT_INIT(0, 0, 5, 5), + }, + { + .name = "reflect-y", + .rotation = DRM_MODE_REFLECT_Y, + .rect = DRM_RECT_INIT(2, 0, 5, 5), + .width = 5, .height = 10, + .expected = DRM_RECT_INIT(2, 5, 5, 5), + }, + { + .name = "rotate-0", + .rotation = DRM_MODE_ROTATE_0, + .rect = DRM_RECT_INIT(0, 2, 5, 5), + .width = 5, .height = 10, + .expected = DRM_RECT_INIT(0, 2, 5, 5), + }, + { + .name = "rotate-90", + .rotation = DRM_MODE_ROTATE_90, + .rect = DRM_RECT_INIT(0, 0, 5, 10), + .width = 5, .height = 10, + .expected = DRM_RECT_INIT(0, 0, 10, 5), + }, + { + .name = "rotate-180", + .rotation = DRM_MODE_ROTATE_180, + .rect = DRM_RECT_INIT(11, 3, 5, 10), + .width = 5, .height = 10, + .expected = DRM_RECT_INIT(-11, -3, 5, 10), + }, + { + .name = "rotate-270", + .rotation = DRM_MODE_ROTATE_270, + .rect = DRM_RECT_INIT(6, 3, 5, 10), + .width = 5, .height = 10, + .expected = DRM_RECT_INIT(-3, 6, 10, 5), + }, +}; + +static void drm_rect_rotate_case_desc(const struct drm_rect_rotate_case *t, char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(drm_rect_rotate, drm_rect_rotate_cases, drm_rect_rotate_case_desc); + +static void drm_test_rect_rotate(struct kunit *test) +{ + const struct drm_rect_rotate_case *params = test->param_value; + struct drm_rect r = params->rect; + + drm_rect_rotate(&r, params->width, params->height, params->rotation); + + drm_rect_compare(test, &r, ¶ms->expected); +} + +static void drm_test_rect_rotate_inv(struct kunit *test) +{ + const struct drm_rect_rotate_case *params = test->param_value; + struct drm_rect r = params->expected; + + drm_rect_rotate_inv(&r, params->width, params->height, params->rotation); + + drm_rect_compare(test, &r, ¶ms->rect); +} + +static struct kunit_case drm_rect_tests[] = { + KUNIT_CASE(drm_test_rect_clip_scaled_div_by_zero), + KUNIT_CASE(drm_test_rect_clip_scaled_not_clipped), + KUNIT_CASE(drm_test_rect_clip_scaled_clipped), + KUNIT_CASE(drm_test_rect_clip_scaled_signed_vs_unsigned), + KUNIT_CASE_PARAM(drm_test_rect_intersect, drm_rect_intersect_gen_params), + KUNIT_CASE_PARAM(drm_test_rect_calc_hscale, drm_rect_scale_gen_params), + KUNIT_CASE_PARAM(drm_test_rect_calc_vscale, drm_rect_scale_gen_params), + KUNIT_CASE_PARAM(drm_test_rect_rotate, drm_rect_rotate_gen_params), + KUNIT_CASE_PARAM(drm_test_rect_rotate_inv, drm_rect_rotate_gen_params), + { } +}; + +static struct kunit_suite drm_rect_test_suite = { + .name = "drm_rect", + .test_cases = drm_rect_tests, +}; + +kunit_test_suite(drm_rect_test_suite); + +MODULE_DESCRIPTION("Test cases for the drm_rect functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tests/drm_sysfb_modeset_test.c b/drivers/gpu/drm/tests/drm_sysfb_modeset_test.c new file mode 100644 index 000000000..e875d8761 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_sysfb_modeset_test.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <kunit/test.h> + +#include <drm/drm_fourcc.h> +#include <drm/drm_kunit_helpers.h> + +#include "../sysfb/drm_sysfb_helper.h" + +#define TEST_BUF_SIZE 50 + +struct sysfb_build_fourcc_list_case { + const char *name; + u32 native_fourccs[TEST_BUF_SIZE]; + size_t native_fourccs_size; + u32 expected[TEST_BUF_SIZE]; + size_t expected_fourccs_size; +}; + +static struct sysfb_build_fourcc_list_case sysfb_build_fourcc_list_cases[] = { + { + .name = "no native formats", + .native_fourccs = { }, + .native_fourccs_size = 0, + .expected = { DRM_FORMAT_XRGB8888 }, + .expected_fourccs_size = 1, + }, + { + .name = "XRGB8888 as native format", + .native_fourccs = { DRM_FORMAT_XRGB8888 }, + .native_fourccs_size = 1, + .expected = { DRM_FORMAT_XRGB8888 }, + .expected_fourccs_size = 1, + }, + { + .name = "remove duplicates", + .native_fourccs = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, + }, + .native_fourccs_size = 11, + .expected = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, + }, + .expected_fourccs_size = 3, + }, + { + .name = "convert alpha formats", + .native_fourccs = { + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, + }, + .native_fourccs_size = 12, + .expected = { + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_RGBX1010102, + DRM_FORMAT_BGRX1010102, + }, + .expected_fourccs_size = 12, + }, + { + .name = "random formats", + .native_fourccs = { + DRM_FORMAT_Y212, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR16161616F, + DRM_FORMAT_C8, + DRM_FORMAT_BGR888, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGR565_A8, + DRM_FORMAT_R10, + DRM_FORMAT_XYUV8888, + }, + .native_fourccs_size = 10, + .expected = { + DRM_FORMAT_Y212, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ABGR16161616F, + DRM_FORMAT_C8, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGR565_A8, + DRM_FORMAT_R10, + DRM_FORMAT_XYUV8888, + DRM_FORMAT_XRGB8888, + }, + .expected_fourccs_size = 10, + }, +}; + +static void sysfb_build_fourcc_list_case_desc(struct sysfb_build_fourcc_list_case *t, char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(sysfb_build_fourcc_list, sysfb_build_fourcc_list_cases, + sysfb_build_fourcc_list_case_desc); + +static void drm_test_sysfb_build_fourcc_list(struct kunit *test) +{ + const struct sysfb_build_fourcc_list_case *params = test->param_value; + u32 fourccs_out[TEST_BUF_SIZE] = {0}; + size_t nfourccs_out; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm), 0, DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + nfourccs_out = drm_sysfb_build_fourcc_list(drm, params->native_fourccs, + params->native_fourccs_size, + fourccs_out, TEST_BUF_SIZE); + + KUNIT_EXPECT_EQ(test, nfourccs_out, params->expected_fourccs_size); + KUNIT_EXPECT_MEMEQ(test, fourccs_out, params->expected, TEST_BUF_SIZE); +} + +static struct kunit_case drm_sysfb_modeset_test_cases[] = { + KUNIT_CASE_PARAM(drm_test_sysfb_build_fourcc_list, sysfb_build_fourcc_list_gen_params), + {} +}; + +static struct kunit_suite drm_sysfb_modeset_test_suite = { + .name = "drm_sysfb_modeset_test", + .test_cases = drm_sysfb_modeset_test_cases, +}; + +kunit_test_suite(drm_sysfb_modeset_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the drm_sysfb_modeset APIs"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>"); |
