diff options
Diffstat (limited to 'drivers/gpu/drm/vkms')
-rw-r--r-- | drivers/gpu/drm/vkms/Kconfig | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/Makefile | 5 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/tests/.kunitconfig | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/tests/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/tests/vkms_config_test.c | 951 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/vkms_config.c | 640 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/vkms_config.h | 437 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/vkms_connector.c | 61 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/vkms_connector.h | 26 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/vkms_crtc.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/vkms_drv.c | 45 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/vkms_drv.h | 17 | ||||
-rw-r--r-- | drivers/gpu/drm/vkms/vkms_output.c | 176 |
13 files changed, 2240 insertions, 142 deletions
diff --git a/drivers/gpu/drm/vkms/Kconfig b/drivers/gpu/drm/vkms/Kconfig index 9def079f685b..3c02f928ffe6 100644 --- a/drivers/gpu/drm/vkms/Kconfig +++ b/drivers/gpu/drm/vkms/Kconfig @@ -14,3 +14,18 @@ config DRM_VKMS a VKMS. If M is selected the module will be called vkms. + +config DRM_VKMS_KUNIT_TEST + tristate "KUnit tests for VKMS" if !KUNIT_ALL_TESTS + depends on DRM_VKMS && KUNIT + default KUNIT_ALL_TESTS + help + This builds unit tests for VKMS. This option is not useful for + distributions or general kernels, but only for kernel + developers working on VKMS. + + For more information on KUnit and unit tests in general, + please refer to the KUnit documentation in + Documentation/dev-tools/kunit/. + + If in doubt, say "N". diff --git a/drivers/gpu/drm/vkms/Makefile b/drivers/gpu/drm/vkms/Makefile index 1b28a6a32948..d657865e573f 100644 --- a/drivers/gpu/drm/vkms/Makefile +++ b/drivers/gpu/drm/vkms/Makefile @@ -6,6 +6,9 @@ vkms-y := \ vkms_formats.o \ vkms_crtc.o \ vkms_composer.o \ - vkms_writeback.o + vkms_writeback.o \ + vkms_connector.o \ + vkms_config.o obj-$(CONFIG_DRM_VKMS) += vkms.o +obj-$(CONFIG_DRM_VKMS_KUNIT_TEST) += tests/ diff --git a/drivers/gpu/drm/vkms/tests/.kunitconfig b/drivers/gpu/drm/vkms/tests/.kunitconfig new file mode 100644 index 000000000000..6a2d87068edc --- /dev/null +++ b/drivers/gpu/drm/vkms/tests/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=y +CONFIG_DRM=y +CONFIG_DRM_VKMS=y +CONFIG_DRM_VKMS_KUNIT_TEST=y diff --git a/drivers/gpu/drm/vkms/tests/Makefile b/drivers/gpu/drm/vkms/tests/Makefile new file mode 100644 index 000000000000..9ded37b67a46 --- /dev/null +++ b/drivers/gpu/drm/vkms/tests/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_DRM_VKMS_KUNIT_TEST) += vkms_config_test.o diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c new file mode 100644 index 000000000000..ff4566cf9925 --- /dev/null +++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c @@ -0,0 +1,951 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <kunit/test.h> + +#include "../vkms_config.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +static size_t vkms_config_get_num_planes(struct vkms_config *config) +{ + struct vkms_config_plane *plane_cfg; + size_t count = 0; + + vkms_config_for_each_plane(config, plane_cfg) + count++; + + return count; +} + +static size_t vkms_config_get_num_encoders(struct vkms_config *config) +{ + struct vkms_config_encoder *encoder_cfg; + size_t count = 0; + + vkms_config_for_each_encoder(config, encoder_cfg) + count++; + + return count; +} + +static size_t vkms_config_get_num_connectors(struct vkms_config *config) +{ + struct vkms_config_connector *connector_cfg; + size_t count = 0; + + vkms_config_for_each_connector(config, connector_cfg) + count++; + + return count; +} + +static struct vkms_config_plane *get_first_plane(struct vkms_config *config) +{ + struct vkms_config_plane *plane_cfg; + + vkms_config_for_each_plane(config, plane_cfg) + return plane_cfg; + + return NULL; +} + +static struct vkms_config_crtc *get_first_crtc(struct vkms_config *config) +{ + struct vkms_config_crtc *crtc_cfg; + + vkms_config_for_each_crtc(config, crtc_cfg) + return crtc_cfg; + + return NULL; +} + +static struct vkms_config_encoder *get_first_encoder(struct vkms_config *config) +{ + struct vkms_config_encoder *encoder_cfg; + + vkms_config_for_each_encoder(config, encoder_cfg) + return encoder_cfg; + + return NULL; +} + +static struct vkms_config_connector *get_first_connector(struct vkms_config *config) +{ + struct vkms_config_connector *connector_cfg; + + vkms_config_for_each_connector(config, connector_cfg) + return connector_cfg; + + return NULL; +} + +struct default_config_case { + bool enable_cursor; + bool enable_writeback; + bool enable_overlay; +}; + +static void vkms_config_test_empty_config(struct kunit *test) +{ + struct vkms_config *config; + const char *dev_name = "test"; + + config = vkms_config_create(dev_name); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* The dev_name string and the config have different lifetimes */ + dev_name = NULL; + KUNIT_EXPECT_STREQ(test, vkms_config_get_device_name(config), "test"); + + KUNIT_EXPECT_EQ(test, vkms_config_get_num_planes(config), 0); + KUNIT_EXPECT_EQ(test, vkms_config_get_num_crtcs(config), 0); + KUNIT_EXPECT_EQ(test, vkms_config_get_num_encoders(config), 0); + KUNIT_EXPECT_EQ(test, vkms_config_get_num_connectors(config), 0); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static struct default_config_case default_config_cases[] = { + { false, false, false }, + { true, false, false }, + { true, true, false }, + { true, false, true }, + { false, true, false }, + { false, true, true }, + { false, false, true }, + { true, true, true }, +}; + +KUNIT_ARRAY_PARAM(default_config, default_config_cases, NULL); + +static void vkms_config_test_default_config(struct kunit *test) +{ + const struct default_config_case *params = test->param_value; + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + int n_primaries = 0; + int n_cursors = 0; + int n_overlays = 0; + + config = vkms_config_default_create(params->enable_cursor, + params->enable_writeback, + params->enable_overlay); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Planes */ + vkms_config_for_each_plane(config, plane_cfg) { + switch (vkms_config_plane_get_type(plane_cfg)) { + case DRM_PLANE_TYPE_PRIMARY: + n_primaries++; + break; + case DRM_PLANE_TYPE_CURSOR: + n_cursors++; + break; + case DRM_PLANE_TYPE_OVERLAY: + n_overlays++; + break; + default: + KUNIT_FAIL_AND_ABORT(test, "Unknown plane type"); + } + } + KUNIT_EXPECT_EQ(test, n_primaries, 1); + KUNIT_EXPECT_EQ(test, n_cursors, params->enable_cursor ? 1 : 0); + KUNIT_EXPECT_EQ(test, n_overlays, params->enable_overlay ? 8 : 0); + + /* CRTCs */ + KUNIT_EXPECT_EQ(test, vkms_config_get_num_crtcs(config), 1); + + crtc_cfg = get_first_crtc(config); + KUNIT_EXPECT_EQ(test, vkms_config_crtc_get_writeback(crtc_cfg), + params->enable_writeback); + + vkms_config_for_each_plane(config, plane_cfg) { + struct vkms_config_crtc *possible_crtc; + int n_possible_crtcs = 0; + unsigned long idx = 0; + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + KUNIT_EXPECT_PTR_EQ(test, crtc_cfg, possible_crtc); + n_possible_crtcs++; + } + KUNIT_EXPECT_EQ(test, n_possible_crtcs, 1); + } + + /* Encoders */ + KUNIT_EXPECT_EQ(test, vkms_config_get_num_encoders(config), 1); + + /* Connectors */ + KUNIT_EXPECT_EQ(test, vkms_config_get_num_connectors(config), 1); + + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_get_planes(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_plane *plane_cfg1, *plane_cfg2; + int n_planes = 0; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + vkms_config_for_each_plane(config, plane_cfg) + n_planes++; + KUNIT_ASSERT_EQ(test, n_planes, 0); + + plane_cfg1 = vkms_config_create_plane(config); + vkms_config_for_each_plane(config, plane_cfg) { + n_planes++; + if (plane_cfg != plane_cfg1) + KUNIT_FAIL(test, "Unexpected plane"); + } + KUNIT_ASSERT_EQ(test, n_planes, 1); + n_planes = 0; + + plane_cfg2 = vkms_config_create_plane(config); + vkms_config_for_each_plane(config, plane_cfg) { + n_planes++; + if (plane_cfg != plane_cfg1 && plane_cfg != plane_cfg2) + KUNIT_FAIL(test, "Unexpected plane"); + } + KUNIT_ASSERT_EQ(test, n_planes, 2); + n_planes = 0; + + vkms_config_destroy_plane(plane_cfg1); + vkms_config_for_each_plane(config, plane_cfg) { + n_planes++; + if (plane_cfg != plane_cfg2) + KUNIT_FAIL(test, "Unexpected plane"); + } + KUNIT_ASSERT_EQ(test, n_planes, 1); + + vkms_config_destroy(config); +} + +static void vkms_config_test_get_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + KUNIT_ASSERT_EQ(test, vkms_config_get_num_crtcs(config), 0); + vkms_config_for_each_crtc(config, crtc_cfg) + KUNIT_FAIL(test, "Unexpected CRTC"); + + crtc_cfg1 = vkms_config_create_crtc(config); + KUNIT_ASSERT_EQ(test, vkms_config_get_num_crtcs(config), 1); + vkms_config_for_each_crtc(config, crtc_cfg) { + if (crtc_cfg != crtc_cfg1) + KUNIT_FAIL(test, "Unexpected CRTC"); + } + + crtc_cfg2 = vkms_config_create_crtc(config); + KUNIT_ASSERT_EQ(test, vkms_config_get_num_crtcs(config), 2); + vkms_config_for_each_crtc(config, crtc_cfg) { + if (crtc_cfg != crtc_cfg1 && crtc_cfg != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected CRTC"); + } + + vkms_config_destroy_crtc(config, crtc_cfg2); + KUNIT_ASSERT_EQ(test, vkms_config_get_num_crtcs(config), 1); + vkms_config_for_each_crtc(config, crtc_cfg) { + if (crtc_cfg != crtc_cfg1) + KUNIT_FAIL(test, "Unexpected CRTC"); + } + + vkms_config_destroy(config); +} + +static void vkms_config_test_get_encoders(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_encoder *encoder_cfg1, *encoder_cfg2; + int n_encoders = 0; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + vkms_config_for_each_encoder(config, encoder_cfg) + n_encoders++; + KUNIT_ASSERT_EQ(test, n_encoders, 0); + + encoder_cfg1 = vkms_config_create_encoder(config); + vkms_config_for_each_encoder(config, encoder_cfg) { + n_encoders++; + if (encoder_cfg != encoder_cfg1) + KUNIT_FAIL(test, "Unexpected encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 1); + n_encoders = 0; + + encoder_cfg2 = vkms_config_create_encoder(config); + vkms_config_for_each_encoder(config, encoder_cfg) { + n_encoders++; + if (encoder_cfg != encoder_cfg1 && encoder_cfg != encoder_cfg2) + KUNIT_FAIL(test, "Unexpected encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 2); + n_encoders = 0; + + vkms_config_destroy_encoder(config, encoder_cfg2); + vkms_config_for_each_encoder(config, encoder_cfg) { + n_encoders++; + if (encoder_cfg != encoder_cfg1) + KUNIT_FAIL(test, "Unexpected encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 1); + n_encoders = 0; + + vkms_config_destroy(config); +} + +static void vkms_config_test_get_connectors(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_connector *connector_cfg; + struct vkms_config_connector *connector_cfg1, *connector_cfg2; + int n_connectors = 0; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + vkms_config_for_each_connector(config, connector_cfg) + n_connectors++; + KUNIT_ASSERT_EQ(test, n_connectors, 0); + + connector_cfg1 = vkms_config_create_connector(config); + vkms_config_for_each_connector(config, connector_cfg) { + n_connectors++; + if (connector_cfg != connector_cfg1) + KUNIT_FAIL(test, "Unexpected connector"); + } + KUNIT_ASSERT_EQ(test, n_connectors, 1); + n_connectors = 0; + + connector_cfg2 = vkms_config_create_connector(config); + vkms_config_for_each_connector(config, connector_cfg) { + n_connectors++; + if (connector_cfg != connector_cfg1 && + connector_cfg != connector_cfg2) + KUNIT_FAIL(test, "Unexpected connector"); + } + KUNIT_ASSERT_EQ(test, n_connectors, 2); + n_connectors = 0; + + vkms_config_destroy_connector(connector_cfg2); + vkms_config_for_each_connector(config, connector_cfg) { + n_connectors++; + if (connector_cfg != connector_cfg1) + KUNIT_FAIL(test, "Unexpected connector"); + } + KUNIT_ASSERT_EQ(test, n_connectors, 1); + n_connectors = 0; + + vkms_config_destroy(config); +} + +static void vkms_config_test_invalid_plane_number(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + int n; + + config = vkms_config_default_create(false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Invalid: No planes */ + plane_cfg = get_first_plane(config); + vkms_config_destroy_plane(plane_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Too many planes */ + for (n = 0; n <= 32; n++) + vkms_config_create_plane(config); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_valid_plane_type(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + int err; + + config = vkms_config_default_create(false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + plane_cfg = get_first_plane(config); + vkms_config_destroy_plane(plane_cfg); + + crtc_cfg = get_first_crtc(config); + + /* Invalid: No primary plane */ + plane_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_OVERLAY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Multiple primary planes */ + plane_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + + plane_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: One primary plane */ + vkms_config_destroy_plane(plane_cfg); + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + /* Invalid: Multiple cursor planes */ + plane_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_CURSOR); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + + plane_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_CURSOR); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: One primary and one cursor plane */ + vkms_config_destroy_plane(plane_cfg); + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + /* Invalid: Second CRTC without primary plane */ + crtc_cfg = vkms_config_create_crtc(config); + encoder_cfg = vkms_config_create_encoder(config); + err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: Second CRTC with a primary plane */ + plane_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_valid_plane_possible_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + + config = vkms_config_default_create(false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + plane_cfg = get_first_plane(config); + crtc_cfg = get_first_crtc(config); + + /* Invalid: Primary plane without a possible CRTC */ + vkms_config_plane_detach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_invalid_crtc_number(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_crtc *crtc_cfg; + int n; + + config = vkms_config_default_create(false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Invalid: No CRTCs */ + crtc_cfg = get_first_crtc(config); + vkms_config_destroy_crtc(config, crtc_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Too many CRTCs */ + for (n = 0; n <= 32; n++) + vkms_config_create_crtc(config); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_invalid_encoder_number(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_encoder *encoder_cfg; + int n; + + config = vkms_config_default_create(false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Invalid: No encoders */ + encoder_cfg = get_first_encoder(config); + vkms_config_destroy_encoder(config, encoder_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Too many encoders */ + for (n = 0; n <= 32; n++) + vkms_config_create_encoder(config); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_valid_encoder_possible_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + struct vkms_config_encoder *encoder_cfg; + int err; + + config = vkms_config_default_create(false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + crtc_cfg1 = get_first_crtc(config); + + /* Invalid: Encoder without a possible CRTC */ + encoder_cfg = vkms_config_create_encoder(config); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: Second CRTC with shared encoder */ + crtc_cfg2 = vkms_config_create_crtc(config); + + plane_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg1); + KUNIT_EXPECT_EQ(test, err, 0); + + err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + /* Invalid: Second CRTC without encoders */ + vkms_config_encoder_detach_crtc(encoder_cfg, crtc_cfg2); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: First CRTC with 2 possible encoder */ + vkms_config_destroy_plane(plane_cfg); + vkms_config_destroy_crtc(config, crtc_cfg2); + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_invalid_connector_number(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_connector *connector_cfg; + int n; + + config = vkms_config_default_create(false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Invalid: No connectors */ + connector_cfg = get_first_connector(config); + vkms_config_destroy_connector(connector_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Too many connectors */ + for (n = 0; n <= 32; n++) + connector_cfg = vkms_config_create_connector(config); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_valid_connector_possible_encoders(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_connector *connector_cfg; + + config = vkms_config_default_create(false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + encoder_cfg = get_first_encoder(config); + connector_cfg = get_first_connector(config); + + /* Invalid: Connector without a possible encoder */ + vkms_config_connector_detach_encoder(connector_cfg, encoder_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_attach_different_configs(struct kunit *test) +{ + struct vkms_config *config1, *config2; + struct vkms_config_plane *plane_cfg1, *plane_cfg2; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + struct vkms_config_encoder *encoder_cfg1, *encoder_cfg2; + struct vkms_config_connector *connector_cfg1, *connector_cfg2; + int err; + + config1 = vkms_config_create("test1"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config1); + + config2 = vkms_config_create("test2"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config2); + + plane_cfg1 = vkms_config_create_plane(config1); + crtc_cfg1 = vkms_config_create_crtc(config1); + encoder_cfg1 = vkms_config_create_encoder(config1); + connector_cfg1 = vkms_config_create_connector(config1); + + plane_cfg2 = vkms_config_create_plane(config2); + crtc_cfg2 = vkms_config_create_crtc(config2); + encoder_cfg2 = vkms_config_create_encoder(config2); + connector_cfg2 = vkms_config_create_connector(config2); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg2); + + err = vkms_config_plane_attach_crtc(plane_cfg1, crtc_cfg2); + KUNIT_EXPECT_NE(test, err, 0); + err = vkms_config_plane_attach_crtc(plane_cfg2, crtc_cfg1); + KUNIT_EXPECT_NE(test, err, 0); + + err = vkms_config_encoder_attach_crtc(encoder_cfg1, crtc_cfg2); + KUNIT_EXPECT_NE(test, err, 0); + err = vkms_config_encoder_attach_crtc(encoder_cfg2, crtc_cfg1); + KUNIT_EXPECT_NE(test, err, 0); + + err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg2); + KUNIT_EXPECT_NE(test, err, 0); + err = vkms_config_connector_attach_encoder(connector_cfg2, encoder_cfg1); + KUNIT_EXPECT_NE(test, err, 0); + + vkms_config_destroy(config1); + vkms_config_destroy(config2); +} + +static void vkms_config_test_plane_attach_crtc(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *overlay_cfg; + struct vkms_config_plane *primary_cfg; + struct vkms_config_plane *cursor_cfg; + struct vkms_config_crtc *crtc_cfg; + int err; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + overlay_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(overlay_cfg, DRM_PLANE_TYPE_OVERLAY); + primary_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(primary_cfg, DRM_PLANE_TYPE_PRIMARY); + cursor_cfg = vkms_config_create_plane(config); + vkms_config_plane_set_type(cursor_cfg, DRM_PLANE_TYPE_CURSOR); + + crtc_cfg = vkms_config_create_crtc(config); + + /* No primary or cursor planes */ + KUNIT_EXPECT_NULL(test, vkms_config_crtc_primary_plane(config, crtc_cfg)); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_cursor_plane(config, crtc_cfg)); + + /* Overlay plane, but no primary or cursor planes */ + err = vkms_config_plane_attach_crtc(overlay_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_primary_plane(config, crtc_cfg)); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_cursor_plane(config, crtc_cfg)); + + /* Primary plane, attaching it twice must fail */ + err = vkms_config_plane_attach_crtc(primary_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + err = vkms_config_plane_attach_crtc(primary_cfg, crtc_cfg); + KUNIT_EXPECT_NE(test, err, 0); + KUNIT_EXPECT_PTR_EQ(test, + vkms_config_crtc_primary_plane(config, crtc_cfg), + primary_cfg); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_cursor_plane(config, crtc_cfg)); + + /* Primary and cursor planes */ + err = vkms_config_plane_attach_crtc(cursor_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_PTR_EQ(test, + vkms_config_crtc_primary_plane(config, crtc_cfg), + primary_cfg); + KUNIT_EXPECT_PTR_EQ(test, + vkms_config_crtc_cursor_plane(config, crtc_cfg), + cursor_cfg); + + /* Detach primary and destroy cursor plane */ + vkms_config_plane_detach_crtc(overlay_cfg, crtc_cfg); + vkms_config_plane_detach_crtc(primary_cfg, crtc_cfg); + vkms_config_destroy_plane(cursor_cfg); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_primary_plane(config, crtc_cfg)); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_cursor_plane(config, crtc_cfg)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_plane_get_possible_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg1, *plane_cfg2; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + int n_crtcs = 0; + int err; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + plane_cfg1 = vkms_config_create_plane(config); + plane_cfg2 = vkms_config_create_plane(config); + crtc_cfg1 = vkms_config_create_crtc(config); + crtc_cfg2 = vkms_config_create_crtc(config); + + /* No possible CRTCs */ + vkms_config_plane_for_each_possible_crtc(plane_cfg1, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + vkms_config_plane_for_each_possible_crtc(plane_cfg2, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + /* Plane 1 attached to CRTC 1 and 2 */ + err = vkms_config_plane_attach_crtc(plane_cfg1, crtc_cfg1); + KUNIT_EXPECT_EQ(test, err, 0); + err = vkms_config_plane_attach_crtc(plane_cfg1, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + vkms_config_plane_for_each_possible_crtc(plane_cfg1, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg1 && possible_crtc != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 2); + n_crtcs = 0; + + vkms_config_plane_for_each_possible_crtc(plane_cfg2, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + /* Plane 1 attached to CRTC 1 and plane 2 to CRTC 2 */ + vkms_config_plane_detach_crtc(plane_cfg1, crtc_cfg2); + vkms_config_plane_for_each_possible_crtc(plane_cfg1, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg1) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 1); + n_crtcs = 0; + + err = vkms_config_plane_attach_crtc(plane_cfg2, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + vkms_config_plane_for_each_possible_crtc(plane_cfg2, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 1); + + vkms_config_destroy(config); +} + +static void vkms_config_test_encoder_get_possible_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_encoder *encoder_cfg1, *encoder_cfg2; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + int n_crtcs = 0; + int err; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + encoder_cfg1 = vkms_config_create_encoder(config); + encoder_cfg2 = vkms_config_create_encoder(config); + crtc_cfg1 = vkms_config_create_crtc(config); + crtc_cfg2 = vkms_config_create_crtc(config); + + /* No possible CRTCs */ + vkms_config_encoder_for_each_possible_crtc(encoder_cfg1, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg2, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + /* Encoder 1 attached to CRTC 1 and 2 */ + err = vkms_config_encoder_attach_crtc(encoder_cfg1, crtc_cfg1); + KUNIT_EXPECT_EQ(test, err, 0); + err = vkms_config_encoder_attach_crtc(encoder_cfg1, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg1, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg1 && possible_crtc != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 2); + n_crtcs = 0; + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg2, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + /* Encoder 1 attached to CRTC 1 and encoder 2 to CRTC 2 */ + vkms_config_encoder_detach_crtc(encoder_cfg1, crtc_cfg2); + vkms_config_encoder_for_each_possible_crtc(encoder_cfg1, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg1) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 1); + n_crtcs = 0; + + err = vkms_config_encoder_attach_crtc(encoder_cfg2, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + vkms_config_encoder_for_each_possible_crtc(encoder_cfg2, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 1); + + vkms_config_destroy(config); +} + +static void vkms_config_test_connector_get_possible_encoders(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_connector *connector_cfg1, *connector_cfg2; + struct vkms_config_encoder *encoder_cfg1, *encoder_cfg2; + struct vkms_config_encoder *possible_encoder; + unsigned long idx = 0; + int n_encoders = 0; + int err; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + connector_cfg1 = vkms_config_create_connector(config); + connector_cfg2 = vkms_config_create_connector(config); + encoder_cfg1 = vkms_config_create_encoder(config); + encoder_cfg2 = vkms_config_create_encoder(config); + + /* No possible encoders */ + vkms_config_connector_for_each_possible_encoder(connector_cfg1, idx, + possible_encoder) + KUNIT_FAIL(test, "Unexpected possible encoder"); + + vkms_config_connector_for_each_possible_encoder(connector_cfg2, idx, + possible_encoder) + KUNIT_FAIL(test, "Unexpected possible encoder"); + + /* Connector 1 attached to encoders 1 and 2 */ + err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg1); + KUNIT_EXPECT_EQ(test, err, 0); + err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + vkms_config_connector_for_each_possible_encoder(connector_cfg1, idx, + possible_encoder) { + n_encoders++; + if (possible_encoder != encoder_cfg1 && + possible_encoder != encoder_cfg2) + KUNIT_FAIL(test, "Unexpected possible encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 2); + n_encoders = 0; + + vkms_config_connector_for_each_possible_encoder(connector_cfg2, idx, + possible_encoder) + KUNIT_FAIL(test, "Unexpected possible encoder"); + + /* Connector 1 attached to encoder 1 and connector 2 to encoder 2 */ + vkms_config_connector_detach_encoder(connector_cfg1, encoder_cfg2); + vkms_config_connector_for_each_possible_encoder(connector_cfg1, idx, + possible_encoder) { + n_encoders++; + if (possible_encoder != encoder_cfg1) + KUNIT_FAIL(test, "Unexpected possible encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 1); + n_encoders = 0; + + err = vkms_config_connector_attach_encoder(connector_cfg2, encoder_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + vkms_config_connector_for_each_possible_encoder(connector_cfg2, idx, + possible_encoder) { + n_encoders++; + if (possible_encoder != encoder_cfg2) + KUNIT_FAIL(test, "Unexpected possible encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 1); + + vkms_config_destroy(config); +} + +static struct kunit_case vkms_config_test_cases[] = { + KUNIT_CASE(vkms_config_test_empty_config), + KUNIT_CASE_PARAM(vkms_config_test_default_config, + default_config_gen_params), + KUNIT_CASE(vkms_config_test_get_planes), + KUNIT_CASE(vkms_config_test_get_crtcs), + KUNIT_CASE(vkms_config_test_get_encoders), + KUNIT_CASE(vkms_config_test_get_connectors), + KUNIT_CASE(vkms_config_test_invalid_plane_number), + KUNIT_CASE(vkms_config_test_valid_plane_type), + KUNIT_CASE(vkms_config_test_valid_plane_possible_crtcs), + KUNIT_CASE(vkms_config_test_invalid_crtc_number), + KUNIT_CASE(vkms_config_test_invalid_encoder_number), + KUNIT_CASE(vkms_config_test_valid_encoder_possible_crtcs), + KUNIT_CASE(vkms_config_test_invalid_connector_number), + KUNIT_CASE(vkms_config_test_valid_connector_possible_encoders), + KUNIT_CASE(vkms_config_test_attach_different_configs), + KUNIT_CASE(vkms_config_test_plane_attach_crtc), + KUNIT_CASE(vkms_config_test_plane_get_possible_crtcs), + KUNIT_CASE(vkms_config_test_encoder_get_possible_crtcs), + KUNIT_CASE(vkms_config_test_connector_get_possible_encoders), + {} +}; + +static struct kunit_suite vkms_config_test_suite = { + .name = "vkms-config", + .test_cases = vkms_config_test_cases, +}; + +kunit_test_suite(vkms_config_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Kunit test for vkms config utility"); diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c new file mode 100644 index 000000000000..a1df5659b0fb --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_config.c @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <linux/slab.h> + +#include <drm/drm_print.h> +#include <drm/drm_debugfs.h> +#include <kunit/visibility.h> + +#include "vkms_config.h" + +struct vkms_config *vkms_config_create(const char *dev_name) +{ + struct vkms_config *config; + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) + return ERR_PTR(-ENOMEM); + + config->dev_name = kstrdup_const(dev_name, GFP_KERNEL); + if (!config->dev_name) { + kfree(config); + return ERR_PTR(-ENOMEM); + } + + INIT_LIST_HEAD(&config->planes); + INIT_LIST_HEAD(&config->crtcs); + INIT_LIST_HEAD(&config->encoders); + INIT_LIST_HEAD(&config->connectors); + + return config; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create); + +struct vkms_config *vkms_config_default_create(bool enable_cursor, + bool enable_writeback, + bool enable_overlay) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_connector *connector_cfg; + int n; + + config = vkms_config_create(DEFAULT_DEVICE_NAME); + if (IS_ERR(config)) + return config; + + plane_cfg = vkms_config_create_plane(config); + if (IS_ERR(plane_cfg)) + goto err_alloc; + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + + crtc_cfg = vkms_config_create_crtc(config); + if (IS_ERR(crtc_cfg)) + goto err_alloc; + vkms_config_crtc_set_writeback(crtc_cfg, enable_writeback); + + if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg)) + goto err_alloc; + + if (enable_overlay) { + for (n = 0; n < NUM_OVERLAY_PLANES; n++) { + plane_cfg = vkms_config_create_plane(config); + if (IS_ERR(plane_cfg)) + goto err_alloc; + + vkms_config_plane_set_type(plane_cfg, + DRM_PLANE_TYPE_OVERLAY); + + if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg)) + goto err_alloc; + } + } + + if (enable_cursor) { + plane_cfg = vkms_config_create_plane(config); + if (IS_ERR(plane_cfg)) + goto err_alloc; + + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_CURSOR); + + if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg)) + goto err_alloc; + } + + encoder_cfg = vkms_config_create_encoder(config); + if (IS_ERR(encoder_cfg)) + goto err_alloc; + + if (vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg)) + goto err_alloc; + + connector_cfg = vkms_config_create_connector(config); + if (IS_ERR(connector_cfg)) + goto err_alloc; + + if (vkms_config_connector_attach_encoder(connector_cfg, encoder_cfg)) + goto err_alloc; + + return config; + +err_alloc: + vkms_config_destroy(config); + return ERR_PTR(-ENOMEM); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_default_create); + +void vkms_config_destroy(struct vkms_config *config) +{ + struct vkms_config_plane *plane_cfg, *plane_tmp; + struct vkms_config_crtc *crtc_cfg, *crtc_tmp; + struct vkms_config_encoder *encoder_cfg, *encoder_tmp; + struct vkms_config_connector *connector_cfg, *connector_tmp; + + list_for_each_entry_safe(plane_cfg, plane_tmp, &config->planes, link) + vkms_config_destroy_plane(plane_cfg); + + list_for_each_entry_safe(crtc_cfg, crtc_tmp, &config->crtcs, link) + vkms_config_destroy_crtc(config, crtc_cfg); + + list_for_each_entry_safe(encoder_cfg, encoder_tmp, &config->encoders, link) + vkms_config_destroy_encoder(config, encoder_cfg); + + list_for_each_entry_safe(connector_cfg, connector_tmp, &config->connectors, link) + vkms_config_destroy_connector(connector_cfg); + + kfree_const(config->dev_name); + kfree(config); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy); + +static bool valid_plane_number(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + size_t n_planes; + + n_planes = list_count_nodes((struct list_head *)&config->planes); + if (n_planes <= 0 || n_planes >= 32) { + drm_info(dev, "The number of planes must be between 1 and 31\n"); + return false; + } + + return true; +} + +static bool valid_planes_for_crtc(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + struct vkms_config_plane *plane_cfg; + bool has_primary_plane = false; + bool has_cursor_plane = false; + + vkms_config_for_each_plane(config, plane_cfg) { + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + enum drm_plane_type type; + + type = vkms_config_plane_get_type(plane_cfg); + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + if (possible_crtc != crtc_cfg) + continue; + + if (type == DRM_PLANE_TYPE_PRIMARY) { + if (has_primary_plane) { + drm_info(dev, "Multiple primary planes\n"); + return false; + } + + has_primary_plane = true; + } else if (type == DRM_PLANE_TYPE_CURSOR) { + if (has_cursor_plane) { + drm_info(dev, "Multiple cursor planes\n"); + return false; + } + + has_cursor_plane = true; + } + } + } + + if (!has_primary_plane) { + drm_info(dev, "Primary plane not found\n"); + return false; + } + + return true; +} + +static bool valid_plane_possible_crtcs(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + struct vkms_config_plane *plane_cfg; + + vkms_config_for_each_plane(config, plane_cfg) { + if (xa_empty(&plane_cfg->possible_crtcs)) { + drm_info(dev, "All planes must have at least one possible CRTC\n"); + return false; + } + } + + return true; +} + +static bool valid_crtc_number(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + size_t n_crtcs; + + n_crtcs = list_count_nodes((struct list_head *)&config->crtcs); + if (n_crtcs <= 0 || n_crtcs >= 32) { + drm_info(dev, "The number of CRTCs must be between 1 and 31\n"); + return false; + } + + return true; +} + +static bool valid_encoder_number(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + size_t n_encoders; + + n_encoders = list_count_nodes((struct list_head *)&config->encoders); + if (n_encoders <= 0 || n_encoders >= 32) { + drm_info(dev, "The number of encoders must be between 1 and 31\n"); + return false; + } + + return true; +} + +static bool valid_encoder_possible_crtcs(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + + vkms_config_for_each_encoder(config, encoder_cfg) { + if (xa_empty(&encoder_cfg->possible_crtcs)) { + drm_info(dev, "All encoders must have at least one possible CRTC\n"); + return false; + } + } + + vkms_config_for_each_crtc(config, crtc_cfg) { + bool crtc_has_encoder = false; + + vkms_config_for_each_encoder(config, encoder_cfg) { + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg, + idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + crtc_has_encoder = true; + } + } + + if (!crtc_has_encoder) { + drm_info(dev, "All CRTCs must have at least one possible encoder\n"); + return false; + } + } + + return true; +} + +static bool valid_connector_number(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + size_t n_connectors; + + n_connectors = list_count_nodes((struct list_head *)&config->connectors); + if (n_connectors <= 0 || n_connectors >= 32) { + drm_info(dev, "The number of connectors must be between 1 and 31\n"); + return false; + } + + return true; +} + +static bool valid_connector_possible_encoders(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + struct vkms_config_connector *connector_cfg; + + vkms_config_for_each_connector(config, connector_cfg) { + if (xa_empty(&connector_cfg->possible_encoders)) { + drm_info(dev, + "All connectors must have at least one possible encoder\n"); + return false; + } + } + + return true; +} + +bool vkms_config_is_valid(const struct vkms_config *config) +{ + struct vkms_config_crtc *crtc_cfg; + + if (!valid_plane_number(config)) + return false; + + if (!valid_crtc_number(config)) + return false; + + if (!valid_encoder_number(config)) + return false; + + if (!valid_connector_number(config)) + return false; + + if (!valid_plane_possible_crtcs(config)) + return false; + + vkms_config_for_each_crtc(config, crtc_cfg) { + if (!valid_planes_for_crtc(config, crtc_cfg)) + return false; + } + + if (!valid_encoder_possible_crtcs(config)) + return false; + + if (!valid_connector_possible_encoders(config)) + return false; + + return true; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_is_valid); + +static int vkms_config_show(struct seq_file *m, void *data) +{ + struct drm_debugfs_entry *entry = m->private; + struct drm_device *dev = entry->dev; + struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev); + const char *dev_name; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_connector *connector_cfg; + + dev_name = vkms_config_get_device_name((struct vkms_config *)vkmsdev->config); + seq_printf(m, "dev_name=%s\n", dev_name); + + vkms_config_for_each_plane(vkmsdev->config, plane_cfg) { + seq_puts(m, "plane:\n"); + seq_printf(m, "\ttype=%d\n", + vkms_config_plane_get_type(plane_cfg)); + } + + vkms_config_for_each_crtc(vkmsdev->config, crtc_cfg) { + seq_puts(m, "crtc:\n"); + seq_printf(m, "\twriteback=%d\n", + vkms_config_crtc_get_writeback(crtc_cfg)); + } + + vkms_config_for_each_encoder(vkmsdev->config, encoder_cfg) + seq_puts(m, "encoder\n"); + + vkms_config_for_each_connector(vkmsdev->config, connector_cfg) + seq_puts(m, "connector\n"); + + return 0; +} + +static const struct drm_debugfs_info vkms_config_debugfs_list[] = { + { "vkms_config", vkms_config_show, 0 }, +}; + +void vkms_config_register_debugfs(struct vkms_device *vkms_device) +{ + drm_debugfs_add_files(&vkms_device->drm, vkms_config_debugfs_list, + ARRAY_SIZE(vkms_config_debugfs_list)); +} + +struct vkms_config_plane *vkms_config_create_plane(struct vkms_config *config) +{ + struct vkms_config_plane *plane_cfg; + + plane_cfg = kzalloc(sizeof(*plane_cfg), GFP_KERNEL); + if (!plane_cfg) + return ERR_PTR(-ENOMEM); + + plane_cfg->config = config; + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_OVERLAY); + xa_init_flags(&plane_cfg->possible_crtcs, XA_FLAGS_ALLOC); + + list_add_tail(&plane_cfg->link, &config->planes); + + return plane_cfg; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_plane); + +void vkms_config_destroy_plane(struct vkms_config_plane *plane_cfg) +{ + xa_destroy(&plane_cfg->possible_crtcs); + list_del(&plane_cfg->link); + kfree(plane_cfg); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_plane); + +int __must_check vkms_config_plane_attach_crtc(struct vkms_config_plane *plane_cfg, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + u32 crtc_idx = 0; + + if (plane_cfg->config != crtc_cfg->config) + return -EINVAL; + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + return -EEXIST; + } + + return xa_alloc(&plane_cfg->possible_crtcs, &crtc_idx, crtc_cfg, + xa_limit_32b, GFP_KERNEL); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_plane_attach_crtc); + +void vkms_config_plane_detach_crtc(struct vkms_config_plane *plane_cfg, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + xa_erase(&plane_cfg->possible_crtcs, idx); + } +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_plane_detach_crtc); + +struct vkms_config_crtc *vkms_config_create_crtc(struct vkms_config *config) +{ + struct vkms_config_crtc *crtc_cfg; + + crtc_cfg = kzalloc(sizeof(*crtc_cfg), GFP_KERNEL); + if (!crtc_cfg) + return ERR_PTR(-ENOMEM); + + crtc_cfg->config = config; + vkms_config_crtc_set_writeback(crtc_cfg, false); + + list_add_tail(&crtc_cfg->link, &config->crtcs); + + return crtc_cfg; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_crtc); + +void vkms_config_destroy_crtc(struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_plane *plane_cfg; + struct vkms_config_encoder *encoder_cfg; + + vkms_config_for_each_plane(config, plane_cfg) + vkms_config_plane_detach_crtc(plane_cfg, crtc_cfg); + + vkms_config_for_each_encoder(config, encoder_cfg) + vkms_config_encoder_detach_crtc(encoder_cfg, crtc_cfg); + + list_del(&crtc_cfg->link); + kfree(crtc_cfg); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_crtc); + +/** + * vkms_config_crtc_get_plane() - Return the first attached plane to a CRTC with + * the specific type + * @config: Configuration containing the CRTC and the plane + * @crtc_cfg: Only find planes attached to this CRTC + * @type: Plane type to search + * + * Returns: + * The first plane found attached to @crtc_cfg with the type @type. + */ +static struct vkms_config_plane *vkms_config_crtc_get_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg, + enum drm_plane_type type) +{ + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *possible_crtc; + enum drm_plane_type current_type; + unsigned long idx = 0; + + vkms_config_for_each_plane(config, plane_cfg) { + current_type = vkms_config_plane_get_type(plane_cfg); + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg && current_type == type) + return plane_cfg; + } + } + + return NULL; +} + +struct vkms_config_plane *vkms_config_crtc_primary_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg) +{ + return vkms_config_crtc_get_plane(config, crtc_cfg, DRM_PLANE_TYPE_PRIMARY); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_crtc_primary_plane); + +struct vkms_config_plane *vkms_config_crtc_cursor_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg) +{ + return vkms_config_crtc_get_plane(config, crtc_cfg, DRM_PLANE_TYPE_CURSOR); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_crtc_cursor_plane); + +struct vkms_config_encoder *vkms_config_create_encoder(struct vkms_config *config) +{ + struct vkms_config_encoder *encoder_cfg; + + encoder_cfg = kzalloc(sizeof(*encoder_cfg), GFP_KERNEL); + if (!encoder_cfg) + return ERR_PTR(-ENOMEM); + + encoder_cfg->config = config; + xa_init_flags(&encoder_cfg->possible_crtcs, XA_FLAGS_ALLOC); + + list_add_tail(&encoder_cfg->link, &config->encoders); + + return encoder_cfg; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_encoder); + +void vkms_config_destroy_encoder(struct vkms_config *config, + struct vkms_config_encoder *encoder_cfg) +{ + struct vkms_config_connector *connector_cfg; + + vkms_config_for_each_connector(config, connector_cfg) + vkms_config_connector_detach_encoder(connector_cfg, encoder_cfg); + + xa_destroy(&encoder_cfg->possible_crtcs); + list_del(&encoder_cfg->link); + kfree(encoder_cfg); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_encoder); + +int __must_check vkms_config_encoder_attach_crtc(struct vkms_config_encoder *encoder_cfg, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + u32 crtc_idx = 0; + + if (encoder_cfg->config != crtc_cfg->config) + return -EINVAL; + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + return -EEXIST; + } + + return xa_alloc(&encoder_cfg->possible_crtcs, &crtc_idx, crtc_cfg, + xa_limit_32b, GFP_KERNEL); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_encoder_attach_crtc); + +void vkms_config_encoder_detach_crtc(struct vkms_config_encoder *encoder_cfg, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + xa_erase(&encoder_cfg->possible_crtcs, idx); + } +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_encoder_detach_crtc); + +struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *config) +{ + struct vkms_config_connector *connector_cfg; + + connector_cfg = kzalloc(sizeof(*connector_cfg), GFP_KERNEL); + if (!connector_cfg) + return ERR_PTR(-ENOMEM); + + connector_cfg->config = config; + xa_init_flags(&connector_cfg->possible_encoders, XA_FLAGS_ALLOC); + + list_add_tail(&connector_cfg->link, &config->connectors); + + return connector_cfg; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_connector); + +void vkms_config_destroy_connector(struct vkms_config_connector *connector_cfg) +{ + xa_destroy(&connector_cfg->possible_encoders); + list_del(&connector_cfg->link); + kfree(connector_cfg); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_connector); + +int __must_check vkms_config_connector_attach_encoder(struct vkms_config_connector *connector_cfg, + struct vkms_config_encoder *encoder_cfg) +{ + struct vkms_config_encoder *possible_encoder; + unsigned long idx = 0; + u32 encoder_idx = 0; + + if (connector_cfg->config != encoder_cfg->config) + return -EINVAL; + + vkms_config_connector_for_each_possible_encoder(connector_cfg, idx, + possible_encoder) { + if (possible_encoder == encoder_cfg) + return -EEXIST; + } + + return xa_alloc(&connector_cfg->possible_encoders, &encoder_idx, + encoder_cfg, xa_limit_32b, GFP_KERNEL); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_connector_attach_encoder); + +void vkms_config_connector_detach_encoder(struct vkms_config_connector *connector_cfg, + struct vkms_config_encoder *encoder_cfg) +{ + struct vkms_config_encoder *possible_encoder; + unsigned long idx = 0; + + vkms_config_connector_for_each_possible_encoder(connector_cfg, idx, + possible_encoder) { + if (possible_encoder == encoder_cfg) + xa_erase(&connector_cfg->possible_encoders, idx); + } +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_connector_detach_encoder); diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h new file mode 100644 index 000000000000..0118e3f99706 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_config.h @@ -0,0 +1,437 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _VKMS_CONFIG_H_ +#define _VKMS_CONFIG_H_ + +#include <linux/list.h> +#include <linux/types.h> +#include <linux/xarray.h> + +#include "vkms_drv.h" + +/** + * struct vkms_config - General configuration for VKMS driver + * + * @dev_name: Name of the device + * @planes: List of planes configured for the device + * @crtcs: List of CRTCs configured for the device + * @encoders: List of encoders configured for the device + * @connectors: List of connectors configured for the device + * @dev: Used to store the current VKMS device. Only set when the device is instantiated. + */ +struct vkms_config { + const char *dev_name; + struct list_head planes; + struct list_head crtcs; + struct list_head encoders; + struct list_head connectors; + struct vkms_device *dev; +}; + +/** + * struct vkms_config_plane + * + * @link: Link to the others planes in vkms_config + * @config: The vkms_config this plane belongs to + * @type: Type of the plane. The creator of configuration needs to ensures that + * at least one primary plane is present. + * @possible_crtcs: Array of CRTCs that can be used with this plane + * @plane: Internal usage. This pointer should never be considered as valid. + * It can be used to store a temporary reference to a VKMS plane during + * device creation. This pointer is not managed by the configuration and + * must be managed by other means. + */ +struct vkms_config_plane { + struct list_head link; + struct vkms_config *config; + + enum drm_plane_type type; + struct xarray possible_crtcs; + + /* Internal usage */ + struct vkms_plane *plane; +}; + +/** + * struct vkms_config_crtc + * + * @link: Link to the others CRTCs in vkms_config + * @config: The vkms_config this CRTC belongs to + * @writeback: If true, a writeback buffer can be attached to the CRTC + * @crtc: Internal usage. This pointer should never be considered as valid. + * It can be used to store a temporary reference to a VKMS CRTC during + * device creation. This pointer is not managed by the configuration and + * must be managed by other means. + */ +struct vkms_config_crtc { + struct list_head link; + struct vkms_config *config; + + bool writeback; + + /* Internal usage */ + struct vkms_output *crtc; +}; + +/** + * struct vkms_config_encoder + * + * @link: Link to the others encoders in vkms_config + * @config: The vkms_config this CRTC belongs to + * @possible_crtcs: Array of CRTCs that can be used with this encoder + * @encoder: Internal usage. This pointer should never be considered as valid. + * It can be used to store a temporary reference to a VKMS encoder + * during device creation. This pointer is not managed by the + * configuration and must be managed by other means. + */ +struct vkms_config_encoder { + struct list_head link; + struct vkms_config *config; + + struct xarray possible_crtcs; + + /* Internal usage */ + struct drm_encoder *encoder; +}; + +/** + * struct vkms_config_connector + * + * @link: Link to the others connector in vkms_config + * @config: The vkms_config this connector belongs to + * @possible_encoders: Array of encoders that can be used with this connector + * @connector: Internal usage. This pointer should never be considered as valid. + * It can be used to store a temporary reference to a VKMS connector + * during device creation. This pointer is not managed by the + * configuration and must be managed by other means. + */ +struct vkms_config_connector { + struct list_head link; + struct vkms_config *config; + + struct xarray possible_encoders; + + /* Internal usage */ + struct vkms_connector *connector; +}; + +/** + * vkms_config_for_each_plane - Iterate over the vkms_config planes + * @config: &struct vkms_config pointer + * @plane_cfg: &struct vkms_config_plane pointer used as cursor + */ +#define vkms_config_for_each_plane(config, plane_cfg) \ + list_for_each_entry((plane_cfg), &(config)->planes, link) + +/** + * vkms_config_for_each_crtc - Iterate over the vkms_config CRTCs + * @config: &struct vkms_config pointer + * @crtc_cfg: &struct vkms_config_crtc pointer used as cursor + */ +#define vkms_config_for_each_crtc(config, crtc_cfg) \ + list_for_each_entry((crtc_cfg), &(config)->crtcs, link) + +/** + * vkms_config_for_each_encoder - Iterate over the vkms_config encoders + * @config: &struct vkms_config pointer + * @encoder_cfg: &struct vkms_config_encoder pointer used as cursor + */ +#define vkms_config_for_each_encoder(config, encoder_cfg) \ + list_for_each_entry((encoder_cfg), &(config)->encoders, link) + +/** + * vkms_config_for_each_connector - Iterate over the vkms_config connectors + * @config: &struct vkms_config pointer + * @connector_cfg: &struct vkms_config_connector pointer used as cursor + */ +#define vkms_config_for_each_connector(config, connector_cfg) \ + list_for_each_entry((connector_cfg), &(config)->connectors, link) + +/** + * vkms_config_plane_for_each_possible_crtc - Iterate over the vkms_config_plane + * possible CRTCs + * @plane_cfg: &struct vkms_config_plane pointer + * @idx: Index of the cursor + * @possible_crtc: &struct vkms_config_crtc pointer used as cursor + */ +#define vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) \ + xa_for_each(&(plane_cfg)->possible_crtcs, idx, (possible_crtc)) + +/** + * vkms_config_encoder_for_each_possible_crtc - Iterate over the + * vkms_config_encoder possible CRTCs + * @encoder_cfg: &struct vkms_config_encoder pointer + * @idx: Index of the cursor + * @possible_crtc: &struct vkms_config_crtc pointer used as cursor + */ +#define vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) \ + xa_for_each(&(encoder_cfg)->possible_crtcs, idx, (possible_crtc)) + +/** + * vkms_config_connector_for_each_possible_encoder - Iterate over the + * vkms_config_connector possible encoders + * @connector_cfg: &struct vkms_config_connector pointer + * @idx: Index of the cursor + * @possible_encoder: &struct vkms_config_encoder pointer used as cursor + */ +#define vkms_config_connector_for_each_possible_encoder(connector_cfg, idx, possible_encoder) \ + xa_for_each(&(connector_cfg)->possible_encoders, idx, (possible_encoder)) + +/** + * vkms_config_create() - Create a new VKMS configuration + * @dev_name: Name of the device + * + * Returns: + * The new vkms_config or an error. Call vkms_config_destroy() to free the + * returned configuration. + */ +struct vkms_config *vkms_config_create(const char *dev_name); + +/** + * vkms_config_default_create() - Create the configuration for the default device + * @enable_cursor: Create or not a cursor plane + * @enable_writeback: Create or not a writeback connector + * @enable_overlay: Create or not overlay planes + * + * Returns: + * The default vkms_config or an error. Call vkms_config_destroy() to free the + * returned configuration. + */ +struct vkms_config *vkms_config_default_create(bool enable_cursor, + bool enable_writeback, + bool enable_overlay); + +/** + * vkms_config_destroy() - Free a VKMS configuration + * @config: vkms_config to free + */ +void vkms_config_destroy(struct vkms_config *config); + +/** + * vkms_config_get_device_name() - Return the name of the device + * @config: Configuration to get the device name from + * + * Returns: + * The device name. Only valid while @config is valid. + */ +static inline const char * +vkms_config_get_device_name(struct vkms_config *config) +{ + return config->dev_name; +} + +/** + * vkms_config_get_num_crtcs() - Return the number of CRTCs in the configuration + * @config: Configuration to get the number of CRTCs from + */ +static inline size_t vkms_config_get_num_crtcs(struct vkms_config *config) +{ + return list_count_nodes(&config->crtcs); +} + +/** + * vkms_config_is_valid() - Validate a configuration + * @config: Configuration to validate + * + * Returns: + * Whether the configuration is valid or not. + * For example, a configuration without primary planes is not valid. + */ +bool vkms_config_is_valid(const struct vkms_config *config); + +/** + * vkms_config_register_debugfs() - Register a debugfs file to show the device's + * configuration + * @vkms_device: Device to register + */ +void vkms_config_register_debugfs(struct vkms_device *vkms_device); + +/** + * vkms_config_create_plane() - Add a new plane configuration + * @config: Configuration to add the plane to + * + * Returns: + * The new plane configuration or an error. Call vkms_config_destroy_plane() to + * free the returned plane configuration. + */ +struct vkms_config_plane *vkms_config_create_plane(struct vkms_config *config); + +/** + * vkms_config_destroy_plane() - Remove and free a plane configuration + * @plane_cfg: Plane configuration to destroy + */ +void vkms_config_destroy_plane(struct vkms_config_plane *plane_cfg); + +/** + * vkms_config_plane_type() - Return the plane type + * @plane_cfg: Plane to get the type from + */ +static inline enum drm_plane_type +vkms_config_plane_get_type(struct vkms_config_plane *plane_cfg) +{ + return plane_cfg->type; +} + +/** + * vkms_config_plane_set_type() - Set the plane type + * @plane_cfg: Plane to set the type to + * @type: New plane type + */ +static inline void +vkms_config_plane_set_type(struct vkms_config_plane *plane_cfg, + enum drm_plane_type type) +{ + plane_cfg->type = type; +} + +/** + * vkms_config_plane_attach_crtc - Attach a plane to a CRTC + * @plane_cfg: Plane to attach + * @crtc_cfg: CRTC to attach @plane_cfg to + */ +int __must_check vkms_config_plane_attach_crtc(struct vkms_config_plane *plane_cfg, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_plane_detach_crtc - Detach a plane from a CRTC + * @plane_cfg: Plane to detach + * @crtc_cfg: CRTC to detach @plane_cfg from + */ +void vkms_config_plane_detach_crtc(struct vkms_config_plane *plane_cfg, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_create_crtc() - Add a new CRTC configuration + * @config: Configuration to add the CRTC to + * + * Returns: + * The new CRTC configuration or an error. Call vkms_config_destroy_crtc() to + * free the returned CRTC configuration. + */ +struct vkms_config_crtc *vkms_config_create_crtc(struct vkms_config *config); + +/** + * vkms_config_destroy_crtc() - Remove and free a CRTC configuration + * @config: Configuration to remove the CRTC from + * @crtc_cfg: CRTC configuration to destroy + */ +void vkms_config_destroy_crtc(struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_crtc_get_writeback() - If a writeback connector will be created + * @crtc_cfg: CRTC with or without a writeback connector + */ +static inline bool +vkms_config_crtc_get_writeback(struct vkms_config_crtc *crtc_cfg) +{ + return crtc_cfg->writeback; +} + +/** + * vkms_config_crtc_set_writeback() - If a writeback connector will be created + * @crtc_cfg: Target CRTC + * @writeback: Enable or disable the writeback connector + */ +static inline void +vkms_config_crtc_set_writeback(struct vkms_config_crtc *crtc_cfg, + bool writeback) +{ + crtc_cfg->writeback = writeback; +} + +/** + * vkms_config_crtc_primary_plane() - Return the primary plane for a CRTC + * @config: Configuration containing the CRTC + * @crtc_config: Target CRTC + * + * Note that, if multiple primary planes are found, the first one is returned. + * In this case, the configuration will be invalid. See vkms_config_is_valid(). + * + * Returns: + * The primary plane or NULL if none is assigned yet. + */ +struct vkms_config_plane *vkms_config_crtc_primary_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_crtc_cursor_plane() - Return the cursor plane for a CRTC + * @config: Configuration containing the CRTC + * @crtc_config: Target CRTC + * + * Note that, if multiple cursor planes are found, the first one is returned. + * In this case, the configuration will be invalid. See vkms_config_is_valid(). + * + * Returns: + * The cursor plane or NULL if none is assigned yet. + */ +struct vkms_config_plane *vkms_config_crtc_cursor_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_create_encoder() - Add a new encoder configuration + * @config: Configuration to add the encoder to + * + * Returns: + * The new encoder configuration or an error. Call vkms_config_destroy_encoder() + * to free the returned encoder configuration. + */ +struct vkms_config_encoder *vkms_config_create_encoder(struct vkms_config *config); + +/** + * vkms_config_destroy_encoder() - Remove and free a encoder configuration + * @config: Configuration to remove the encoder from + * @encoder_cfg: Encoder configuration to destroy + */ +void vkms_config_destroy_encoder(struct vkms_config *config, + struct vkms_config_encoder *encoder_cfg); + +/** + * vkms_config_encoder_attach_crtc - Attach a encoder to a CRTC + * @encoder_cfg: Encoder to attach + * @crtc_cfg: CRTC to attach @encoder_cfg to + */ +int __must_check vkms_config_encoder_attach_crtc(struct vkms_config_encoder *encoder_cfg, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_encoder_detach_crtc - Detach a encoder from a CRTC + * @encoder_cfg: Encoder to detach + * @crtc_cfg: CRTC to detach @encoder_cfg from + */ +void vkms_config_encoder_detach_crtc(struct vkms_config_encoder *encoder_cfg, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_create_connector() - Add a new connector configuration + * @config: Configuration to add the connector to + * + * Returns: + * The new connector configuration or an error. Call + * vkms_config_destroy_connector() to free the returned connector configuration. + */ +struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *config); + +/** + * vkms_config_destroy_connector() - Remove and free a connector configuration + * @connector_cfg: Connector configuration to destroy + */ +void vkms_config_destroy_connector(struct vkms_config_connector *connector_cfg); + +/** + * vkms_config_connector_attach_encoder - Attach a connector to an encoder + * @connector_cfg: Connector to attach + * @encoder_cfg: Encoder to attach @connector_cfg to + */ +int __must_check vkms_config_connector_attach_encoder(struct vkms_config_connector *connector_cfg, + struct vkms_config_encoder *encoder_cfg); + +/** + * vkms_config_connector_detach_encoder - Detach a connector from an encoder + * @connector_cfg: Connector to detach + * @encoder_cfg: Encoder to detach @connector_cfg from + */ +void vkms_config_connector_detach_encoder(struct vkms_config_connector *connector_cfg, + struct vkms_config_encoder *encoder_cfg); + +#endif /* _VKMS_CONFIG_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c new file mode 100644 index 000000000000..48b10cba322a --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_connector.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_managed.h> +#include <drm/drm_probe_helper.h> + +#include "vkms_connector.h" + +static const struct drm_connector_funcs vkms_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int vkms_conn_get_modes(struct drm_connector *connector) +{ + int count; + + /* Use the default modes list from DRM */ + count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX); + drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF); + + return count; +} + +static struct drm_encoder *vkms_conn_best_encoder(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + + drm_connector_for_each_possible_encoder(connector, encoder) + return encoder; + + return NULL; +} + +static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = { + .get_modes = vkms_conn_get_modes, + .best_encoder = vkms_conn_best_encoder, +}; + +struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev) +{ + struct drm_device *dev = &vkmsdev->drm; + struct vkms_connector *connector; + int ret; + + connector = drmm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) + return ERR_PTR(-ENOMEM); + + ret = drmm_connector_init(dev, &connector->base, &vkms_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL, NULL); + if (ret) + return ERR_PTR(ret); + + drm_connector_helper_add(&connector->base, &vkms_conn_helper_funcs); + + return connector; +} diff --git a/drivers/gpu/drm/vkms/vkms_connector.h b/drivers/gpu/drm/vkms/vkms_connector.h new file mode 100644 index 000000000000..c9149c1b7af0 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_connector.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _VKMS_CONNECTOR_H_ +#define _VKMS_CONNECTOR_H_ + +#include "vkms_drv.h" + +/** + * struct vkms_connector - VKMS custom type wrapping around the DRM connector + * + * @drm: Base DRM connector + */ +struct vkms_connector { + struct drm_connector base; +}; + +/** + * vkms_connector_init() - Initialize a connector + * @vkmsdev: VKMS device containing the connector + * + * Returns: + * The connector or an error on failure. + */ +struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev); + +#endif /* _VKMS_CONNECTOR_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c index 12034ec12029..8c9898b9055d 100644 --- a/drivers/gpu/drm/vkms/vkms_crtc.c +++ b/drivers/gpu/drm/vkms/vkms_crtc.c @@ -194,7 +194,7 @@ static int vkms_crtc_atomic_check(struct drm_crtc *crtc, i++; } - vkms_state->active_planes = kcalloc(i, sizeof(plane), GFP_KERNEL); + vkms_state->active_planes = kcalloc(i, sizeof(*vkms_state->active_planes), GFP_KERNEL); if (!vkms_state->active_planes) return -ENOMEM; vkms_state->num_active_planes = i; diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c index b6de91134a22..a24d1655f7b8 100644 --- a/drivers/gpu/drm/vkms/vkms_drv.c +++ b/drivers/gpu/drm/vkms/vkms_drv.c @@ -27,11 +27,9 @@ #include <drm/drm_gem_shmem_helper.h> #include <drm/drm_vblank.h> +#include "vkms_config.h" #include "vkms_drv.h" -#include <drm/drm_print.h> -#include <drm/drm_debugfs.h> - #define DRIVER_NAME "vkms" #define DRIVER_DESC "Virtual Kernel Mode Setting" #define DRIVER_MAJOR 1 @@ -81,23 +79,6 @@ static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state) drm_atomic_helper_cleanup_planes(dev, old_state); } -static int vkms_config_show(struct seq_file *m, void *data) -{ - struct drm_debugfs_entry *entry = m->private; - struct drm_device *dev = entry->dev; - struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev); - - seq_printf(m, "writeback=%d\n", vkmsdev->config->writeback); - seq_printf(m, "cursor=%d\n", vkmsdev->config->cursor); - seq_printf(m, "overlay=%d\n", vkmsdev->config->overlay); - - return 0; -} - -static const struct drm_debugfs_info vkms_config_debugfs_list[] = { - { "vkms_config", vkms_config_show, 0 }, -}; - static const struct drm_driver vkms_driver = { .driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM, .fops = &vkms_driver_fops, @@ -170,8 +151,10 @@ static int vkms_create(struct vkms_config *config) int ret; struct platform_device *pdev; struct vkms_device *vkms_device; + const char *dev_name; - pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); + dev_name = vkms_config_get_device_name(config); + pdev = platform_device_register_simple(dev_name, -1, NULL, 0); if (IS_ERR(pdev)) return PTR_ERR(pdev); @@ -198,7 +181,8 @@ static int vkms_create(struct vkms_config *config) goto out_devres; } - ret = drm_vblank_init(&vkms_device->drm, 1); + ret = drm_vblank_init(&vkms_device->drm, + vkms_config_get_num_crtcs(config)); if (ret) { DRM_ERROR("Failed to vblank\n"); goto out_devres; @@ -208,8 +192,7 @@ static int vkms_create(struct vkms_config *config) if (ret) goto out_devres; - drm_debugfs_add_files(&vkms_device->drm, vkms_config_debugfs_list, - ARRAY_SIZE(vkms_config_debugfs_list)); + vkms_config_register_debugfs(vkms_device); ret = drm_dev_register(&vkms_device->drm, 0); if (ret) @@ -231,17 +214,13 @@ static int __init vkms_init(void) int ret; struct vkms_config *config; - config = kmalloc(sizeof(*config), GFP_KERNEL); - if (!config) - return -ENOMEM; - - config->cursor = enable_cursor; - config->writeback = enable_writeback; - config->overlay = enable_overlay; + config = vkms_config_default_create(enable_cursor, enable_writeback, enable_overlay); + if (IS_ERR(config)) + return PTR_ERR(config); ret = vkms_create(config); if (ret) { - kfree(config); + vkms_config_destroy(config); return ret; } @@ -275,7 +254,7 @@ static void __exit vkms_exit(void) return; vkms_destroy(default_config); - kfree(default_config); + vkms_config_destroy(default_config); } module_init(vkms_init); diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h index abbb652be2b5..a74a7fc3a056 100644 --- a/drivers/gpu/drm/vkms/vkms_drv.h +++ b/drivers/gpu/drm/vkms/vkms_drv.h @@ -12,6 +12,8 @@ #include <drm/drm_encoder.h> #include <drm/drm_writeback.h> +#define DEFAULT_DEVICE_NAME "vkms" + #define XRES_MIN 10 #define YRES_MIN 10 @@ -189,20 +191,7 @@ struct vkms_output { spinlock_t composer_lock; }; -/** - * struct vkms_config - General configuration for VKMS driver - * - * @writeback: If true, a writeback buffer can be attached to the CRTC - * @cursor: If true, a cursor plane is created in the VKMS device - * @overlay: If true, NUM_OVERLAY_PLANES will be created for the VKMS device - * @dev: Used to store the current VKMS device. Only set when the device is instantiated. - */ -struct vkms_config { - bool writeback; - bool cursor; - bool overlay; - struct vkms_device *dev; -}; +struct vkms_config; /** * struct vkms_device - Description of a VKMS device diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c index 22f0d678af3a..8d7ca0cdd79f 100644 --- a/drivers/gpu/drm/vkms/vkms_output.c +++ b/drivers/gpu/drm/vkms/vkms_output.c @@ -1,121 +1,111 @@ // SPDX-License-Identifier: GPL-2.0+ +#include "vkms_config.h" +#include "vkms_connector.h" #include "vkms_drv.h" -#include <drm/drm_atomic_helper.h> -#include <drm/drm_edid.h> #include <drm/drm_managed.h> -#include <drm/drm_probe_helper.h> - -static const struct drm_connector_funcs vkms_connector_funcs = { - .fill_modes = drm_helper_probe_single_connector_modes, - .reset = drm_atomic_helper_connector_reset, - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, -}; - -static int vkms_conn_get_modes(struct drm_connector *connector) -{ - int count; - - /* Use the default modes list from DRM */ - count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX); - drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF); - - return count; -} - -static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = { - .get_modes = vkms_conn_get_modes, -}; int vkms_output_init(struct vkms_device *vkmsdev) { struct drm_device *dev = &vkmsdev->drm; - struct drm_connector *connector; - struct drm_encoder *encoder; - struct vkms_output *output; - struct vkms_plane *primary, *overlay, *cursor = NULL; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_connector *connector_cfg; int ret; int writeback; - unsigned int n; - - /* - * Initialize used plane. One primary plane is required to perform the composition. - * - * The overlay and cursor planes are not mandatory, but can be used to perform complex - * composition. - */ - primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY); - if (IS_ERR(primary)) - return PTR_ERR(primary); - - if (vkmsdev->config->cursor) { - cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR); - if (IS_ERR(cursor)) - return PTR_ERR(cursor); - } - output = vkms_crtc_init(dev, &primary->base, - cursor ? &cursor->base : NULL); - if (IS_ERR(output)) { - DRM_ERROR("Failed to allocate CRTC\n"); - return PTR_ERR(output); - } + if (!vkms_config_is_valid(vkmsdev->config)) + return -EINVAL; - if (vkmsdev->config->overlay) { - for (n = 0; n < NUM_OVERLAY_PLANES; n++) { - overlay = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_OVERLAY); - if (IS_ERR(overlay)) { - DRM_DEV_ERROR(dev->dev, "Failed to init vkms plane\n"); - return PTR_ERR(overlay); - } - overlay->base.possible_crtcs = drm_crtc_mask(&output->crtc); + vkms_config_for_each_plane(vkmsdev->config, plane_cfg) { + enum drm_plane_type type; + + type = vkms_config_plane_get_type(plane_cfg); + + plane_cfg->plane = vkms_plane_init(vkmsdev, type); + if (IS_ERR(plane_cfg->plane)) { + DRM_DEV_ERROR(dev->dev, "Failed to init vkms plane\n"); + return PTR_ERR(plane_cfg->plane); } } - connector = drmm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); - if (!connector) { - DRM_ERROR("Failed to allocate connector\n"); - return -ENOMEM; - } + vkms_config_for_each_crtc(vkmsdev->config, crtc_cfg) { + struct vkms_config_plane *primary, *cursor; - ret = drmm_connector_init(dev, connector, &vkms_connector_funcs, - DRM_MODE_CONNECTOR_VIRTUAL, NULL); - if (ret) { - DRM_ERROR("Failed to init connector\n"); - return ret; - } + primary = vkms_config_crtc_primary_plane(vkmsdev->config, crtc_cfg); + cursor = vkms_config_crtc_cursor_plane(vkmsdev->config, crtc_cfg); - drm_connector_helper_add(connector, &vkms_conn_helper_funcs); + crtc_cfg->crtc = vkms_crtc_init(dev, &primary->plane->base, + cursor ? &cursor->plane->base : NULL); + if (IS_ERR(crtc_cfg->crtc)) { + DRM_ERROR("Failed to allocate CRTC\n"); + return PTR_ERR(crtc_cfg->crtc); + } - encoder = drmm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL); - if (!encoder) { - DRM_ERROR("Failed to allocate encoder\n"); - return -ENOMEM; + /* Initialize the writeback component */ + if (vkms_config_crtc_get_writeback(crtc_cfg)) { + writeback = vkms_enable_writeback_connector(vkmsdev, crtc_cfg->crtc); + if (writeback) + DRM_ERROR("Failed to init writeback connector\n"); + } } - ret = drmm_encoder_init(dev, encoder, NULL, - DRM_MODE_ENCODER_VIRTUAL, NULL); - if (ret) { - DRM_ERROR("Failed to init encoder\n"); - return ret; + + vkms_config_for_each_plane(vkmsdev->config, plane_cfg) { + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + plane_cfg->plane->base.possible_crtcs |= + drm_crtc_mask(&possible_crtc->crtc->crtc); + } } - encoder->possible_crtcs = drm_crtc_mask(&output->crtc); - /* Attach the encoder and the connector */ - ret = drm_connector_attach_encoder(connector, encoder); - if (ret) { - DRM_ERROR("Failed to attach connector to encoder\n"); - return ret; + vkms_config_for_each_encoder(vkmsdev->config, encoder_cfg) { + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + encoder_cfg->encoder = drmm_kzalloc(dev, sizeof(*encoder_cfg->encoder), GFP_KERNEL); + if (!encoder_cfg->encoder) { + DRM_ERROR("Failed to allocate encoder\n"); + return -ENOMEM; + } + ret = drmm_encoder_init(dev, encoder_cfg->encoder, NULL, + DRM_MODE_ENCODER_VIRTUAL, NULL); + if (ret) { + DRM_ERROR("Failed to init encoder\n"); + return ret; + } + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) { + encoder_cfg->encoder->possible_crtcs |= + drm_crtc_mask(&possible_crtc->crtc->crtc); + } } - /* Initialize the writeback component */ - if (vkmsdev->config->writeback) { - writeback = vkms_enable_writeback_connector(vkmsdev, output); - if (writeback) - DRM_ERROR("Failed to init writeback connector\n"); + vkms_config_for_each_connector(vkmsdev->config, connector_cfg) { + struct vkms_config_encoder *possible_encoder; + unsigned long idx = 0; + + connector_cfg->connector = vkms_connector_init(vkmsdev); + if (IS_ERR(connector_cfg->connector)) { + DRM_ERROR("Failed to init connector\n"); + return PTR_ERR(connector_cfg->connector); + } + + vkms_config_connector_for_each_possible_encoder(connector_cfg, + idx, + possible_encoder) { + ret = drm_connector_attach_encoder(&connector_cfg->connector->base, + possible_encoder->encoder); + if (ret) { + DRM_ERROR("Failed to attach connector to encoder\n"); + return ret; + } + } } drm_mode_config_reset(dev); - return ret; + return 0; } |