[PATCH 13/13] drm/vkms: Allow to attach connectors and encoders

José Expósito
2025-01-29T11:01:21Z

Add a list of possible encoders to the connector configuration and
helpers to attach and detach them.

Now that the default configuration has its connector and encoder
correctly, configure the output following the configuration.

Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
---
drivers/gpu/drm/vkms/tests/vkms_config_test.c | 62 +++++++++++++++++
drivers/gpu/drm/vkms/vkms_config.c | 69 +++++++++++++++++++
drivers/gpu/drm/vkms/vkms_config.h | 33 +++++++++
drivers/gpu/drm/vkms/vkms_output.c | 57 +++++++++++----
4 files changed, 209 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
index 40c385eedc1d..d89acdc1a752 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_config_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
@@ -688,6 +688,67 @@ static void vkms_config_test_encoder_get_possible_crtcs(struct kunit *test)
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 **array;
+ size_t length;
+ int err;
+
+ config = vkms_config_create("test");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
+
+ connector_cfg1 = vkms_config_add_connector(config);
+ connector_cfg2 = vkms_config_add_connector(config);
+ encoder_cfg1 = vkms_config_add_encoder(config);
+ encoder_cfg2 = vkms_config_add_encoder(config);
+
+ /* No possible encoders */
+ array = vkms_config_connector_get_possible_encoders(connector_cfg1, &length);
+ KUNIT_ASSERT_EQ(test, length, 0);
+ KUNIT_ASSERT_NULL(test, array);
+
+ array = vkms_config_connector_get_possible_encoders(connector_cfg2, &length);
+ KUNIT_ASSERT_EQ(test, length, 0);
+ KUNIT_ASSERT_NULL(test, array);
+
+ /* 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);
+
+ array = vkms_config_connector_get_possible_encoders(connector_cfg1, &length);
+ KUNIT_ASSERT_EQ(test, length, 2);
+ KUNIT_ASSERT_PTR_EQ(test, array[0], encoder_cfg1);
+ KUNIT_ASSERT_PTR_EQ(test, array[1], encoder_cfg2);
+ kfree(array);
+
+ array = vkms_config_connector_get_possible_encoders(connector_cfg2, &length);
+ KUNIT_ASSERT_EQ(test, length, 0);
+ KUNIT_ASSERT_NULL(test, array);
+
+ /* Connector 1 attached to encoder 1 and connector 2 to encoder 2 */
+ vkms_config_connector_detach_encoder(connector_cfg1, encoder_cfg2);
+
+ array = vkms_config_connector_get_possible_encoders(connector_cfg1, &length);
+ KUNIT_ASSERT_EQ(test, length, 1);
+ KUNIT_ASSERT_PTR_EQ(test, array[0], encoder_cfg1);
+ kfree(array);
+
+ err = vkms_config_connector_attach_encoder(connector_cfg2, encoder_cfg2);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ array = vkms_config_connector_get_possible_encoders(connector_cfg2, &length);
+ KUNIT_ASSERT_EQ(test, length, 1);
+ KUNIT_ASSERT_PTR_EQ(test, array[0], encoder_cfg2);
+ kfree(array);
+
+ 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,
@@ -706,6 +767,7 @@ static struct kunit_case vkms_config_test_cases[] = {
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),
{}
};

diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c
index e94e48fe3ad9..db6e3c71fd68 100644
--- a/drivers/gpu/drm/vkms/vkms_config.c
+++ b/drivers/gpu/drm/vkms/vkms_config.c
@@ -94,6 +94,9 @@ struct vkms_config *vkms_config_default_create(bool enable_cursor,
goto err_alloc;
vkms_config_connector_set_enabled(connector_cfg, true);

+ if (vkms_config_connector_attach_encoder(connector_cfg, encoder_cfg))
+ goto err_alloc;
+
return config;

err_alloc:
@@ -629,6 +632,11 @@ struct vkms_config_encoder *vkms_config_add_encoder(struct vkms_config *config)
void vkms_config_destroy_encoder(struct vkms_config *config,
struct vkms_config_encoder *encoder_cfg)
{
+ struct vkms_config_connector *connector_cfg;
+
+ list_for_each_entry(connector_cfg, &config->connectors, link)
+ vkms_config_connector_detach_encoder(connector_cfg, encoder_cfg);
+
xa_destroy(&encoder_cfg->possible_crtcs);
list_del(&encoder_cfg->link);
kfree(encoder_cfg);
@@ -702,6 +710,7 @@ struct vkms_config_connector *vkms_config_add_connector(struct vkms_config *conf
return ERR_PTR(-ENOMEM);

vkms_config_connector_set_enabled(connector_cfg, false);
+ xa_init_flags(&connector_cfg->possible_encoders, XA_FLAGS_ALLOC);

list_add_tail(&connector_cfg->link, &config->connectors);

@@ -710,6 +719,66 @@ struct vkms_config_connector *vkms_config_add_connector(struct vkms_config *conf

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);
}
+
+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;
+
+ xa_for_each(&connector_cfg->possible_encoders, idx, possible_encoder) {
+ if (possible_encoder == encoder_cfg)
+ return -EINVAL;
+ }
+
+ return xa_alloc(&connector_cfg->possible_encoders, &encoder_idx,
+ encoder_cfg, xa_limit_32b, GFP_KERNEL);
+}
+
+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;
+
+ xa_for_each(&connector_cfg->possible_encoders, idx, possible_encoder) {
+ if (possible_encoder == encoder_cfg)
+ xa_erase(&connector_cfg->possible_encoders, idx);
+ }
+}
+
+struct vkms_config_encoder **
+vkms_config_connector_get_possible_encoders(struct vkms_config_connector *connector_cfg,
+ size_t *out_length)
+{
+ struct vkms_config_encoder **array;
+ struct vkms_config_encoder *possible_encoder;
+ unsigned long idx;
+ size_t length = 0;
+ int n = 0;
+
+ xa_for_each(&connector_cfg->possible_encoders, idx, possible_encoder)
+ length++;
+
+ if (length == 0) {
+ *out_length = length;
+ return NULL;
+ }
+
+ array = kmalloc_array(length, sizeof(*array), GFP_KERNEL);
+ if (!array)
+ return ERR_PTR(-ENOMEM);
+
+ xa_for_each(&connector_cfg->possible_encoders, idx, possible_encoder) {
+ array[n] = possible_encoder;
+ n++;
+ }
+
+ *out_length = length;
+ return array;
+}
diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h
index cc32aadfda8d..24052de05e98 100644
--- a/drivers/gpu/drm/vkms/vkms_config.h
+++ b/drivers/gpu/drm/vkms/vkms_config.h
@@ -94,6 +94,7 @@ struct vkms_config_encoder {
* @enabled: Connector are a different from planes, CRTCs and encoders because
* they can be added and removed once the device is created.
* This flag represents if they are part of the device or not.
+ * @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
@@ -103,6 +104,7 @@ struct vkms_config_connector {
struct list_head link;

bool enabled;
+ struct xarray possible_encoders;

/* Internal usage */
struct vkms_connector *connector;
@@ -443,4 +445,35 @@ vkms_config_connector_set_enabled(struct vkms_config_connector *connector_cfg,
connector_cfg->enabled = enabled;
}

+/**
+ * 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);
+
+/**
+ * vkms_config_connector_get_possible_encoders() - Return the array of possible
+ * encoders
+ * @connector_cfg: Connector to get the possible encoders from
+ * @out_length: Length of the returned array
+ *
+ * Returns:
+ * A list of pointers to the configurations. On success, the caller is
+ * responsible to free the returned array, but not its contents. On error,
+ * it returns an error and @out_length is invalid.
+ */
+struct vkms_config_encoder **
+vkms_config_connector_get_possible_encoders(struct vkms_config_connector *connector_cfg,
+ size_t *out_length);
+
#endif /* _VKMS_CONFIG_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 9c3e00817add..20ee8fdfa6f8 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -8,13 +8,14 @@
int vkms_output_init(struct vkms_device *vkmsdev)
{
struct drm_device *dev = &vkmsdev->drm;
- struct vkms_connector *connector;
struct vkms_config_plane **plane_cfgs = NULL;
size_t n_planes;
struct vkms_config_crtc **crtc_cfgs = NULL;
size_t n_crtcs;
struct vkms_config_encoder **encoder_cfgs = NULL;
size_t n_encoders;
+ struct vkms_config_connector **connector_cfgs = NULL;
+ size_t n_connectors;
int ret = 0;
int writeback;
unsigned int n, i;
@@ -35,6 +36,13 @@ int vkms_output_init(struct vkms_device *vkmsdev)
goto err_free;
}

+ connector_cfgs = vkms_config_get_connectors(vkmsdev->config,
+ &n_connectors);
+ if (IS_ERR(connector_cfgs)) {
+ ret = PTR_ERR(connector_cfgs);
+ goto err_free;
+ }
+
for (n = 0; n < n_planes; n++) {
struct vkms_config_plane *plane_cfg;
enum drm_plane_type type;
@@ -136,18 +144,42 @@ int vkms_output_init(struct vkms_device *vkmsdev)
kfree(possible_crtcs);
}

- connector = vkms_connector_init(vkmsdev);
- if (IS_ERR(connector)) {
- DRM_ERROR("Failed to init connector\n");
- ret = PTR_ERR(connector);
- goto err_free;
- }
+ for (n = 0; n < n_connectors; n++) {
+ struct vkms_config_connector *connector_cfg;
+ struct vkms_config_encoder **possible_encoders;
+ size_t n_possible_encoders;

- /* Attach the encoder and the connector */
- ret = drm_connector_attach_encoder(&connector->base, encoder_cfgs[0]->encoder);
- if (ret) {
- DRM_ERROR("Failed to attach connector to encoder\n");
- goto err_free;
+ connector_cfg = connector_cfgs[n];
+
+ connector_cfg->connector = vkms_connector_init(vkmsdev);
+ if (IS_ERR(connector_cfg->connector)) {
+ DRM_ERROR("Failed to init connector\n");
+ ret = PTR_ERR(connector_cfg->connector);
+ goto err_free;
+ }
+
+ possible_encoders =
+ vkms_config_connector_get_possible_encoders(connector_cfg,
+ &n_possible_encoders);
+ if (IS_ERR(possible_encoders)) {
+ ret = PTR_ERR(possible_encoders);
+ goto err_free;
+ }
+
+ for (i = 0; i < n_possible_encoders; i++) {
+ struct vkms_config_encoder *possible_encoder;
+
+ possible_encoder = possible_encoders[i];
+ ret = drm_connector_attach_encoder(&connector_cfg->connector->base,
+ possible_encoder->encoder);
+ if (ret) {
+ DRM_ERROR("Failed to attach connector to encoder\n");
+ kfree(possible_encoders);
+ goto err_free;
+ }
+ }
+
+ kfree(possible_encoders);
}

drm_mode_config_reset(dev);
@@ -156,6 +188,7 @@ int vkms_output_init(struct vkms_device *vkmsdev)
kfree(plane_cfgs);
kfree(crtc_cfgs);
kfree(encoder_cfgs);
+ kfree(connector_cfgs);

return ret;
}
--
2.48.1