| From fe2114e058de69b42584475731e59fa9c5275f7e Mon Sep 17 00:00:00 2001 |
| From: Brandon Pollack <[email protected]> |
| Date: Fri, 23 Jun 2023 18:23:46 -0400 |
| Subject: [PATCH] FROMLIST: drm/vkms: Add ConfigFS scaffolding to VKMS |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| This change adds the basic scaffolding for ConfigFS, including setting |
| up the default directories. It does not allow for the registration of |
| configfs-backed devices, which is complex and provided in a follow-up |
| commit. |
| |
| This CL includes docs about using ConfigFS with VKMS, but I'll summarize |
| in brief here as well (assuming ConfigFS is mounted at /config/): |
| |
| To create a new device, you can do so via `mkdir |
| /config/vkms/my-device`. |
| |
| This will create a number of directories and files automatically: |
| |
| /config |
| `-- vkms |
| `-- my-device |
| |-- connectors |
| |-- crtcs |
| |-- encoders |
| |-- planes |
| `-- enabled |
| |
| You can then configure objects by mkdir'ing in each of the directories. |
| |
| When you're satisfied, you can `echo 1 > /config/vkms/my-device/enabled`. |
| This will create a new device according to your configuration. |
| |
| For now, this will fail, but the next change will add support for it. |
| |
| Signed-off-by: Jim Shargo <[email protected]> |
| Signed-off-by: Brandon Pollack <[email protected]> |
| (am from https://patchwork.kernel.org/project/dri-devel/patch/[email protected]/) |
| |
| The change is in the final stages of review (fifth iteration), All that |
| remains is some (potential) igt tests and final signoff. |
| |
| Some reasons we'd like to submit in chromeOS are: |
| |
| This will allow our virtual multi display tests to run in presubmit. |
| This real world case will be compelling evidence if/when upstream |
| requests some data that this ran for real in production (and avoiding |
| the chicken egg problem of we need this in the kernel to run the tests |
| but without running the tests we can't submit upstream). |
| |
| Signed-off-by: Brandon Pollack <[email protected]> |
| BUG=241800059 |
| BUG=283357160 |
| TEST=new tests defined here passing https://chromium-review.googlesource.com/c/chromiumos/platform/tast-tests/+/4666669 |
| |
| Change-Id: I853254cbcb439ee58d6e538b51fab8a2aa842ca3 |
| Signed-off-by: Brandon Pollack <[email protected]> |
| Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/4810101 |
| Reviewed-by: Stéphane Marchesin <[email protected]> |
| --- |
| Documentation/gpu/vkms.rst | 18 +- |
| drivers/gpu/drm/vkms/Kconfig | 1 + |
| drivers/gpu/drm/vkms/Makefile | 1 + |
| drivers/gpu/drm/vkms/vkms_configfs.c | 650 +++++++++++++++++++++++++++ |
| drivers/gpu/drm/vkms/vkms_drv.c | 56 ++- |
| drivers/gpu/drm/vkms/vkms_drv.h | 92 +++- |
| drivers/gpu/drm/vkms/vkms_output.c | 5 + |
| 7 files changed, 806 insertions(+), 17 deletions(-) |
| create mode 100644 drivers/gpu/drm/vkms/vkms_configfs.c |
| |
| diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst |
| index ba04ac7c2167a9d484c54c69a09a2fb8f2d9c0aa..c3875bf66dba0312eb439b6ae2f05643d29e5318 100644 |
| --- a/Documentation/gpu/vkms.rst |
| +++ b/Documentation/gpu/vkms.rst |
| @@ -51,6 +51,12 @@ To disable the driver, use :: |
| |
| sudo modprobe -r vkms |
| |
| +Configuration With ConfigFS |
| +=========================== |
| + |
| +.. kernel-doc:: drivers/gpu/drm/vkms/vkms_configfs.c |
| + :doc: ConfigFS Support for VKMS |
| + |
| Testing With IGT |
| ================ |
| |
| @@ -135,22 +141,16 @@ project. |
| Runtime Configuration |
| --------------------- |
| |
| -We want to be able to reconfigure vkms instance without having to reload the |
| -module. Use/Test-cases: |
| +We want to be able to manipulate vkms instances without having to reload the |
| +module. Such configuration can be added as extensions to vkms's ConfigFS |
| +support. Use-cases: |
| |
| - Hotplug/hotremove connectors on the fly (to be able to test DP MST handling |
| of compositors). |
| |
| -- Configure planes/crtcs/connectors (we'd need some code to have more than 1 of |
| - them first). |
| - |
| - Change output configuration: Plug/unplug screens, change EDID, allow changing |
| the refresh rate. |
| |
| -The currently proposed solution is to expose vkms configuration through |
| -configfs. All existing module options should be supported through configfs |
| -too. |
| - |
| Writeback support |
| ----------------- |
| |
| diff --git a/drivers/gpu/drm/vkms/Kconfig b/drivers/gpu/drm/vkms/Kconfig |
| index b9ecdebecb0b6a57b638aba655bacd9d87656eaa..43b4d39f9f5965b3cb5a8f9ebdd0fe1e4a755670 100644 |
| --- a/drivers/gpu/drm/vkms/Kconfig |
| +++ b/drivers/gpu/drm/vkms/Kconfig |
| @@ -5,6 +5,7 @@ config DRM_VKMS |
| depends on DRM && MMU |
| select DRM_KMS_HELPER |
| select DRM_GEM_SHMEM_HELPER |
| + select CONFIGFS_FS |
| select CRC32 |
| default n |
| help |
| diff --git a/drivers/gpu/drm/vkms/Makefile b/drivers/gpu/drm/vkms/Makefile |
| index 1b28a6a32948b557867dda51d2ccfdea687a2b62..6b83907ad5544a0695c15aad705a0fca76b36e28 100644 |
| --- a/drivers/gpu/drm/vkms/Makefile |
| +++ b/drivers/gpu/drm/vkms/Makefile |
| @@ -1,5 +1,6 @@ |
| # SPDX-License-Identifier: GPL-2.0-only |
| vkms-y := \ |
| + vkms_configfs.o \ |
| vkms_drv.o \ |
| vkms_plane.o \ |
| vkms_output.o \ |
| diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..dae2e85d83a14ac6cbda77e8d8973d417ea8a233 |
| --- /dev/null |
| +++ b/drivers/gpu/drm/vkms/vkms_configfs.c |
| @@ -0,0 +1,650 @@ |
| +// SPDX-License-Identifier: GPL-2.0+ |
| + |
| +#include <linux/configfs.h> |
| +#include <linux/mutex.h> |
| +#include <linux/platform_device.h> |
| + |
| +#include <drm/drm_plane.h> |
| +#include <drm/drm_print.h> |
| + |
| +#include "vkms_drv.h" |
| + |
| +/** |
| + * DOC: ConfigFS Support for VKMS |
| + * |
| + * VKMS is instrumented with support for configuration via :doc:`ConfigFS |
| + * <../filesystems/configfs>`. |
| + * |
| + * With VKMS installed, you can mount ConfigFS at ``/config/`` like so:: |
| + * |
| + * mkdir -p /config/ |
| + * sudo mount -t configfs none /config |
| + * |
| + * This allows you to configure multiple virtual devices. Note |
| + * that the default device which can be enabled in the module params with:: |
| + * |
| + * modprobe vkms default_device=1 |
| + * |
| + * is immutable because we cannot pre-populate ConfigFS directories with normal |
| + * files. |
| + * |
| + * To set up a new device, create a new directory under the VKMS configfs |
| + * directory:: |
| + * |
| + * mkdir /config/vkms/test |
| + * |
| + * With your device created you'll find an new directory ready to be |
| + * configured:: |
| + * |
| + * /config |
| + * `-- vkms |
| + * `-- test |
| + * |-- connectors |
| + * |-- crtcs |
| + * |-- encoders |
| + * |-- planes |
| + * `-- enabled |
| + * |
| + * Each directory you add within the connectors, crtcs, encoders, and planes |
| + * directories will let you configure a new object of that type. Adding new |
| + * objects will automatically create a set of default files and folders you can |
| + * use to configure that object. |
| + * |
| + * For instance, we can set up a two-output device like so:: |
| + * |
| + * DRM_PLANE_TYPE_PRIMARY=1 |
| + * DRM_PLANE_TYPE_CURSOR=2 |
| + * DRM_PLANE_TYPE_OVERLAY=0 |
| + * |
| + * mkdir /config/vkms/test/planes/primary |
| + * echo $DRM_PLANE_TYPE_PRIMARY > /config/vkms/test/planes/primary/type |
| + * |
| + * mkdir /config/vkms/test/planes/other_primary |
| + * echo $DRM_PLANE_TYPE_PRIMARY > /config/vkms/test/planes/other_primary/type |
| + * |
| + * mkdir /config/vkms/test/crtcs/crtc |
| + * mkdir /config/vkms/test/crtcs/crtc_other |
| + * |
| + * mkdir /config/vkms/test/encoders/encoder |
| + * mkdir /config/vkms/test/encoders/encoder_other |
| + * |
| + * mkdir /config/vkms/test/connectors/connector |
| + * mkdir /config/vkms/test/connectors/connector_other |
| + * |
| + * You can see that specific attributes, such as ``.../<plane>/type``, can be |
| + * configured by writing into them. Associating objects together can be done via |
| + * symlinks:: |
| + * |
| + * ln -s /config/vkms/test/encoders/encoder /config/vkms/test/connectors/connector/possible_encoders |
| + * ln -s /config/vkms/test/encoders/encoder_other /config/vkms/test/connectors/connector_other/possible_encoders |
| + * |
| + * ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/primary/possible_crtcs/ |
| + * ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/other_primary/possible_crtcs/ |
| + * |
| + * ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/encoders/encoder/possible_crtcs/ |
| + * ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/encoders/encoder_other/possible_crtcs/ |
| + * |
| + * Finally, to enable your configured device, just write 1 to the ``enabled`` |
| + * file:: |
| + * |
| + * echo 1 > /config/vkms/test/enabled |
| + * |
| + * When you're done with the virtual device, you can clean up the device like |
| + * so:: |
| + * |
| + * echo 0 > /config/vkms/test/enabled |
| + * |
| + * rm /config/vkms/test/connectors/connector/possible_encoders/encoder |
| + * rm /config/vkms/test/encoders/encoder/possible_crtcs/crtc |
| + * rm /config/vkms/test/planes/primary/possible_crtcs/crtc |
| + * rm /config/vkms/test/planes/cursor/possible_crtcs/crtc |
| + * rm /config/vkms/test/planes/overlay/possible_crtcs/crtc |
| + * rm /config/vkms/test/planes/overlay/possible_crtcs/crtc_other |
| + * rm /config/vkms/test/planes/other_primary/possible_crtcs/crtc_other |
| + * |
| + * rmdir /config/vkms/test/planes/primary |
| + * rmdir /config/vkms/test/planes/other_primary |
| + * rmdir /config/vkms/test/planes/cursor |
| + * rmdir /config/vkms/test/planes/overlay |
| + * rmdir /config/vkms/test/crtcs/crtc |
| + * rmdir /config/vkms/test/crtcs/crtc_other |
| + * rmdir /config/vkms/test/encoders/encoder |
| + * rmdir /config/vkms/test/encoders/encoder_other |
| + * rmdir /config/vkms/test/connectors/connector |
| + * rmdir /config/vkms/test/connectors/connector_other |
| + * |
| + * rmdir /config/vkms/test |
| + */ |
| + |
| +/* |
| + * Common helpers (i.e. common sub-groups) |
| + */ |
| + |
| +/* Possible CRTCs, e.g. /config/vkms/device/<object>/possible_crtcs/<symlink> */ |
| + |
| +static struct config_item_type crtc_type; |
| + |
| +static int possible_crtcs_allow_link(struct config_item *src, |
| + struct config_item *target) |
| +{ |
| + struct vkms_config_links *links = item_to_config_links(src); |
| + struct vkms_config_crtc *crtc; |
| + |
| + if (target->ci_type != &crtc_type) { |
| + DRM_ERROR("Unable to link non-CRTCs.\n"); |
| + return -EINVAL; |
| + } |
| + |
| + crtc = item_to_config_crtc(target); |
| + |
| + if (links->linked_object_bitmap & BIT(crtc->crtc_config_idx)) { |
| + DRM_ERROR( |
| + "Tried to add two symlinks to the same CRTC from the same object\n"); |
| + return -EINVAL; |
| + } |
| + |
| + links->linked_object_bitmap |= BIT(crtc->crtc_config_idx); |
| + |
| + return 0; |
| +} |
| + |
| +static void possible_crtcs_drop_link(struct config_item *src, |
| + struct config_item *target) |
| +{ |
| + struct vkms_config_links *links = item_to_config_links(src); |
| + struct vkms_config_crtc *crtc = item_to_config_crtc(target); |
| + |
| + links->linked_object_bitmap &= ~BIT(crtc->crtc_config_idx); |
| +} |
| + |
| +static struct configfs_item_operations possible_crtcs_item_ops = { |
| + .allow_link = &possible_crtcs_allow_link, |
| + .drop_link = &possible_crtcs_drop_link, |
| +}; |
| + |
| +static struct config_item_type possible_crtcs_group_type = { |
| + .ct_item_ops = &possible_crtcs_item_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static void add_possible_crtcs(struct config_group *parent, |
| + struct config_group *possible_crtcs) |
| +{ |
| + config_group_init_type_name(possible_crtcs, "possible_crtcs", |
| + &possible_crtcs_group_type); |
| + configfs_add_default_group(possible_crtcs, parent); |
| +} |
| + |
| +/* Possible encoders, e.g. /config/vkms/device/connector/possible_encoders/<symlink> */ |
| + |
| +static struct config_item_type encoder_type; |
| + |
| +static int possible_encoders_allow_link(struct config_item *src, |
| + struct config_item *target) |
| +{ |
| + struct vkms_config_links *links = item_to_config_links(src); |
| + struct vkms_config_encoder *encoder; |
| + |
| + if (target->ci_type != &encoder_type) { |
| + DRM_ERROR("Unable to link non-encoders.\n"); |
| + return -EINVAL; |
| + } |
| + |
| + encoder = item_to_config_encoder(target); |
| + |
| + if (links->linked_object_bitmap & BIT(encoder->encoder_config_idx)) { |
| + DRM_ERROR( |
| + "Tried to add two symlinks to the same encoder from the same object\n"); |
| + return -EINVAL; |
| + } |
| + |
| + links->linked_object_bitmap |= BIT(encoder->encoder_config_idx); |
| + |
| + return 0; |
| +} |
| + |
| +static void possible_encoders_drop_link(struct config_item *src, |
| + struct config_item *target) |
| +{ |
| + struct vkms_config_links *links = item_to_config_links(src); |
| + struct vkms_config_encoder *encoder = item_to_config_encoder(target); |
| + |
| + links->linked_object_bitmap &= ~BIT(encoder->encoder_config_idx); |
| +} |
| + |
| +static struct configfs_item_operations possible_encoders_item_ops = { |
| + .allow_link = &possible_encoders_allow_link, |
| + .drop_link = &possible_encoders_drop_link, |
| +}; |
| + |
| +static struct config_item_type possible_encoders_group_type = { |
| + .ct_item_ops = &possible_encoders_item_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static void add_possible_encoders(struct config_group *parent, |
| + struct config_group *possible_encoders) |
| +{ |
| + config_group_init_type_name(possible_encoders, "possible_encoders", |
| + &possible_encoders_group_type); |
| + configfs_add_default_group(possible_encoders, parent); |
| +} |
| + |
| +/* |
| + * Individual objects (connectors, crtcs, encoders, planes): |
| + */ |
| + |
| +/* Connector item, e.g. /config/vkms/device/connectors/ID */ |
| + |
| +static struct config_item_type connector_type = { |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +/* Crtc item, e.g. /config/vkms/device/crtcs/ID */ |
| + |
| +static struct config_item_type crtc_type = { |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +/* Encoder item, e.g. /config/vkms/device/encoder/ID */ |
| + |
| +static struct config_item_type encoder_type = { |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +/* Plane item, e.g. /config/vkms/device/planes/ID */ |
| + |
| +static ssize_t plane_type_show(struct config_item *item, char *buf) |
| +{ |
| + struct vkms_config_plane *plane = item_to_config_plane(item); |
| + struct vkms_configfs *configfs = plane_item_to_configfs(item); |
| + enum drm_plane_type plane_type; |
| + |
| + mutex_lock(&configfs->lock); |
| + plane_type = plane->type; |
| + mutex_unlock(&configfs->lock); |
| + |
| + return sprintf(buf, "%u", plane_type); |
| +} |
| + |
| +static ssize_t plane_type_store(struct config_item *item, const char *buf, |
| + size_t len) |
| +{ |
| + struct vkms_config_plane *plane = item_to_config_plane(item); |
| + struct vkms_configfs *configfs = plane_item_to_configfs(item); |
| + int val, ret; |
| + |
| + ret = kstrtouint(buf, 10, &val); |
| + if (ret) |
| + return ret; |
| + |
| + if (val != DRM_PLANE_TYPE_PRIMARY && val != DRM_PLANE_TYPE_CURSOR && |
| + val != DRM_PLANE_TYPE_OVERLAY) |
| + return -EINVAL; |
| + |
| + mutex_lock(&configfs->lock); |
| + plane->type = val; |
| + mutex_unlock(&configfs->lock); |
| + |
| + return len; |
| +} |
| + |
| +CONFIGFS_ATTR(plane_, type); |
| + |
| +static struct configfs_attribute *plane_attrs[] = { |
| + &plane_attr_type, |
| + NULL, |
| +}; |
| + |
| +static struct config_item_type plane_type = { |
| + .ct_attrs = plane_attrs, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +/* |
| + * Directory groups, e.g. /config/vkms/device/{planes, crtcs, ...} |
| + */ |
| + |
| +/* Connectors group: /config/vkms/device/connectors/ */ |
| + |
| +static struct config_group *connectors_group_make(struct config_group *group, |
| + const char *name) |
| +{ |
| + struct vkms_config_connector *connector = |
| + kzalloc(sizeof(*connector), GFP_KERNEL); |
| + if (!connector) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + config_group_init_type_name(&connector->config_group, name, |
| + &connector_type); |
| + add_possible_encoders(&connector->config_group, |
| + &connector->possible_encoders.group); |
| + |
| + return &connector->config_group; |
| +} |
| + |
| +static void connectors_group_drop(struct config_group *group, |
| + struct config_item *item) |
| +{ |
| + struct vkms_config_connector *connector = |
| + item_to_config_connector(item); |
| + kfree(connector); |
| +} |
| + |
| +static struct configfs_group_operations connectors_group_ops = { |
| + .make_group = &connectors_group_make, |
| + .drop_item = &connectors_group_drop, |
| +}; |
| + |
| +static struct config_item_type connectors_group_type = { |
| + .ct_group_ops = &connectors_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +/* CRTCs group: /config/vkms/device/crtcs/ */ |
| + |
| +static struct config_group *crtcs_group_make(struct config_group *group, |
| + const char *name) |
| +{ |
| + struct vkms_configfs *configfs = |
| + container_of(group, struct vkms_configfs, crtcs_group); |
| + unsigned long next_idx; |
| + struct vkms_config_crtc *crtc; |
| + |
| + mutex_lock(&configfs->lock); |
| + |
| + next_idx = find_first_zero_bit(&configfs->allocated_crtcs, |
| + VKMS_MAX_OUTPUT_OBJECTS); |
| + |
| + if (next_idx == VKMS_MAX_OUTPUT_OBJECTS) { |
| + DRM_ERROR("Unable to allocate another CRTC.\n"); |
| + mutex_unlock(&configfs->lock); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + |
| + crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); |
| + if (!crtc) { |
| + DRM_ERROR("Unable to allocate CRTC.\n"); |
| + mutex_unlock(&configfs->lock); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + |
| + config_group_init_type_name(&crtc->config_group, name, &crtc_type); |
| + crtc->crtc_config_idx = next_idx; |
| + |
| + set_bit(next_idx, &configfs->allocated_crtcs); |
| + |
| + mutex_unlock(&configfs->lock); |
| + |
| + return &crtc->config_group; |
| +} |
| + |
| +static void crtcs_group_drop(struct config_group *group, |
| + struct config_item *item) |
| +{ |
| + struct vkms_config_crtc *crtc = item_to_config_crtc(item); |
| + |
| + kfree(crtc); |
| +} |
| + |
| +static struct configfs_group_operations crtcs_group_ops = { |
| + .make_group = &crtcs_group_make, |
| + .drop_item = &crtcs_group_drop, |
| +}; |
| + |
| +static struct config_item_type crtcs_group_type = { |
| + .ct_group_ops = &crtcs_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +/* Encoders group: /config/vkms/device/encoders/ */ |
| + |
| +static struct config_group *encoders_group_make(struct config_group *group, |
| + const char *name) |
| +{ |
| + struct vkms_configfs *configfs = |
| + container_of(group, struct vkms_configfs, encoders_group); |
| + unsigned long next_idx; |
| + struct vkms_config_encoder *encoder; |
| + |
| + mutex_lock(&configfs->lock); |
| + |
| + next_idx = find_first_zero_bit(&configfs->allocated_encoders, |
| + VKMS_MAX_OUTPUT_OBJECTS); |
| + |
| + if (next_idx == VKMS_MAX_OUTPUT_OBJECTS) { |
| + DRM_ERROR("Unable to allocate another encoder.\n"); |
| + mutex_unlock(&configfs->lock); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + |
| + encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); |
| + if (!encoder) { |
| + DRM_ERROR("Unable to allocate encoder.\n"); |
| + mutex_unlock(&configfs->lock); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + |
| + config_group_init_type_name(&encoder->config_group, name, |
| + &encoder_type); |
| + add_possible_crtcs(&encoder->config_group, |
| + &encoder->possible_crtcs.group); |
| + encoder->encoder_config_idx = next_idx; |
| + set_bit(next_idx, &configfs->allocated_encoders); |
| + |
| + mutex_unlock(&configfs->lock); |
| + |
| + return &encoder->config_group; |
| +} |
| + |
| +static void encoders_group_drop(struct config_group *group, |
| + struct config_item *item) |
| +{ |
| + struct vkms_config_encoder *encoder = item_to_config_encoder(item); |
| + |
| + kfree(encoder); |
| +} |
| + |
| +static struct configfs_group_operations encoders_group_ops = { |
| + .make_group = &encoders_group_make, |
| + .drop_item = &encoders_group_drop, |
| +}; |
| + |
| +static struct config_item_type encoders_group_type = { |
| + .ct_group_ops = &encoders_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +/* Planes group: /config/vkms/device/planes/ */ |
| + |
| +static struct config_group *make_plane_group(struct config_group *group, |
| + const char *name) |
| +{ |
| + struct vkms_config_plane *plane = kzalloc(sizeof(*plane), GFP_KERNEL); |
| + |
| + if (!plane) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + config_group_init_type_name(&plane->config_group, name, &plane_type); |
| + add_possible_crtcs(&plane->config_group, &plane->possible_crtcs.group); |
| + |
| + return &plane->config_group; |
| +} |
| + |
| +static void drop_plane_group(struct config_group *group, |
| + struct config_item *item) |
| +{ |
| + struct vkms_config_plane *plane = item_to_config_plane(item); |
| + |
| + kfree(plane); |
| +} |
| + |
| +static struct configfs_group_operations plane_group_ops = { |
| + .make_group = &make_plane_group, |
| + .drop_item = &drop_plane_group, |
| +}; |
| + |
| +static struct config_item_type planes_group_type = { |
| + .ct_group_ops = &plane_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +/* Root directory group, e.g. /config/vkms/device */ |
| + |
| +static ssize_t device_enabled_show(struct config_item *item, char *buf) |
| +{ |
| + struct vkms_configfs *configfs = item_to_configfs(item); |
| + bool is_enabled; |
| + |
| + mutex_lock(&configfs->lock); |
| + is_enabled = configfs->vkms_device != NULL; |
| + mutex_unlock(&configfs->lock); |
| + |
| + return sprintf(buf, "%d", is_enabled); |
| +} |
| + |
| +static ssize_t device_enabled_store(struct config_item *item, const char *buf, |
| + size_t len) |
| +{ |
| + struct vkms_configfs *configfs = item_to_configfs(item); |
| + struct vkms_device *device; |
| + int value, ret; |
| + |
| + ret = kstrtoint(buf, 0, &value); |
| + if (ret) |
| + return ret; |
| + |
| + if (value != 1) |
| + return -EINVAL; |
| + |
| + mutex_lock(&configfs->lock); |
| + |
| + if (configfs->vkms_device) { |
| + mutex_unlock(&configfs->lock); |
| + return len; |
| + } |
| + |
| + device = vkms_add_device(configfs); |
| + mutex_unlock(&configfs->lock); |
| + |
| + if (IS_ERR(device)) |
| + return -PTR_ERR(device); |
| + |
| + return len; |
| +} |
| + |
| +CONFIGFS_ATTR(device_, enabled); |
| + |
| +static ssize_t device_id_show(struct config_item *item, char *buf) |
| +{ |
| + struct vkms_configfs *configfs = item_to_configfs(item); |
| + int id = -1; |
| + |
| + mutex_lock(&configfs->lock); |
| + if (configfs->vkms_device) |
| + id = configfs->vkms_device->platform->id; |
| + |
| + mutex_unlock(&configfs->lock); |
| + |
| + return sprintf(buf, "%d", id); |
| +} |
| + |
| +CONFIGFS_ATTR_RO(device_, id); |
| + |
| +static struct configfs_attribute *device_group_attrs[] = { |
| + &device_attr_id, |
| + &device_attr_enabled, |
| + NULL, |
| +}; |
| + |
| +static struct config_item_type device_group_type = { |
| + .ct_attrs = device_group_attrs, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static void vkms_configfs_setup_default_groups(struct vkms_configfs *configfs, |
| + const char *name) |
| +{ |
| + config_group_init_type_name(&configfs->device_group, name, |
| + &device_group_type); |
| + |
| + config_group_init_type_name(&configfs->connectors_group, "connectors", |
| + &connectors_group_type); |
| + configfs_add_default_group(&configfs->connectors_group, |
| + &configfs->device_group); |
| + |
| + config_group_init_type_name(&configfs->crtcs_group, "crtcs", |
| + &crtcs_group_type); |
| + configfs_add_default_group(&configfs->crtcs_group, |
| + &configfs->device_group); |
| + |
| + config_group_init_type_name(&configfs->encoders_group, "encoders", |
| + &encoders_group_type); |
| + configfs_add_default_group(&configfs->encoders_group, |
| + &configfs->device_group); |
| + |
| + config_group_init_type_name(&configfs->planes_group, "planes", |
| + &planes_group_type); |
| + configfs_add_default_group(&configfs->planes_group, |
| + &configfs->device_group); |
| +} |
| + |
| +/* Root directory group and subsystem, e.g. /config/vkms/ */ |
| + |
| +static struct config_group *make_root_group(struct config_group *group, |
| + const char *name) |
| +{ |
| + struct vkms_configfs *configfs = kzalloc(sizeof(*configfs), GFP_KERNEL); |
| + |
| + if (!configfs) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + vkms_configfs_setup_default_groups(configfs, name); |
| + mutex_init(&configfs->lock); |
| + |
| + return &configfs->device_group; |
| +} |
| + |
| +static void drop_root_group(struct config_group *group, |
| + struct config_item *item) |
| +{ |
| + struct vkms_configfs *configfs = item_to_configfs(item); |
| + |
| + mutex_lock(&configfs->lock); |
| + if (configfs->vkms_device) |
| + vkms_remove_device(configfs->vkms_device); |
| + mutex_unlock(&configfs->lock); |
| + |
| + kfree(configfs); |
| +} |
| + |
| +static struct configfs_group_operations root_group_ops = { |
| + .make_group = &make_root_group, |
| + .drop_item = &drop_root_group, |
| +}; |
| + |
| +static struct config_item_type vkms_type = { |
| + .ct_group_ops = &root_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static struct configfs_subsystem vkms_subsys = { |
| + .su_group = { |
| + .cg_item = { |
| + .ci_name = "vkms", |
| + .ci_type = &vkms_type, |
| + }, |
| + }, |
| + .su_mutex = __MUTEX_INITIALIZER(vkms_subsys.su_mutex), |
| +}; |
| + |
| +int vkms_init_configfs(void) |
| +{ |
| + config_group_init(&vkms_subsys.su_group); |
| + return configfs_register_subsystem(&vkms_subsys); |
| +} |
| + |
| +void vkms_unregister_configfs(void) |
| +{ |
| + configfs_unregister_subsystem(&vkms_subsys); |
| +} |
| diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c |
| index 6c94c2b5d52922dfcf3bd987017884d9566485ec..819e880a8cf780d466b1bb71385cb42404ce5448 100644 |
| --- a/drivers/gpu/drm/vkms/vkms_drv.c |
| +++ b/drivers/gpu/drm/vkms/vkms_drv.c |
| @@ -9,8 +9,10 @@ |
| * the GPU in DRM API tests. |
| */ |
| |
| -#include "asm-generic/errno-base.h" |
| +#include <linux/configfs.h> |
| #include <linux/device.h> |
| +#include <linux/dma-mapping.h> |
| +#include <linux/err.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/dma-mapping.h> |
| @@ -172,8 +174,8 @@ static int vkms_modeset_init(struct vkms_device *vkmsdev) |
| dev->mode_config.preferred_depth = 0; |
| dev->mode_config.helper_private = &vkms_mode_config_helpers; |
| |
| - return vkmsdev->is_default ? vkms_output_init_default(vkmsdev) : |
| - -EINVAL; |
| + return vkmsdev->configfs ? vkms_output_init(vkmsdev) : |
| + vkms_output_init_default(vkmsdev); |
| } |
| |
| static int vkms_platform_probe(struct platform_device *pdev) |
| @@ -184,8 +186,10 @@ static int vkms_platform_probe(struct platform_device *pdev) |
| void *grp; |
| |
| grp = devres_open_group(&pdev->dev, NULL, GFP_KERNEL); |
| - if (!grp) |
| + if (!grp) { |
| + DRM_ERROR("Could not open devres group\n"); |
| return -ENOMEM; |
| + } |
| |
| vkms_device = devm_drm_dev_alloc(&pdev->dev, &vkms_driver, |
| struct vkms_device, drm); |
| @@ -198,7 +202,7 @@ static int vkms_platform_probe(struct platform_device *pdev) |
| vkms_device->config.cursor = enable_cursor; |
| vkms_device->config.writeback = enable_writeback; |
| vkms_device->config.overlay = enable_overlay; |
| - vkms_device->is_default = vkms_device_setup->is_default; |
| + vkms_device->configfs = vkms_device_setup->configfs; |
| |
| ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev, |
| DMA_BIT_MASK(64)); |
| @@ -258,12 +262,43 @@ static struct platform_driver vkms_platform_driver = { |
| .driver.name = DRIVER_NAME, |
| }; |
| |
| +struct vkms_device *vkms_add_device(struct vkms_configfs *configfs) |
| +{ |
| + struct device *dev = NULL; |
| + struct platform_device *pdev; |
| + int max_id = 1; |
| + struct vkms_device_setup vkms_device_setup = { |
| + .configfs = configfs, |
| + }; |
| + |
| + while ((dev = platform_find_device_by_driver( |
| + dev, &vkms_platform_driver.driver))) { |
| + pdev = to_platform_device(dev); |
| + max_id = max(max_id, pdev->id); |
| + } |
| + |
| + pdev = platform_device_register_data(NULL, DRIVER_NAME, max_id + 1, |
| + &vkms_device_setup, |
| + sizeof(vkms_device_setup)); |
| + if (IS_ERR(pdev)) { |
| + DRM_ERROR("Unable to register vkms device'\n"); |
| + return ERR_PTR(PTR_ERR(pdev)); |
| + } |
| + |
| + return platform_get_drvdata(pdev); |
| +} |
| + |
| +void vkms_remove_device(struct vkms_device *vkms_device) |
| +{ |
| + platform_device_unregister(vkms_device->platform); |
| +} |
| + |
| static int __init vkms_init(void) |
| { |
| int ret; |
| struct platform_device *pdev; |
| struct vkms_device_setup vkms_device_setup = { |
| - .is_default = true, |
| + .configfs = NULL, |
| }; |
| |
| ret = platform_driver_register(&vkms_platform_driver); |
| @@ -281,6 +316,13 @@ static int __init vkms_init(void) |
| return PTR_ERR(pdev); |
| } |
| |
| + ret = vkms_init_configfs(); |
| + if (ret) { |
| + DRM_ERROR("Unable to initialize configfs\n"); |
| + platform_device_unregister(pdev); |
| + platform_driver_unregister(&vkms_platform_driver); |
| + } |
| + |
| return 0; |
| } |
| |
| @@ -288,6 +330,8 @@ static void __exit vkms_exit(void) |
| { |
| struct device *dev; |
| |
| + vkms_unregister_configfs(); |
| + |
| while ((dev = platform_find_device_by_driver( |
| NULL, &vkms_platform_driver.driver))) { |
| // platform_find_device_by_driver increments the refcount. Drop |
| diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h |
| index 4262dcffd7e15c68a607f3eb49d6c84a00369971..8cdd7949f6614a362466a500fc600de6449c31f5 100644 |
| --- a/drivers/gpu/drm/vkms/vkms_drv.h |
| +++ b/drivers/gpu/drm/vkms/vkms_drv.h |
| @@ -3,6 +3,7 @@ |
| #ifndef _VKMS_DRV_H_ |
| #define _VKMS_DRV_H_ |
| |
| +#include <linux/configfs.h> |
| #include <linux/hrtimer.h> |
| |
| #include <drm/drm.h> |
| @@ -10,6 +11,7 @@ |
| #include <drm/drm_gem.h> |
| #include <drm/drm_gem_atomic_helper.h> |
| #include <drm/drm_encoder.h> |
| +#include <drm/drm_plane.h> |
| #include <drm/drm_writeback.h> |
| |
| #define XRES_MIN 10 |
| @@ -138,14 +140,65 @@ struct vkms_config { |
| bool overlay; |
| }; |
| |
| +struct vkms_config_links { |
| + struct config_group group; |
| + unsigned long linked_object_bitmap; |
| +}; |
| + |
| +struct vkms_config_connector { |
| + struct config_group config_group; |
| + struct vkms_config_links possible_encoders; |
| +}; |
| + |
| +struct vkms_config_crtc { |
| + struct config_group config_group; |
| + unsigned long crtc_config_idx; |
| +}; |
| + |
| +struct vkms_config_encoder { |
| + struct config_group config_group; |
| + struct vkms_config_links possible_crtcs; |
| + unsigned long encoder_config_idx; |
| +}; |
| + |
| +struct vkms_config_plane { |
| + struct vkms_configfs *configfs; |
| + struct config_group config_group; |
| + struct vkms_config_links possible_crtcs; |
| + enum drm_plane_type type; |
| +}; |
| + |
| +struct vkms_configfs { |
| + /* Directory group containing connector configs, e.g. /config/vkms/device/ */ |
| + struct config_group device_group; |
| + /* Directory group containing connector configs, e.g. /config/vkms/device/connectors/ */ |
| + struct config_group connectors_group; |
| + /* Directory group containing CRTC configs, e.g. /config/vkms/device/crtcs/ */ |
| + struct config_group crtcs_group; |
| + /* Directory group containing encoder configs, e.g. /config/vkms/device/encoders/ */ |
| + struct config_group encoders_group; |
| + /* Directory group containing plane configs, e.g. /config/vkms/device/planes/ */ |
| + struct config_group planes_group; |
| + |
| + unsigned long allocated_crtcs; |
| + unsigned long allocated_encoders; |
| + |
| + struct mutex lock; |
| + |
| + /* The platform device if this is registered, otherwise NULL */ |
| + struct vkms_device *vkms_device; |
| +}; |
| + |
| struct vkms_device_setup { |
| - bool is_default; |
| + // Is NULL in the case of the default card. |
| + struct vkms_configfs *configfs; |
| }; |
| |
| struct vkms_device { |
| struct drm_device drm; |
| struct platform_device *platform; |
| - bool is_default; |
| + // Is NULL in the case of the default card. |
| + struct vkms_configfs *configfs; |
| struct vkms_output output; |
| struct vkms_config config; |
| }; |
| @@ -164,11 +217,42 @@ struct vkms_device { |
| #define to_vkms_plane_state(target)\ |
| container_of(target, struct vkms_plane_state, base.base) |
| |
| +#define item_to_configfs(item) \ |
| + container_of(to_config_group(item), struct vkms_configfs, device_group) |
| + |
| +#define item_to_config_connector(item) \ |
| + container_of(to_config_group(item), struct vkms_config_connector, \ |
| + config_group) |
| + |
| +#define item_to_config_crtc(item) \ |
| + container_of(to_config_group(item), struct vkms_config_crtc, \ |
| + config_group) |
| + |
| +#define item_to_config_encoder(item) \ |
| + container_of(to_config_group(item), struct vkms_config_encoder, \ |
| + config_group) |
| + |
| +#define item_to_config_plane(item) \ |
| + container_of(to_config_group(item), struct vkms_config_plane, \ |
| + config_group) |
| + |
| +#define item_to_config_links(item) \ |
| + container_of(to_config_group(item), struct vkms_config_links, group) |
| + |
| +#define plane_item_to_configfs(item) \ |
| + container_of(to_config_group(item->ci_parent), struct vkms_configfs, \ |
| + planes_group) |
| + |
| +/* Devices */ |
| +struct vkms_device *vkms_add_device(struct vkms_configfs *configfs); |
| +void vkms_remove_device(struct vkms_device *vkms_device); |
| + |
| /* CRTC */ |
| struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev, |
| struct drm_plane *primary, |
| struct drm_plane *cursor); |
| |
| +int vkms_output_init(struct vkms_device *vkmsdev); |
| int vkms_output_init_default(struct vkms_device *vkmsdev); |
| |
| struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev, |
| @@ -191,4 +275,8 @@ void vkms_writeback_row(struct vkms_writeback_job *wb, const struct line_buffer |
| int vkms_enable_writeback_connector(struct vkms_device *vkmsdev, |
| struct vkms_crtc *vkms_crtc); |
| |
| +/* ConfigFS Support */ |
| +int vkms_init_configfs(void); |
| +void vkms_unregister_configfs(void); |
| + |
| #endif /* _VKMS_DRV_H_ */ |
| diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c |
| index bfc2e2362c6d2c0e6cf90de5d207db21bbc7ad67..dc69959c5e1d037df5ab601873ed566372ddf9f4 100644 |
| --- a/drivers/gpu/drm/vkms/vkms_output.c |
| +++ b/drivers/gpu/drm/vkms/vkms_output.c |
| @@ -176,3 +176,8 @@ int vkms_output_init_default(struct vkms_device *vkmsdev) |
| |
| return ret; |
| } |
| + |
| +int vkms_output_init(struct vkms_device *vkmsdev) |
| +{ |
| + return -EOPNOTSUPP; |
| +} |
| -- |
| 2.44.0.478.gd926399ef9-goog |
| |