[PATCH 12/13] drm/vkms: Allow to configure multiple connectors

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

Add a list of connectors to vkms_config and helper functions to add and
remove as many connectors as wanted.

Unlike planes, CRTCs and encoders, connectors can be added and removed
once the device is created. To reflect it, add an "enabled" flag in the
configuration and filter disabled connectors in the
vkms_config_get_connectors() function.

For the moment, changing the enabled status of the connector has no
effect after the device is created, but a future patch will add this
capability.

For backwards compatibility, add one enabled connector to the default
configuration.

A future patch will allow to attach connectors and encoders, but for the
moment there are no changes in the way the output is configured.

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 | 84 +++++++++++++++++
drivers/gpu/drm/vkms/vkms_config.c | 93 +++++++++++++++++++
drivers/gpu/drm/vkms/vkms_config.h | 76 +++++++++++++++
drivers/gpu/drm/vkms/vkms_connector.c | 11 +++
4 files changed, 264 insertions(+)

diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
index 27d44315c2de..40c385eedc1d 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_config_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
@@ -27,6 +27,7 @@ static void vkms_config_test_empty_config(struct kunit *test)
KUNIT_EXPECT_TRUE(test, list_empty(&config->planes));
KUNIT_EXPECT_TRUE(test, list_empty(&config->crtcs));
KUNIT_EXPECT_TRUE(test, list_empty(&config->encoders));
+ KUNIT_EXPECT_TRUE(test, list_empty(&config->connectors));

KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config));

@@ -52,6 +53,7 @@ static void vkms_config_test_default_config(struct kunit *test)
struct vkms_config *config;
struct vkms_config_plane *plane_cfg;
struct vkms_config_crtc *crtc_cfg;
+ struct vkms_config_connector *connector_cfg;
int n_primaries = 0;
int n_cursors = 0;
int n_overlays = 0;
@@ -103,6 +105,12 @@ static void vkms_config_test_default_config(struct kunit *test)
/* Encoders */
KUNIT_EXPECT_EQ(test, list_count_nodes(&config->encoders), 1);

+ /* Connectors */
+ KUNIT_EXPECT_EQ(test, list_count_nodes(&config->connectors), 1);
+ connector_cfg = list_first_entry(&config->connectors,
+ typeof(*connector_cfg), link);
+ KUNIT_EXPECT_TRUE(test, vkms_config_connector_is_enabled(connector_cfg));
+
KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));

vkms_config_destroy(config);
@@ -216,6 +224,47 @@ static void vkms_config_test_get_encoders(struct kunit *test)
vkms_config_destroy(config);
}

+static void vkms_config_test_get_connectors(struct kunit *test)
+{
+ struct vkms_config *config;
+ struct vkms_config_connector *connector_cfg1, *connector_cfg2;
+ struct vkms_config_connector **array;
+ size_t length;
+
+ config = vkms_config_create("test");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
+
+ array = vkms_config_get_connectors(config, &length);
+ KUNIT_ASSERT_EQ(test, length, 0);
+ KUNIT_ASSERT_NULL(test, array);
+
+ connector_cfg1 = vkms_config_add_connector(config);
+ array = vkms_config_get_connectors(config, &length);
+ KUNIT_ASSERT_EQ(test, length, 0);
+ KUNIT_ASSERT_NULL(test, array);
+
+ vkms_config_connector_set_enabled(connector_cfg1, true);
+ array = vkms_config_get_connectors(config, &length);
+ KUNIT_ASSERT_EQ(test, length, 1);
+ KUNIT_ASSERT_PTR_EQ(test, array[0], connector_cfg1);
+ kfree(array);
+
+ connector_cfg2 = vkms_config_add_connector(config);
+ vkms_config_connector_set_enabled(connector_cfg2, true);
+ array = vkms_config_get_connectors(config, &length);
+ KUNIT_ASSERT_EQ(test, length, 2);
+ KUNIT_ASSERT_PTR_EQ(test, array[0], connector_cfg1);
+ KUNIT_ASSERT_PTR_EQ(test, array[1], connector_cfg2);
+ kfree(array);
+
+ vkms_config_connector_set_enabled(connector_cfg1, false);
+ vkms_config_destroy_connector(connector_cfg2);
+ array = vkms_config_get_connectors(config, &length);
+ KUNIT_ASSERT_NULL(test, array);
+
+ vkms_config_destroy(config);
+}
+
static void vkms_config_test_valid_plane_number(struct kunit *test)
{
struct vkms_config *config;
@@ -423,6 +472,39 @@ static void vkms_config_test_valid_encoder_possible_crtcs(struct kunit *test)
vkms_config_destroy(config);
}

+static void vkms_config_test_valid_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);
+
+ /* Valid: No connectors */
+ connector_cfg = list_first_entry(&config->connectors, typeof(*connector_cfg), link);
+ vkms_config_destroy_connector(connector_cfg);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Only a disabled connector */
+ connector_cfg = vkms_config_add_connector(config);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: The connector is enabled */
+ vkms_config_connector_set_enabled(connector_cfg, true);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Invalid: Too many connectors */
+ for (n = 0; n <= 32; n++) {
+ connector_cfg = vkms_config_add_connector(config);
+ vkms_config_connector_set_enabled(connector_cfg, true);
+ }
+
+ KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config));
+
+ vkms_config_destroy(config);
+}
+
static void vkms_config_test_plane_attach_crtc(struct kunit *test)
{
struct vkms_config *config;
@@ -613,12 +695,14 @@ static struct kunit_case vkms_config_test_cases[] = {
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_valid_plane_number),
KUNIT_CASE(vkms_config_test_valid_plane_type),
KUNIT_CASE(vkms_config_test_valid_plane_possible_crtcs),
KUNIT_CASE(vkms_config_test_valid_crtc_number),
KUNIT_CASE(vkms_config_test_valid_encoder_number),
KUNIT_CASE(vkms_config_test_valid_encoder_possible_crtcs),
+ KUNIT_CASE(vkms_config_test_valid_connector_number),
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),
diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c
index 437a9980e9a8..e94e48fe3ad9 100644
--- a/drivers/gpu/drm/vkms/vkms_config.c
+++ b/drivers/gpu/drm/vkms/vkms_config.c
@@ -24,6 +24,7 @@ struct vkms_config *vkms_config_create(const char *dev_name)
INIT_LIST_HEAD(&config->planes);
INIT_LIST_HEAD(&config->crtcs);
INIT_LIST_HEAD(&config->encoders);
+ INIT_LIST_HEAD(&config->connectors);

return config;
}
@@ -36,6 +37,7 @@ struct vkms_config *vkms_config_default_create(bool enable_cursor,
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);
@@ -87,6 +89,11 @@ struct vkms_config *vkms_config_default_create(bool enable_cursor,
if (vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg))
goto err_alloc;

+ connector_cfg = vkms_config_add_connector(config);
+ if (IS_ERR(connector_cfg))
+ goto err_alloc;
+ vkms_config_connector_set_enabled(connector_cfg, true);
+
return config;

err_alloc:
@@ -99,6 +106,7 @@ 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);
@@ -109,6 +117,9 @@ void vkms_config_destroy(struct vkms_config *config)
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);
}
@@ -194,6 +205,39 @@ struct vkms_config_encoder **vkms_config_get_encoders(const struct vkms_config *
return array;
}

+struct vkms_config_connector **vkms_config_get_connectors(const struct vkms_config *config,
+ size_t *out_length)
+{
+ struct vkms_config_connector **array;
+ struct vkms_config_connector *connector_cfg;
+ size_t length = 0;
+ int n = 0;
+
+ list_for_each_entry(connector_cfg, &config->connectors, link) {
+ if (vkms_config_connector_is_enabled(connector_cfg))
+ length++;
+ }
+
+ if (length == 0) {
+ *out_length = length;
+ return NULL;
+ }
+
+ array = kmalloc_array(length, sizeof(*array), GFP_KERNEL);
+ if (!array)
+ return ERR_PTR(-ENOMEM);
+
+ list_for_each_entry(connector_cfg, &config->connectors, link) {
+ if (vkms_config_connector_is_enabled(connector_cfg)) {
+ array[n] = connector_cfg;
+ n++;
+ }
+ }
+
+ *out_length = length;
+ return array;
+}
+
static bool valid_plane_number(struct vkms_config *config)
{
size_t n_planes;
@@ -325,6 +369,24 @@ static bool valid_encoder_possible_crtcs(struct vkms_config *config)
return true;
}

+static bool valid_connector_number(struct vkms_config *config)
+{
+ struct vkms_config_connector *connector_cfg;
+ size_t n_connectors = 0;
+
+ list_for_each_entry(connector_cfg, &config->connectors, link) {
+ if (vkms_config_connector_is_enabled(connector_cfg))
+ n_connectors++;
+ }
+
+ if (n_connectors >= 32) {
+ pr_err("The number of connectors must be between 0 and 31\n");
+ return false;
+ }
+
+ return true;
+}
+
bool vkms_config_is_valid(struct vkms_config *config)
{
struct vkms_config_crtc *crtc_cfg;
@@ -338,6 +400,9 @@ bool vkms_config_is_valid(struct vkms_config *config)
if (!valid_encoder_number(config))
return false;

+ if (!valid_connector_number(config))
+ return false;
+
if (!valid_plane_possible_crtcs(config))
return false;

@@ -361,6 +426,7 @@ static int vkms_config_show(struct seq_file *m, void *data)
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);
@@ -381,6 +447,12 @@ static int vkms_config_show(struct seq_file *m, void *data)
seq_puts(m, "encoder\n");
}

+ list_for_each_entry(connector_cfg, &vkmsdev->config->connectors, link) {
+ seq_puts(m, "connector:\n");
+ seq_printf(m, "\tenabled=%d\n",
+ vkms_config_connector_is_enabled(connector_cfg));
+ }
+
return 0;
}

@@ -620,3 +692,24 @@ vkms_config_encoder_get_possible_crtcs(struct vkms_config_encoder *encoder_cfg,
*out_length = length;
return array;
}
+
+struct vkms_config_connector *vkms_config_add_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);
+
+ vkms_config_connector_set_enabled(connector_cfg, false);
+
+ list_add_tail(&connector_cfg->link, &config->connectors);
+
+ return connector_cfg;
+}
+
+void vkms_config_destroy_connector(struct vkms_config_connector *connector_cfg)
+{
+ list_del(&connector_cfg->link);
+ kfree(connector_cfg);
+}
diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h
index 5f4a33e113bf..cc32aadfda8d 100644
--- a/drivers/gpu/drm/vkms/vkms_config.h
+++ b/drivers/gpu/drm/vkms/vkms_config.h
@@ -15,6 +15,7 @@
* @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 {
@@ -22,6 +23,7 @@ struct vkms_config {
struct list_head planes;
struct list_head crtcs;
struct list_head encoders;
+ struct list_head connectors;
struct vkms_device *dev;
};

@@ -85,6 +87,27 @@ struct vkms_config_encoder {
struct drm_encoder *encoder;
};

+/**
+ * struct vkms_config_connector
+ *
+ * @link: Link to the others connector in vkms_config
+ * @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.
+ * @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;
+
+ bool enabled;
+
+ /* Internal usage */
+ struct vkms_connector *connector;
+};
+
/**
* vkms_config_create() - Create a new VKMS configuration
* @dev_name: Name of the device
@@ -173,6 +196,21 @@ struct vkms_config_crtc **vkms_config_get_crtcs(const struct vkms_config *config
struct vkms_config_encoder **vkms_config_get_encoders(const struct vkms_config *config,
size_t *out_length);

+/**
+ * vkms_config_get_connectors() - Return the array of connectors of the device
+ * @config: Configuration to get the connectors from
+ * @out_length: Length of the returned array
+ *
+ * Note that only enabled connectors are returned.
+ *
+ * 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_connector **vkms_config_get_connectors(const struct vkms_config *config,
+ size_t *out_length);
+
/**
* vkms_config_is_valid() - Validate a configuration
* @config: Configuration to validate
@@ -367,4 +405,42 @@ struct vkms_config_crtc **
vkms_config_encoder_get_possible_crtcs(struct vkms_config_encoder *encoder_cfg,
size_t *out_length);

+/**
+ * vkms_config_add_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_add_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_is_enabled() - If the connector is part of the device
+ * @connector_cfg: The connector
+ */
+static inline bool
+vkms_config_connector_is_enabled(struct vkms_config_connector *connector_cfg)
+{
+ return connector_cfg->enabled;
+}
+
+/**
+ * vkms_config_connector_set_enabled() - If the connector is part of the device
+ * @crtc_cfg: Target connector
+ * @enabled: Add or remove the connector
+ */
+static inline void
+vkms_config_connector_set_enabled(struct vkms_config_connector *connector_cfg,
+ bool enabled)
+{
+ connector_cfg->enabled = enabled;
+}
+
#endif /* _VKMS_CONFIG_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c
index ab8b52a84151..48b10cba322a 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.c
+++ b/drivers/gpu/drm/vkms/vkms_connector.c
@@ -25,8 +25,19 @@ static int vkms_conn_get_modes(struct drm_connector *connector)
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)
--
2.48.1