[PATCH RFC 7/7] drm/vkms: Add crtc and encoder configuration in ConfigFS

From: Louis Chauvet
Date: Wed Aug 14 2024 - 11:20:06 EST


To allows the userspace to test many hardware configuration, introduce a
new interface to configure CRTCs and encoders.

The CRTCs and encoders are created in their own directory. To link the
CRTC, symlinks are used in the `possible_crtcs` folders.

The current interface is:
/config/vkms
DEVICE_1
┣━ enable
┣━ planes
┃ ┗━ PLANE_1
┃ ┣━ type
┃ ┣━ supported_rotations
┃ ┣━ supported_color_encoding
┃ ┣━ supported_color_ranges
┃ ┣━ default_rotation
┃ ┣━ default_color_encoding
┃ ┣━ default_color_range
┃ ┗━ possible_crtcs
┃ ┗━ >> /config/vkms/DEVICE_1/crtcs/CRTC_1
┣━ encoders
┃ ┗━ ENCODER_1
┃ ┗━ possible_crtcs
┃ ┗━ >> /config/vkms/DEVICE_1/crtcs/CRTC_1
┣━ crtcs
┃ ┗━ CRTC_1
DEVICE_2
┗━ ditto

Signed-off-by: Louis Chauvet <louis.chauvet@xxxxxxxxxxx>
---
drivers/gpu/drm/vkms/vkms_configfs.c | 404 +++++++++++++++++++++++++++++++++--
drivers/gpu/drm/vkms/vkms_configfs.h | 54 ++++-
2 files changed, 437 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index aabc83283626..50628ec91b9a 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -5,6 +5,7 @@
#include <drm/drm_print.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
+#include <linux/generic-radix-tree.h>

#include "vkms_configfs.h"
#include "vkms_drv.h"
@@ -394,6 +395,83 @@ static const struct config_item_type subgroup_plane = {
.ct_item_ops = &plane_item_operations,
.ct_owner = THIS_MODULE,
};
+static const struct config_item_type crtc_item_type;
+static const struct config_item_type planes_item_type;
+
+static int possible_crtcs_allow_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_configfs_device *vkms_configfs = plane_possible_crtc_src_item_to_vkms_configfs_device(src);
+ struct vkms_config_crtc *crtc;
+
+ mutex_lock(&vkms_configfs->lock);
+
+ if (target->ci_type != &crtc_item_type) {
+ mutex_unlock(&vkms_configfs->lock);
+ return -EINVAL;
+ }
+
+ crtc = crtc_item_to_vkms_configfs_crtc(target)->vkms_config_crtc;
+ struct vkms_config_plane *plane = plane_possible_crtc_src_item_to_vkms_configfs_plane(src)->vkms_config_plane;
+
+ struct vkms_config_crtc *crtc_entry;
+ unsigned long idx = 0;
+
+ xa_for_each(&plane->possible_crtcs, idx, crtc_entry) {
+ if (crtc_entry == crtc) {
+ mutex_unlock(&vkms_configfs->lock);
+ return -EINVAL;
+ }
+ }
+
+ if (vkms_config_plane_attach_crtc(plane, crtc))
+ return -EINVAL;
+
+ mutex_unlock(&vkms_configfs->lock);
+
+ return 0;
+}
+
+static void possible_crtcs_drop_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_config_crtc *crtc;
+ struct vkms_configfs_device *vkms_configfs = plane_possible_crtc_src_item_to_vkms_configfs_device(src);
+
+ mutex_lock(&vkms_configfs->lock);
+
+ crtc = crtc_item_to_vkms_configfs_crtc(target)->vkms_config_crtc;
+ struct vkms_config_plane *plane = plane_possible_crtc_src_item_to_vkms_configfs_plane(src)->vkms_config_plane;
+
+ struct vkms_config_crtc *crtc_entry;
+ struct vkms_config_plane *plane_entry;
+ unsigned long crtc_idx = -1;
+
+ xa_for_each(&plane->possible_crtcs, crtc_idx, crtc_entry) {
+ if (crtc_entry == crtc)
+ break;
+ }
+ unsigned long plane_idx = -1;
+
+ xa_erase(&plane->possible_crtcs, crtc_idx);
+ xa_for_each(&crtc->possible_planes, plane_idx, plane_entry) {
+ if (plane_entry == plane)
+ break;
+ }
+ xa_erase(&crtc->possible_planes, plane_idx);
+
+ mutex_unlock(&vkms_configfs->lock);
+}
+
+static struct configfs_item_operations plane_possible_crtcs_item_ops = {
+ .allow_link = &possible_crtcs_allow_link,
+ .drop_link = &possible_crtcs_drop_link,
+};
+
+static struct config_item_type plane_possible_crtcs_group_type = {
+ .ct_item_ops = &plane_possible_crtcs_item_ops,
+ .ct_owner = THIS_MODULE,
+};

static struct config_group *planes_make_group(struct config_group *config_group,
const char *name)
@@ -419,10 +497,7 @@ static struct config_group *planes_make_group(struct config_group *config_group,

if (list_count_nodes(&vkms_configfs->vkms_config->planes) == 1)
vkms_configfs_plane->vkms_config_plane->type = DRM_PLANE_TYPE_PRIMARY;
-
- if (!vkms_configfs_plane->vkms_config_plane ||
- vkms_config_plane_attach_crtc(vkms_configfs_plane->vkms_config_plane,
- vkms_configfs->vkms_config_crtc)) {
+ if (!vkms_configfs_plane->vkms_config_plane) {
kfree(vkms_configfs_plane);
mutex_unlock(&vkms_configfs->lock);
return ERR_PTR(-ENOMEM);
@@ -439,7 +514,12 @@ static struct config_group *planes_make_group(struct config_group *config_group,

config_group_init_type_name(&vkms_configfs_plane->group, name, &subgroup_plane);

+ config_group_init_type_name(&vkms_configfs_plane->possible_crtc_group, "possible_crtcs",
+ &plane_possible_crtcs_group_type);
+ configfs_add_default_group(&vkms_configfs_plane->possible_crtc_group,
+ &vkms_configfs_plane->group);
vkms_configfs_plane->vkms_configfs_device = vkms_configfs;
+
mutex_unlock(&vkms_configfs->lock);

return &vkms_configfs_plane->group;
@@ -454,6 +534,287 @@ static const struct config_item_type planes_item_type = {
.ct_owner = THIS_MODULE,
};

+static void crtc_release(struct config_item *item)
+{
+ struct vkms_configfs_crtc *vkms_configfs_crtc = crtc_item_to_vkms_configfs_crtc(item);
+
+ mutex_lock(&vkms_configfs_crtc->vkms_configfs_device->lock);
+ vkms_config_delete_crtc(vkms_configfs_crtc->vkms_config_crtc,
+ vkms_configfs_crtc->vkms_configfs_device->vkms_config);
+ mutex_unlock(&vkms_configfs_crtc->vkms_configfs_device->lock);
+
+ kfree(vkms_configfs_crtc);
+}
+
+static struct configfs_item_operations crtc_item_operations = {
+ .release = crtc_release,
+};
+
+static const struct config_item_type crtc_item_type = {
+ .ct_owner = THIS_MODULE,
+ .ct_item_ops = &crtc_item_operations,
+};
+
+static struct config_group *crtcs_make_group(struct config_group *config_group,
+ const char *name)
+{
+ struct config_item *root_item = config_group->cg_item.ci_parent;
+ struct vkms_configfs_device *vkms_configfs = config_item_to_vkms_configfs_device(root_item);
+ struct vkms_configfs_crtc *vkms_configfs_crtc;
+
+ vkms_configfs_crtc = kzalloc(sizeof(*vkms_configfs_crtc), GFP_KERNEL);
+
+ if (!vkms_configfs_crtc)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_lock(&vkms_configfs->lock);
+ vkms_configfs_crtc->vkms_configfs_device = vkms_configfs;
+
+ if (vkms_configfs->enabled) {
+ kfree(vkms_configfs_crtc);
+ mutex_unlock(&vkms_configfs->lock);
+ return ERR_PTR(-EINVAL);
+ }
+
+ vkms_configfs_crtc->vkms_config_crtc = vkms_config_create_crtc(vkms_configfs->vkms_config);
+
+ if (!vkms_configfs_crtc->vkms_config_crtc) {
+ kfree(vkms_configfs_crtc);
+ mutex_unlock(&vkms_configfs->lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ vkms_configfs_crtc->vkms_config_crtc->name = kzalloc(strlen(name) + 1, GFP_KERNEL);
+ if (!vkms_configfs_crtc->vkms_config_crtc->name) {
+ kfree(vkms_configfs_crtc->vkms_config_crtc);
+ kfree(vkms_configfs_crtc);
+ mutex_unlock(&vkms_configfs->lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ vkms_configfs_crtc->vkms_configfs_device = vkms_configfs;
+
+ strscpy(vkms_configfs_crtc->vkms_config_crtc->name, name, strlen(name) + 1);
+ config_group_init_type_name(&vkms_configfs_crtc->group, name,
+ &crtc_item_type);
+
+
+ mutex_unlock(&vkms_configfs->lock);
+
+ return &vkms_configfs_crtc->group;
+}
+
+
+static struct configfs_group_operations crtcs_group_operations = {
+ .make_group = &crtcs_make_group,
+};
+
+
+static const struct config_item_type crtcs_item_type = {
+ .ct_group_ops = &crtcs_group_operations,
+ .ct_owner = THIS_MODULE,
+};
+
+static int encoder_possible_crtcs_allow_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_config_crtc *crtc;
+ struct vkms_configfs_device *vkms_configfs = encoder_possible_crtc_src_item_to_vkms_configfs_device(src);
+
+ mutex_lock(&vkms_configfs->lock);
+
+ if (target->ci_type != &crtc_item_type) {
+ DRM_ERROR("Unable to link non-CRTCs.\n");
+ mutex_unlock(&vkms_configfs->lock);
+ return -EINVAL;
+ }
+
+ crtc = crtc_item_to_vkms_configfs_crtc(target)->vkms_config_crtc;
+ struct vkms_config_encoder *encoder = encoder_possible_crtc_src_item_to_vkms_configfs_encoder(src)->vkms_config_encoder;
+
+ struct vkms_config_crtc *crtc_entry;
+ unsigned long idx = 0;
+
+ xa_for_each(&encoder->possible_crtcs, idx, crtc_entry) {
+ if (crtc_entry == crtc) {
+ pr_err("Tried to add two symlinks to the same CRTC from the same object.\n");
+ mutex_unlock(&vkms_configfs->lock);
+ return -EINVAL;
+ }
+ }
+
+ if (vkms_config_encoder_attach_crtc(encoder, crtc))
+ return -EINVAL;
+
+ mutex_unlock(&vkms_configfs->lock);
+
+ return 0;
+}
+
+static void encoder_possible_crtcs_drop_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_config_crtc *crtc;
+ struct vkms_configfs_device *vkms_configfs = encoder_possible_crtc_src_item_to_vkms_configfs_device(src);
+
+ mutex_lock(&vkms_configfs->lock);
+
+ crtc = crtc_item_to_vkms_configfs_crtc(target)->vkms_config_crtc;
+ struct vkms_config_encoder *encoder = encoder_possible_crtc_src_item_to_vkms_configfs_encoder(src)->vkms_config_encoder;
+
+ struct vkms_config_encoder *encoder_entry;
+ struct vkms_config_crtc *crtc_entry;
+ unsigned long encoder_idx = -1;
+ unsigned long crtc_idx = -1;
+
+ xa_for_each(&encoder->possible_crtcs, crtc_idx, crtc_entry) {
+ if (crtc_entry == crtc)
+ break;
+ }
+ xa_erase(&encoder->possible_crtcs, crtc_idx);
+ xa_for_each(&crtc->possible_encoders, encoder_idx, encoder_entry) {
+ if (encoder_entry == encoder)
+ break;
+ }
+ xa_erase(&crtc->possible_encoders, encoder_idx);
+
+ mutex_unlock(&vkms_configfs->lock);
+}
+
+static struct configfs_item_operations encoder_possible_crtcs_item_operations = {
+ .allow_link = &encoder_possible_crtcs_allow_link,
+ .drop_link = &encoder_possible_crtcs_drop_link,
+};
+
+static struct config_item_type encoder_possible_crtcs_item_type = {
+ .ct_item_ops = &encoder_possible_crtcs_item_operations,
+ .ct_owner = THIS_MODULE,
+};
+
+static void encoder_release(struct config_item *item)
+{
+ struct vkms_configfs_encoder *vkms_configfs_encoder = encoder_item_to_vkms_configfs_encoder(item);
+
+ mutex_lock(&vkms_configfs_encoder->vkms_configfs_device->lock);
+ vkms_config_delete_encoder(vkms_configfs_encoder->vkms_config_encoder, vkms_configfs_encoder->vkms_configfs_device->vkms_config);
+ mutex_unlock(&vkms_configfs_encoder->vkms_configfs_device->lock);
+
+ kfree(vkms_configfs_encoder);
+}
+
+static struct configfs_item_operations encoder_item_operations = {
+ .release = encoder_release,
+};
+
+static const struct config_item_type encoder_item_type = {
+ .ct_item_ops = &encoder_item_operations,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *encoder_make_group(struct config_group *config_group,
+ const char *name)
+{
+ struct vkms_configfs_device *vkms_configfs = encoder_item_to_vkms_configfs_device(&config_group->cg_item);
+ struct vkms_configfs_encoder *vkms_configfs_encoder;
+
+ vkms_configfs_encoder = kzalloc(sizeof(*vkms_configfs_encoder), GFP_KERNEL);
+
+ if (!vkms_configfs_encoder)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_lock(&vkms_configfs->lock);
+
+ if (vkms_configfs->enabled) {
+ kfree(vkms_configfs_encoder);
+ mutex_unlock(&vkms_configfs->lock);
+ return ERR_PTR(-EINVAL);
+ }
+
+ vkms_configfs_encoder->vkms_config_encoder = vkms_config_create_encoder(
+ vkms_configfs->vkms_config);
+
+ if (!vkms_configfs_encoder->vkms_config_encoder) {
+ kfree(vkms_configfs_encoder);
+ mutex_unlock(&vkms_configfs->lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ vkms_configfs_encoder->vkms_config_encoder->name = kzalloc(strlen(name) + 1, GFP_KERNEL);
+ if (!vkms_configfs_encoder->vkms_config_encoder->name) {
+ kfree(vkms_configfs_encoder->vkms_config_encoder);
+ kfree(vkms_configfs_encoder);
+ mutex_unlock(&vkms_configfs->lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ strscpy(vkms_configfs_encoder->vkms_config_encoder->name, name, strlen(name) + 1);
+ config_group_init_type_name(&vkms_configfs_encoder->group, name,
+ &encoder_item_type);
+
+ config_group_init_type_name(&vkms_configfs_encoder->possible_crtc_group, "possible_crtcs",
+ &encoder_possible_crtcs_item_type);
+ configfs_add_default_group(&vkms_configfs_encoder->possible_crtc_group,
+ &vkms_configfs_encoder->group);
+ vkms_configfs_encoder->vkms_configfs_device = vkms_configfs;
+
+ mutex_unlock(&vkms_configfs->lock);
+
+ return &vkms_configfs_encoder->group;
+}
+
+static struct configfs_group_operations encoder_group_operations = {
+ .make_group = &encoder_make_group,
+};
+
+static const struct config_item_type encoders_item_type = {
+ .ct_group_ops = &encoder_group_operations,
+ .ct_owner = THIS_MODULE,
+};
+
+/**
+ * configfs_lock_dependencies() - In order to forbid the userspace to delete items when the
+ * device is enabled, mark all configfs items as dependent
+ *
+ * @vkms_configfs_device - Device to lock
+ */
+static void configfs_lock_dependencies(struct vkms_configfs_device *vkms_configfs_device)
+{
+ /* Lock the group itself */
+ configfs_depend_item_unlocked(vkms_configfs_device->group.cg_subsys,
+ &vkms_configfs_device->group.cg_item);
+ /* Lock the planes elements */
+ struct config_item *item;
+
+ list_for_each_entry(item, &vkms_configfs_device->plane_group.cg_children, ci_entry) {
+ configfs_depend_item_unlocked(vkms_configfs_device->plane_group.cg_subsys,
+ item);
+ }
+ list_for_each_entry(item, &vkms_configfs_device->crtc_group.cg_children, ci_entry) {
+ configfs_depend_item_unlocked(vkms_configfs_device->crtc_group.cg_subsys,
+ item);
+ }
+}
+
+/**
+ * configfs_unlock_dependencies() - Once the device is disable, its configuration can be modified.
+ *
+ * @vkms_configfs_device - Device to unlock
+ */
+static void configfs_unlock_dependencies(struct vkms_configfs_device *vkms_configfs_device)
+{
+ struct config_item *item;
+
+ configfs_undepend_item_unlocked(&vkms_configfs_device->group.cg_item);
+
+ list_for_each_entry(item, &vkms_configfs_device->plane_group.cg_children, ci_entry) {
+ configfs_undepend_item_unlocked(item);
+ }
+ list_for_each_entry(item, &vkms_configfs_device->crtc_group.cg_children, ci_entry) {
+ configfs_undepend_item_unlocked(item);
+ }
+}
+
+
static ssize_t device_enable_show(struct config_item *item, char *page)
{
return sprintf(page, "%d\n",
@@ -474,13 +835,25 @@ static ssize_t device_enable_store(struct config_item *item,
return -EINVAL;

mutex_lock(&vkms_configfs_device->lock);
+ if (vkms_configfs_device->enabled == value) {
+ mutex_unlock(&vkms_configfs_device->lock);
+ return (ssize_t) count;
+ }
+
+ if (value && !vkms_config_is_valid(vkms_configfs_device->vkms_config)) {
+ mutex_unlock(&vkms_configfs_device->lock);
+ return -EINVAL;
+ }

vkms_configfs_device->enabled = value;

- if (value)
+ if (value) {
+ configfs_lock_dependencies(vkms_configfs_device);
vkms_create(vkms_configfs_device->vkms_config);
- else
+ } else {
+ configfs_unlock_dependencies(vkms_configfs_device);
vkms_destroy(vkms_configfs_device->vkms_config);
+ }

mutex_unlock(&vkms_configfs_device->lock);

@@ -519,9 +892,6 @@ static const struct config_item_type device_item_type = {
static struct config_group *root_make_group(struct config_group *group,
const char *name)
{
- struct vkms_config_plane *plane;
- struct vkms_config_crtc *crtc;
- struct vkms_config_encoder *encoder;
struct vkms_configfs_device *configfs = kzalloc(sizeof(*configfs), GFP_KERNEL);

if (!configfs)
@@ -536,22 +906,18 @@ static struct config_group *root_make_group(struct config_group *group,
return ERR_PTR(-ENOMEM);
}

- configfs->vkms_config_crtc = vkms_config_create_crtc(configfs->vkms_config);
- configfs->vkms_config_encoder = vkms_config_create_encoder(configfs->vkms_config);
- if (!configfs->vkms_config_crtc || !configfs->vkms_config_encoder ||
- vkms_config_encoder_attach_crtc(configfs->vkms_config_encoder,
- configfs->vkms_config_crtc)) {
- vkms_config_destroy(configfs->vkms_config);
- kfree(configfs);
- return ERR_PTR(-ENOMEM);
- }
-
config_group_init_type_name(&configfs->group, name,
&device_item_type);

config_group_init_type_name(&configfs->plane_group, "planes", &planes_item_type);
configfs_add_default_group(&configfs->plane_group, &configfs->group);

+ config_group_init_type_name(&configfs->crtc_group, "crtcs", &crtcs_item_type);
+ configfs_add_default_group(&configfs->crtc_group, &configfs->group);
+
+ config_group_init_type_name(&configfs->encoder_group, "encoders", &encoders_item_type);
+ configfs_add_default_group(&configfs->encoder_group, &configfs->group);
+
return &configfs->group;
}

diff --git a/drivers/gpu/drm/vkms/vkms_configfs.h b/drivers/gpu/drm/vkms/vkms_configfs.h
index 6dc4d34a9e44..df743e0107f4 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.h
+++ b/drivers/gpu/drm/vkms/vkms_configfs.h
@@ -20,34 +20,84 @@ struct vkms_configfs_device {
struct config_group group;

struct config_group plane_group;
+ struct config_group crtc_group;
+ struct config_group encoder_group;

struct mutex lock;
bool enabled;

struct vkms_config *vkms_config;
- struct vkms_config_crtc *vkms_config_crtc;
- struct vkms_config_encoder *vkms_config_encoder;
};

struct vkms_configfs_plane {
struct config_group group;
+ struct config_group possible_crtc_group;

struct vkms_configfs_device *vkms_configfs_device;
struct vkms_config_plane *vkms_config_plane;
};

+struct vkms_configfs_crtc {
+ struct config_group group;
+
+ struct vkms_configfs_device *vkms_configfs_device;
+ struct vkms_config_crtc *vkms_config_crtc;
+};
+
+struct vkms_configfs_encoder {
+ /* must be first because it is a krefcounted stuff */
+ struct config_group group;
+
+ struct config_group possible_crtc_group;
+ struct vkms_configfs_device *vkms_configfs_device;
+ struct vkms_config_encoder *vkms_config_encoder;
+};
+
#define config_item_to_vkms_configfs_device(item) \
container_of(to_config_group((item)), struct vkms_configfs_device, group)

#define planes_item_to_vkms_configfs_device(item) \
config_item_to_vkms_configfs_device((item)->ci_parent)

+#define encoders_item_to_vkms_configfs_device(item) \
+ config_item_to_vkms_configfs_device((item)->ci_parent)
+
+#define crtc_item_to_vkms_configfs_crtc(item) \
+ container_of(to_config_group((item)), struct vkms_configfs_crtc, group)
+
+#define encoder_item_to_vkms_configfs_encoder(item) \
+ container_of(to_config_group((item)), struct vkms_configfs_encoder, group)
+
#define plane_item_to_vkms_configfs_device(item) \
planes_item_to_vkms_configfs_device((item)->ci_parent)

#define plane_item_to_vkms_configfs_plane(item) \
container_of(to_config_group((item)), struct vkms_configfs_plane, group)

+#define plane_possible_crtc_src_item_to_vkms_configfs_device(item) \
+ plane_item_to_vkms_configfs_device((item)->ci_parent)
+
+#define plane_possible_crtc_src_item_to_vkms_configfs_plane(item) \
+ plane_item_to_vkms_configfs_plane((item)->ci_parent)
+
+#define crtc_item_to_vkms_configfs_device(item) \
+ config_item_to_vkms_configfs_device((item)->ci_parent)
+
+#define crtc_child_item_to_vkms_configfs_device(item) \
+ crtc_item_to_vkms_configfs_device((item)->ci_parent)
+
+#define encoder_item_to_vkms_configfs_device(item) \
+ config_item_to_vkms_configfs_device((item)->ci_parent)
+
+#define encoder_child_item_to_vkms_configfs_device(item) \
+ encoder_item_to_vkms_configfs_device((item)->ci_parent)
+
+#define encoder_possible_crtc_src_item_to_vkms_configfs_device(item) \
+ encoder_child_item_to_vkms_configfs_device((item)->ci_parent)
+
+#define encoder_possible_crtc_src_item_to_vkms_configfs_encoder(item) \
+ encoder_item_to_vkms_configfs_encoder((item)->ci_parent)
+
/* ConfigFS Support */
int vkms_init_configfs(void);
void vkms_unregister_configfs(void);

--
2.44.2