summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/tests
diff options
context:
space:
mode:
authorNiklas Cassel <cassel@kernel.org>2026-09-16 15:00:32 +0200
committerNiklas Cassel <cassel@kernel.org>2026-09-16 16:18:00 +0200
commit8d836581f9b1f57ceaa2b47654754ef1260b410b (patch)
tree1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /drivers/gpu/drm/tests
downloadlinux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz
linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth <thuth@redhat.com> Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal <dlemoal@kernel.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel <cassel@kernel.org>
Diffstat (limited to 'drivers/gpu/drm/tests')
-rw-r--r--drivers/gpu/drm/tests/.kunitconfig5
-rw-r--r--drivers/gpu/drm/tests/Makefile30
-rw-r--r--drivers/gpu/drm/tests/drm_atomic_commit_test.c409
-rw-r--r--drivers/gpu/drm/tests/drm_atomic_test.c153
-rw-r--r--drivers/gpu/drm/tests/drm_bridge_test.c1386
-rw-r--r--drivers/gpu/drm/tests/drm_client_modeset_test.c203
-rw-r--r--drivers/gpu/drm/tests/drm_cmdline_parser_test.c1079
-rw-r--r--drivers/gpu/drm/tests/drm_connector_test.c1837
-rw-r--r--drivers/gpu/drm/tests/drm_damage_helper_test.c640
-rw-r--r--drivers/gpu/drm/tests/drm_dp_mst_helper_test.c577
-rw-r--r--drivers/gpu/drm/tests/drm_exec_test.c228
-rw-r--r--drivers/gpu/drm/tests/drm_fixp_test.c71
-rw-r--r--drivers/gpu/drm/tests/drm_format_helper_test.c1740
-rw-r--r--drivers/gpu/drm/tests/drm_format_test.c360
-rw-r--r--drivers/gpu/drm/tests/drm_framebuffer_test.c725
-rw-r--r--drivers/gpu/drm/tests/drm_gem_shmem_test.c399
-rw-r--r--drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c3489
-rw-r--r--drivers/gpu/drm/tests/drm_kunit_edid.c995
-rw-r--r--drivers/gpu/drm/tests/drm_kunit_edid.h16
-rw-r--r--drivers/gpu/drm/tests/drm_kunit_helpers.c400
-rw-r--r--drivers/gpu/drm/tests/drm_managed_test.c117
-rw-r--r--drivers/gpu/drm/tests/drm_mm_test.c359
-rw-r--r--drivers/gpu/drm/tests/drm_modes_test.c208
-rw-r--r--drivers/gpu/drm/tests/drm_panic_test.c221
-rw-r--r--drivers/gpu/drm/tests/drm_plane_helper_test.c319
-rw-r--r--drivers/gpu/drm/tests/drm_probe_helper_test.c217
-rw-r--r--drivers/gpu/drm/tests/drm_rect_test.c564
-rw-r--r--drivers/gpu/drm/tests/drm_sysfb_modeset_test.c168
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 = &params->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 = &params->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,
+ &params->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, &params->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 = &params->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,
+ &params->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, &params->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 = &params->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,
+ &params->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, &params->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, &params->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, &params->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 = &params->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,
+ &params->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, &params->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, &params->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 = &params->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,
+ &params->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, &params->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, &params->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 = &params->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,
+ &params->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, &params->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, &params->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 = &params->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,
+ &params->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, &params->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, &params->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 = &params->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,
+ &params->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, &params->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, &params->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 = &params->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, &params->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, &params->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, &params->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 = &params->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, &params->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, &params->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, &params->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 = &params->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, &params->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, &params->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, &params->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 = &params->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, &params->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, &params->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 = &params->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, &params->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, &params->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, &params->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, &params->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, &params->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 = &params->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, &params->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, &params->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 = &params->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, &params->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, &params->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(&params->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, &params->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],
+ &params->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, &params->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, &params->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, &params->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, &params->r2);
+
+ KUNIT_EXPECT_EQ(test, visible, params->should_be_visible);
+ drm_rect_compare(test, &r1_aux, &params->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(&params->src, &params->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(&params->src, &params->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, &params->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, &params->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>");