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 | 401 +++++++++++++++++++++++++++++++++-- drivers/gpu/drm/vkms/vkms_configfs.h | 54 ++++- 2 files changed, 434 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c index aabc832836266668c8adc60d7d5284ee1f385f31..a410c9be4f2bbf7b2651245747eb357fcf32d1f2 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" @@ -395,6 +396,84 @@ static const struct config_item_type subgroup_plane = { .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 +498,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 +515,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 +535,283 @@ 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 +832,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 +889,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 +903,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 6dc4d34a9e44ed4beb115ad3c86b759e28f5d0ef..df743e0107f40cd10433bdb638108d266f9c83a6 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.47.0