linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/6] Adds support for ConfigFS to VKMS!
@ 2023-06-23 22:23 Jim Shargo
  2023-06-23 22:23 ` [PATCH v2 1/6] drm/vkms: Back VKMS with DRM memory management instead of static objects Jim Shargo
                   ` (7 more replies)
  0 siblings, 8 replies; 21+ messages in thread
From: Jim Shargo @ 2023-06-23 22:23 UTC (permalink / raw)
  To: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: Jim Shargo, dri-devel, linux-doc, linux-kernel

Intro
=====

At long last, we're back!

This patchset adds basic ConfigFS support to VKMS, allowing users to
build new DRM devices with user-defined DRM objects and object
relationships by creating, writing, and symlinking files.

Usage
=====

After installing these patches, you can create a VKMS device with two
displays and a movable overlay like so (this is documented in the
patches):

  $ modprobe vkms enable_overlay=1 enable_cursor=1 enable_writeback=1
  $ mkdir -p /config/
  $ mount -t configfs none /config

  $ export DRM_PLANE_TYPE_PRIMARY=1
  $ export DRM_PLANE_TYPE_CURSOR=2
  $ export DRM_PLANE_TYPE_OVERLAY=0

  $ mkdir /config/vkms/test

  $ 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/planes/cursor
  $ echo $DRM_PLANE_TYPE_CURSOR > /config/vkms/test/planes/cursor/type

  $ mkdir /config/vkms/test/planes/overlay
  $ echo $DRM_PLANE_TYPE_OVERLAY > /config/vkms/test/planes/overlay/type

  $ mkdir /config/vkms/test/crtcs/crtc
  $ mkdir /config/vkms/test/crtcs/crtc_other
  $ mkdir /config/vkms/test/encoders/encoder
  $ mkdir /config/vkms/test/connectors/connector

  $ ln -s /config/vkms/test/encoders/encoder /config/vkms/test/connectors/connector/possible_encoders
  $ ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/encoders/encoder/possible_crtcs/
  $ ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/primary/possible_crtcs/
  $ ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/cursor/possible_crtcs/
  $ ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/overlay/possible_crtcs/
  $ ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/overlay/possible_crtcs/
  $ ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/other_primary/possible_crtcs/

  $ echo 1 > /config/vkms/test/enabled

Changes within core VKMS
========================

This introduces a few important changes to the overall structure of
VKMS:

  - Devices are now memory managed!
  - Support for multiple CRTCs and other objects has been added

Since v1
========

  - Added DRMM memory management to automatically clean up resources
  - Added a param to disable the default device
  - Renamed "cards" to "devices" to improve legibility
  - Added a lock for the configfs setup handler
  - Moved all the new docs into the relevant .c file
  - Addressed as many of sean@poorly.run as possible

Testing
=======

  - New IGT tests (see
    gitlab.freedesktop.org/jshargo/igt-gpu-tools/-/merge_requests/1)
  - Existing IGT tests (excluding .*suspend.*, including .*kms_flip.*
    .*kms_writeback.* .*kms_cursor_crc.*, .*kms_plane.*)

Outro
=====

I'm excited to share these changes, it's my still my first kernel patch
and I've been putting a lot of love into these.

Jim Shargo (6):
  drm/vkms: Back VKMS with DRM memory management instead of static
    objects
  drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device
  drm/vkms: Provide platform data when creating VKMS devices
  drm/vkms: Add ConfigFS scaffolding to VKMS
  drm/vkms: Support enabling ConfigFS devices
  drm/vkms: Add a module param to enable/disable the default device

 Documentation/gpu/vkms.rst            |  17 +-
 drivers/gpu/drm/Kconfig               |   1 +
 drivers/gpu/drm/vkms/Makefile         |   1 +
 drivers/gpu/drm/vkms/vkms_composer.c  |  28 +-
 drivers/gpu/drm/vkms/vkms_configfs.c  | 657 ++++++++++++++++++++++++++
 drivers/gpu/drm/vkms/vkms_crtc.c      |  97 ++--
 drivers/gpu/drm/vkms/vkms_drv.c       | 208 +++++---
 drivers/gpu/drm/vkms/vkms_drv.h       | 166 +++++--
 drivers/gpu/drm/vkms/vkms_output.c    | 299 ++++++++++--
 drivers/gpu/drm/vkms/vkms_plane.c     |  44 +-
 drivers/gpu/drm/vkms/vkms_writeback.c |  26 +-
 11 files changed, 1312 insertions(+), 232 deletions(-)
 create mode 100644 drivers/gpu/drm/vkms/vkms_configfs.c

-- 
2.41.0.162.gfafddb0af9-goog


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 1/6] drm/vkms: Back VKMS with DRM memory management instead of static objects
  2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
@ 2023-06-23 22:23 ` Jim Shargo
  2023-06-25 17:40   ` Maira Canal
  2023-06-23 22:23 ` [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device Jim Shargo
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 21+ messages in thread
From: Jim Shargo @ 2023-06-23 22:23 UTC (permalink / raw)
  To: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: Jim Shargo, dri-devel, linux-doc, linux-kernel

This is a small refactor to make ConfigFS support easier. Once we
support ConfigFS, there can be multiple devices instantiated by the
driver, and so moving everything into managed memory makes things much
easier.

This should be a no-op refactor.

Signed-off-by: Jim Shargo <jshargo@chromium.org>
---
 drivers/gpu/drm/vkms/vkms_drv.c    | 130 +++++++++++++++--------------
 drivers/gpu/drm/vkms/vkms_drv.h    |   4 +-
 drivers/gpu/drm/vkms/vkms_output.c |   6 +-
 3 files changed, 72 insertions(+), 68 deletions(-)

diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
index e3c9c9571c8d..f07454fff046 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -9,10 +9,12 @@
  * the GPU in DRM API tests.
  */
 
+#include <linux/device.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/dma-mapping.h>
 
+#include <drm/drm_device.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
@@ -37,8 +39,6 @@
 #define DRIVER_MAJOR	1
 #define DRIVER_MINOR	0
 
-static struct vkms_config *default_config;
-
 static bool enable_cursor = true;
 module_param_named(enable_cursor, enable_cursor, bool, 0444);
 MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
@@ -92,13 +92,13 @@ static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
 
 static int vkms_config_show(struct seq_file *m, void *data)
 {
-	struct drm_debugfs_entry *entry = m->private;
-	struct drm_device *dev = entry->dev;
-	struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
+	struct drm_info_node *drm_info = m->private;
+	struct vkms_device *vkms_device =
+		drm_device_to_vkms_device(drm_info->minor->dev);
 
-	seq_printf(m, "writeback=%d\n", vkmsdev->config->writeback);
-	seq_printf(m, "cursor=%d\n", vkmsdev->config->cursor);
-	seq_printf(m, "overlay=%d\n", vkmsdev->config->overlay);
+	seq_printf(m, "writeback=%d\n", vkms_device->config.writeback);
+	seq_printf(m, "cursor=%d\n", vkms_device->config.cursor);
+	seq_printf(m, "overlay=%d\n", vkms_device->config.overlay);
 
 	return 0;
 }
@@ -155,114 +155,120 @@ static int vkms_modeset_init(struct vkms_device *vkmsdev)
 	return vkms_output_init(vkmsdev, 0);
 }
 
-static int vkms_create(struct vkms_config *config)
+static int vkms_platform_probe(struct platform_device *pdev)
 {
 	int ret;
-	struct platform_device *pdev;
 	struct vkms_device *vkms_device;
+	void *grp;
 
-	pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
-	if (IS_ERR(pdev))
-		return PTR_ERR(pdev);
-
-	if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) {
-		ret = -ENOMEM;
-		goto out_unregister;
+	grp = devres_open_group(&pdev->dev, NULL, GFP_KERNEL);
+	if (!grp) {
+		return -ENOMEM;
 	}
 
 	vkms_device = devm_drm_dev_alloc(&pdev->dev, &vkms_driver,
 					 struct vkms_device, drm);
 	if (IS_ERR(vkms_device)) {
 		ret = PTR_ERR(vkms_device);
-		goto out_devres;
+		goto out_release_group;
 	}
+
 	vkms_device->platform = pdev;
-	vkms_device->config = config;
-	config->dev = vkms_device;
+	vkms_device->config.cursor = enable_cursor;
+	vkms_device->config.writeback = enable_writeback;
+	vkms_device->config.overlay = enable_overlay;
 
 	ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev,
 					   DMA_BIT_MASK(64));
-
 	if (ret) {
 		DRM_ERROR("Could not initialize DMA support\n");
-		goto out_devres;
+		goto out_release_group;
 	}
 
 	ret = drm_vblank_init(&vkms_device->drm, 1);
 	if (ret) {
 		DRM_ERROR("Failed to vblank\n");
-		goto out_devres;
+		goto out_release_group;
 	}
 
 	ret = vkms_modeset_init(vkms_device);
-	if (ret)
-		goto out_devres;
+	if (ret) {
+		DRM_ERROR("Unable to initialize modesetting\n");
+		goto out_release_group;
+	}
 
 	drm_debugfs_add_files(&vkms_device->drm, vkms_config_debugfs_list,
 			      ARRAY_SIZE(vkms_config_debugfs_list));
 
 	ret = drm_dev_register(&vkms_device->drm, 0);
-	if (ret)
-		goto out_devres;
+	if (ret) {
+		DRM_ERROR("Unable to register device\n");
+		return ret;
+	}
 
 	drm_fbdev_generic_setup(&vkms_device->drm, 0);
+	platform_set_drvdata(pdev, vkms_device);
+	devres_close_group(&pdev->dev, grp);
 
 	return 0;
 
-out_devres:
-	devres_release_group(&pdev->dev, NULL);
-out_unregister:
-	platform_device_unregister(pdev);
+out_release_group:
+	devres_release_group(&pdev->dev, grp);
 	return ret;
 }
 
-static int __init vkms_init(void)
+static int vkms_platform_remove(struct platform_device *pdev)
 {
-	int ret;
-	struct vkms_config *config;
-
-	config = kmalloc(sizeof(*config), GFP_KERNEL);
-	if (!config)
-		return -ENOMEM;
-
-	default_config = config;
+	struct vkms_device *vkms_device;
 
-	config->cursor = enable_cursor;
-	config->writeback = enable_writeback;
-	config->overlay = enable_overlay;
+	vkms_device = platform_get_drvdata(pdev);
+	if (!vkms_device)
+		return 0;
 
-	ret = vkms_create(config);
-	if (ret)
-		kfree(config);
-
-	return ret;
+	drm_dev_unregister(&vkms_device->drm);
+	drm_atomic_helper_shutdown(&vkms_device->drm);
+	return 0;
 }
 
-static void vkms_destroy(struct vkms_config *config)
+static struct platform_driver vkms_platform_driver = {
+	.probe = vkms_platform_probe,
+	.remove = vkms_platform_remove,
+	.driver.name = DRIVER_NAME,
+};
+
+static int __init vkms_init(void)
 {
+	int ret;
 	struct platform_device *pdev;
 
-	if (!config->dev) {
-		DRM_INFO("vkms_device is NULL.\n");
-		return;
+	ret = platform_driver_register(&vkms_platform_driver);
+	if (ret) {
+		DRM_ERROR("Unable to register platform driver\n");
+		return ret;
 	}
 
-	pdev = config->dev->platform;
-
-	drm_dev_unregister(&config->dev->drm);
-	drm_atomic_helper_shutdown(&config->dev->drm);
-	devres_release_group(&pdev->dev, NULL);
-	platform_device_unregister(pdev);
+	pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
+	if (IS_ERR(pdev)) {
+		platform_driver_unregister(&vkms_platform_driver);
+		return PTR_ERR(pdev);
+	}
 
-	config->dev = NULL;
+	return 0;
 }
 
 static void __exit vkms_exit(void)
 {
-	if (default_config->dev)
-		vkms_destroy(default_config);
+	struct device *dev;
+
+	while ((dev = platform_find_device_by_driver(
+			NULL, &vkms_platform_driver.driver))) {
+		// platform_find_device_by_driver increments the refcount. Drop 
+		// it so we don't leak memory.
+		put_device(dev);
+		platform_device_unregister(to_platform_device(dev));
+	}
 
-	kfree(default_config);
+	platform_driver_unregister(&vkms_platform_driver);
 }
 
 module_init(vkms_init);
diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
index 5f1a0a44a78c..e87c8aea6fb3 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.h
+++ b/drivers/gpu/drm/vkms/vkms_drv.h
@@ -114,15 +114,13 @@ struct vkms_config {
 	bool writeback;
 	bool cursor;
 	bool overlay;
-	/* only set when instantiated */
-	struct vkms_device *dev;
 };
 
 struct vkms_device {
 	struct drm_device drm;
 	struct platform_device *platform;
 	struct vkms_output output;
-	const struct vkms_config *config;
+	struct vkms_config config;
 };
 
 #define drm_crtc_to_vkms_output(target) \
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 5ce70dd946aa..963a64cf068b 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -62,7 +62,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
 	if (IS_ERR(primary))
 		return PTR_ERR(primary);
 
-	if (vkmsdev->config->overlay) {
+	if (vkmsdev->config.overlay) {
 		for (n = 0; n < NUM_OVERLAY_PLANES; n++) {
 			ret = vkms_add_overlay_plane(vkmsdev, index, crtc);
 			if (ret)
@@ -70,7 +70,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
 		}
 	}
 
-	if (vkmsdev->config->cursor) {
+	if (vkmsdev->config.cursor) {
 		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR, index);
 		if (IS_ERR(cursor))
 			return PTR_ERR(cursor);
@@ -103,7 +103,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
 		goto err_attach;
 	}
 
-	if (vkmsdev->config->writeback) {
+	if (vkmsdev->config.writeback) {
 		writeback = vkms_enable_writeback_connector(vkmsdev);
 		if (writeback)
 			DRM_ERROR("Failed to init writeback connector\n");
-- 
2.41.0.162.gfafddb0af9-goog


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device
  2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
  2023-06-23 22:23 ` [PATCH v2 1/6] drm/vkms: Back VKMS with DRM memory management instead of static objects Jim Shargo
@ 2023-06-23 22:23 ` Jim Shargo
  2023-06-25 17:57   ` Maira Canal
  2023-08-15 11:29   ` Marius Vlad
  2023-06-23 22:23 ` [PATCH v2 3/6] drm/vkms: Provide platform data when creating VKMS devices Jim Shargo
                   ` (5 subsequent siblings)
  7 siblings, 2 replies; 21+ messages in thread
From: Jim Shargo @ 2023-06-23 22:23 UTC (permalink / raw)
  To: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: Jim Shargo, dri-devel, linux-doc, linux-kernel

This change supports multiple CRTCs, encoders, connectors instead of one
of each per device.

Since ConfigFS-based devices will support multiple crtcs, it's useful to
move all of the writeback/composition data from being per-"output" to
being per-CRTC.

Since there's still only ever one CRTC, this should be a no-op refactor.

Signed-off-by: Jim Shargo <jshargo@chromium.org>
---
 drivers/gpu/drm/vkms/vkms_composer.c  |  28 +++---
 drivers/gpu/drm/vkms/vkms_crtc.c      |  95 +++++++++++---------
 drivers/gpu/drm/vkms/vkms_drv.c       |  14 +--
 drivers/gpu/drm/vkms/vkms_drv.h       |  65 +++++++++-----
 drivers/gpu/drm/vkms/vkms_output.c    | 123 ++++++++++++++++++--------
 drivers/gpu/drm/vkms/vkms_plane.c     |  44 ++++++---
 drivers/gpu/drm/vkms/vkms_writeback.c |  26 +++---
 7 files changed, 252 insertions(+), 143 deletions(-)

diff --git a/drivers/gpu/drm/vkms/vkms_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c
index 1636ce3a79f9..75fa42e0ec07 100644
--- a/drivers/gpu/drm/vkms/vkms_composer.c
+++ b/drivers/gpu/drm/vkms/vkms_composer.c
@@ -230,13 +230,13 @@ void vkms_composer_worker(struct work_struct *work)
 						composer_work);
 	struct drm_crtc *crtc = crtc_state->base.crtc;
 	struct vkms_writeback_job *active_wb = crtc_state->active_writeback;
-	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 	bool crc_pending, wb_pending;
 	u64 frame_start, frame_end;
 	u32 crc32 = 0;
 	int ret;
 
-	spin_lock_irq(&out->composer_lock);
+	spin_lock_irq(&vkms_crtc->composer_lock);
 	frame_start = crtc_state->frame_start;
 	frame_end = crtc_state->frame_end;
 	crc_pending = crtc_state->crc_pending;
@@ -244,7 +244,7 @@ void vkms_composer_worker(struct work_struct *work)
 	crtc_state->frame_start = 0;
 	crtc_state->frame_end = 0;
 	crtc_state->crc_pending = false;
-	spin_unlock_irq(&out->composer_lock);
+	spin_unlock_irq(&vkms_crtc->composer_lock);
 
 	/*
 	 * We raced with the vblank hrtimer and previous work already computed
@@ -262,10 +262,10 @@ void vkms_composer_worker(struct work_struct *work)
 		return;
 
 	if (wb_pending) {
-		drm_writeback_signal_completion(&out->wb_connector, 0);
-		spin_lock_irq(&out->composer_lock);
+		drm_writeback_signal_completion(&vkms_crtc->wb_connector, 0);
+		spin_lock_irq(&vkms_crtc->composer_lock);
 		crtc_state->wb_pending = false;
-		spin_unlock_irq(&out->composer_lock);
+		spin_unlock_irq(&vkms_crtc->composer_lock);
 	}
 
 	/*
@@ -315,25 +315,25 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *src_name,
 	return 0;
 }
 
-void vkms_set_composer(struct vkms_output *out, bool enabled)
+void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled)
 {
 	bool old_enabled;
 
 	if (enabled)
-		drm_crtc_vblank_get(&out->crtc);
+		drm_crtc_vblank_get(&vkms_crtc->base);
 
-	spin_lock_irq(&out->lock);
-	old_enabled = out->composer_enabled;
-	out->composer_enabled = enabled;
-	spin_unlock_irq(&out->lock);
+	spin_lock_irq(&vkms_crtc->lock);
+	old_enabled = vkms_crtc->composer_enabled;
+	vkms_crtc->composer_enabled = enabled;
+	spin_unlock_irq(&vkms_crtc->lock);
 
 	if (old_enabled)
-		drm_crtc_vblank_put(&out->crtc);
+		drm_crtc_vblank_put(&vkms_crtc->base);
 }
 
 int vkms_set_crc_source(struct drm_crtc *crtc, const char *src_name)
 {
-	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *out = drm_crtc_to_vkms_crtc(crtc);
 	bool enabled = false;
 	int ret = 0;
 
diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
index 515f6772b866..d91e49c53adc 100644
--- a/drivers/gpu/drm/vkms/vkms_crtc.c
+++ b/drivers/gpu/drm/vkms/vkms_crtc.c
@@ -11,35 +11,34 @@
 
 static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
 {
-	struct vkms_output *output = container_of(timer, struct vkms_output,
-						  vblank_hrtimer);
-	struct drm_crtc *crtc = &output->crtc;
+	struct vkms_crtc *vkms_crtc = timer_to_vkms_crtc(timer);
+	struct drm_crtc *crtc = &vkms_crtc->base;
 	struct vkms_crtc_state *state;
 	u64 ret_overrun;
 	bool ret, fence_cookie;
 
 	fence_cookie = dma_fence_begin_signalling();
 
-	ret_overrun = hrtimer_forward_now(&output->vblank_hrtimer,
-					  output->period_ns);
+	ret_overrun = hrtimer_forward_now(&vkms_crtc->vblank_hrtimer,
+					  vkms_crtc->period_ns);
 	if (ret_overrun != 1)
 		pr_warn("%s: vblank timer overrun\n", __func__);
 
-	spin_lock(&output->lock);
+	spin_lock(&vkms_crtc->lock);
 	ret = drm_crtc_handle_vblank(crtc);
 	if (!ret)
 		DRM_ERROR("vkms failure on handling vblank");
 
-	state = output->composer_state;
-	spin_unlock(&output->lock);
+	state = vkms_crtc->composer_state;
+	spin_unlock(&vkms_crtc->lock);
 
-	if (state && output->composer_enabled) {
+	if (state && vkms_crtc->composer_enabled) {
 		u64 frame = drm_crtc_accurate_vblank_count(crtc);
 
 		/* update frame_start only if a queued vkms_composer_worker()
 		 * has read the data
 		 */
-		spin_lock(&output->composer_lock);
+		spin_lock(&vkms_crtc->composer_lock);
 		if (!state->crc_pending)
 			state->frame_start = frame;
 		else
@@ -47,9 +46,10 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
 					 state->frame_start, frame);
 		state->frame_end = frame;
 		state->crc_pending = true;
-		spin_unlock(&output->composer_lock);
+		spin_unlock(&vkms_crtc->composer_lock);
 
-		ret = queue_work(output->composer_workq, &state->composer_work);
+		ret = queue_work(vkms_crtc->composer_workq,
+				 &state->composer_work);
 		if (!ret)
 			DRM_DEBUG_DRIVER("Composer worker already queued\n");
 	}
@@ -62,25 +62,27 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
 static int vkms_enable_vblank(struct drm_crtc *crtc)
 {
 	struct drm_device *dev = crtc->dev;
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 	unsigned int pipe = drm_crtc_index(crtc);
 	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
-	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
 
 	drm_calc_timestamping_constants(crtc, &crtc->mode);
 
-	hrtimer_init(&out->vblank_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
-	out->vblank_hrtimer.function = &vkms_vblank_simulate;
-	out->period_ns = ktime_set(0, vblank->framedur_ns);
-	hrtimer_start(&out->vblank_hrtimer, out->period_ns, HRTIMER_MODE_REL);
+	hrtimer_init(&vkms_crtc->vblank_hrtimer, CLOCK_MONOTONIC,
+		     HRTIMER_MODE_REL);
+	vkms_crtc->vblank_hrtimer.function = &vkms_vblank_simulate;
+	vkms_crtc->period_ns = ktime_set(0, vblank->framedur_ns);
+	hrtimer_start(&vkms_crtc->vblank_hrtimer, vkms_crtc->period_ns,
+		      HRTIMER_MODE_REL);
 
 	return 0;
 }
 
 static void vkms_disable_vblank(struct drm_crtc *crtc)
 {
-	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 
-	hrtimer_cancel(&out->vblank_hrtimer);
+	hrtimer_cancel(&vkms_crtc->vblank_hrtimer);
 }
 
 static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
@@ -88,9 +90,8 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
 				      bool in_vblank_irq)
 {
 	struct drm_device *dev = crtc->dev;
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 	unsigned int pipe = crtc->index;
-	struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
-	struct vkms_output *output = &vkmsdev->output;
 	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
 
 	if (!READ_ONCE(vblank->enabled)) {
@@ -98,7 +99,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
 		return true;
 	}
 
-	*vblank_time = READ_ONCE(output->vblank_hrtimer.node.expires);
+	*vblank_time = READ_ONCE(vkms_crtc->vblank_hrtimer.node.expires);
 
 	if (WARN_ON(*vblank_time == vblank->time))
 		return true;
@@ -110,7 +111,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
 	 * the vblank core expects. Therefore we need to always correct the
 	 * timestampe by one frame.
 	 */
-	*vblank_time -= output->period_ns;
+	*vblank_time -= vkms_crtc->period_ns;
 
 	return true;
 }
@@ -236,18 +237,18 @@ static void vkms_crtc_atomic_disable(struct drm_crtc *crtc,
 static void vkms_crtc_atomic_begin(struct drm_crtc *crtc,
 				   struct drm_atomic_state *state)
 {
-	struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 
 	/* This lock is held across the atomic commit to block vblank timer
 	 * from scheduling vkms_composer_worker until the composer is updated
 	 */
-	spin_lock_irq(&vkms_output->lock);
+	spin_lock_irq(&vkms_crtc->lock);
 }
 
 static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
 				   struct drm_atomic_state *state)
 {
-	struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 
 	if (crtc->state->event) {
 		spin_lock(&crtc->dev->event_lock);
@@ -262,9 +263,9 @@ static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
 		crtc->state->event = NULL;
 	}
 
-	vkms_output->composer_state = to_vkms_crtc_state(crtc->state);
+	vkms_crtc->composer_state = to_vkms_crtc_state(crtc->state);
 
-	spin_unlock_irq(&vkms_output->lock);
+	spin_unlock_irq(&vkms_crtc->lock);
 }
 
 static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
@@ -275,27 +276,41 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
 	.atomic_disable	= vkms_crtc_atomic_disable,
 };
 
-int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
-		   struct drm_plane *primary, struct drm_plane *cursor)
+struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
+				 struct drm_plane *primary,
+				 struct drm_plane *cursor)
 {
-	struct vkms_output *vkms_out = drm_crtc_to_vkms_output(crtc);
+	struct drm_device *dev = &vkmsdev->drm;
+	struct vkms_crtc *vkms_crtc;
 	int ret;
 
-	ret = drmm_crtc_init_with_planes(dev, crtc, primary, cursor,
+	if (vkmsdev->output.num_crtcs >= VKMS_MAX_OUTPUT_OBJECTS) {
+		return ERR_PTR(-ENOMEM);
+	}
+	vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
+
+	ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, primary, cursor,
 					 &vkms_crtc_funcs, NULL);
 	if (ret) {
 		DRM_ERROR("Failed to init CRTC\n");
-		return ret;
+		goto out_error;
 	}
 
-	drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs);
+	drm_crtc_helper_add(&vkms_crtc->base, &vkms_crtc_helper_funcs);
 
-	spin_lock_init(&vkms_out->lock);
-	spin_lock_init(&vkms_out->composer_lock);
+	spin_lock_init(&vkms_crtc->lock);
+	spin_lock_init(&vkms_crtc->composer_lock);
 
-	vkms_out->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
-	if (!vkms_out->composer_workq)
-		return -ENOMEM;
+	vkms_crtc->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
+	if (!vkms_crtc->composer_workq) {
+		ret = -ENOMEM;
+		goto out_error;
+	}
+
+	return vkms_crtc;
 
-	return ret;
+out_error:
+	memset(vkms_crtc, 0, sizeof(*vkms_crtc));
+	vkmsdev->output.num_crtcs -= 1;
+	return ERR_PTR(ret);
 }
diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
index f07454fff046..aa2bee82b757 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -57,8 +57,8 @@ static void vkms_release(struct drm_device *dev)
 {
 	struct vkms_device *vkms = drm_device_to_vkms_device(dev);
 
-	if (vkms->output.composer_workq)
-		destroy_workqueue(vkms->output.composer_workq);
+	for (int i = 0; i < vkms->output.num_crtcs; i++)
+		destroy_workqueue(vkms->output.crtcs[i].composer_workq);
 }
 
 static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
@@ -185,15 +185,15 @@ static int vkms_platform_probe(struct platform_device *pdev)
 		goto out_release_group;
 	}
 
-	ret = drm_vblank_init(&vkms_device->drm, 1);
+	ret = vkms_modeset_init(vkms_device);
 	if (ret) {
-		DRM_ERROR("Failed to vblank\n");
+		DRM_ERROR("Unable to initialize modesetting\n");
 		goto out_release_group;
 	}
 
-	ret = vkms_modeset_init(vkms_device);
+	ret = drm_vblank_init(&vkms_device->drm, vkms_device->output.num_crtcs);
 	if (ret) {
-		DRM_ERROR("Unable to initialize modesetting\n");
+		DRM_ERROR("Failed to vblank\n");
 		goto out_release_group;
 	}
 
@@ -202,7 +202,7 @@ static int vkms_platform_probe(struct platform_device *pdev)
 
 	ret = drm_dev_register(&vkms_device->drm, 0);
 	if (ret) {
-		DRM_ERROR("Unable to register device\n");
+		DRM_ERROR("Unable to register device with id %d\n", pdev->id);
 		return ret;
 	}
 
diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
index e87c8aea6fb3..4c3274838ec7 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.h
+++ b/drivers/gpu/drm/vkms/vkms_drv.h
@@ -23,6 +23,9 @@
 
 #define NUM_OVERLAY_PLANES 8
 
+#define VKMS_MAX_OUTPUT_OBJECTS 16
+#define VKMS_MAX_PLANES (3 * VKMS_MAX_OUTPUT_OBJECTS)
+
 struct vkms_frame_info {
 	struct drm_framebuffer *fb;
 	struct drm_rect src, dst;
@@ -65,6 +68,25 @@ struct vkms_plane {
 	struct drm_plane base;
 };
 
+struct vkms_crtc {
+	struct drm_crtc base;
+
+	struct drm_writeback_connector wb_connector;
+	struct hrtimer vblank_hrtimer;
+	ktime_t period_ns;
+	struct drm_pending_vblank_event *event;
+	/* ordered wq for composer_work */
+	struct workqueue_struct *composer_workq;
+	/* protects concurrent access to composer */
+	spinlock_t lock;
+
+	/* protected by @lock */
+	bool composer_enabled;
+	struct vkms_crtc_state *composer_state;
+
+	spinlock_t composer_lock;
+};
+
 /**
  * vkms_crtc_state - Driver specific CRTC state
  * @base: base CRTC state
@@ -89,23 +111,14 @@ struct vkms_crtc_state {
 };
 
 struct vkms_output {
-	struct drm_crtc crtc;
-	struct drm_encoder encoder;
-	struct drm_connector connector;
-	struct drm_writeback_connector wb_connector;
-	struct hrtimer vblank_hrtimer;
-	ktime_t period_ns;
-	struct drm_pending_vblank_event *event;
-	/* ordered wq for composer_work */
-	struct workqueue_struct *composer_workq;
-	/* protects concurrent access to composer */
-	spinlock_t lock;
-
-	/* protected by @lock */
-	bool composer_enabled;
-	struct vkms_crtc_state *composer_state;
-
-	spinlock_t composer_lock;
+	int num_crtcs;
+	struct vkms_crtc crtcs[VKMS_MAX_OUTPUT_OBJECTS];
+	int num_encoders;
+	struct drm_encoder encoders[VKMS_MAX_OUTPUT_OBJECTS];
+	int num_connectors;
+	struct drm_connector connectors[VKMS_MAX_OUTPUT_OBJECTS];
+	int num_planes;
+	struct vkms_plane planes[VKMS_MAX_PLANES];
 };
 
 struct vkms_device;
@@ -123,12 +136,14 @@ struct vkms_device {
 	struct vkms_config config;
 };
 
-#define drm_crtc_to_vkms_output(target) \
-	container_of(target, struct vkms_output, crtc)
+#define drm_crtc_to_vkms_crtc(crtc) container_of(crtc, struct vkms_crtc, base)
 
 #define drm_device_to_vkms_device(target) \
 	container_of(target, struct vkms_device, drm)
 
+#define timer_to_vkms_crtc(timer) \
+	container_of(timer, struct vkms_crtc, vblank_hrtimer)
+
 #define to_vkms_crtc_state(target)\
 	container_of(target, struct vkms_crtc_state, base)
 
@@ -136,13 +151,14 @@ struct vkms_device {
 	container_of(target, struct vkms_plane_state, base.base)
 
 /* CRTC */
-int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
-		   struct drm_plane *primary, struct drm_plane *cursor);
+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 index);
 
 struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
-				   enum drm_plane_type type, int index);
+				   enum drm_plane_type type);
 
 /* CRC Support */
 const char *const *vkms_get_crc_sources(struct drm_crtc *crtc,
@@ -153,10 +169,11 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
 
 /* Composer Support */
 void vkms_composer_worker(struct work_struct *work);
-void vkms_set_composer(struct vkms_output *out, bool enabled);
+void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled);
 void vkms_compose_row(struct line_buffer *stage_buffer, struct vkms_plane_state *plane, int y);
 
 /* Writeback */
-int vkms_enable_writeback_connector(struct vkms_device *vkmsdev);
+int vkms_enable_writeback_connector(struct vkms_device *vkmsdev,
+				    struct vkms_crtc *vkms_crtc);
 
 #endif /* _VKMS_DRV_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 963a64cf068b..6b4a77780d3c 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -1,9 +1,13 @@
 // SPDX-License-Identifier: GPL-2.0+
 
-#include "vkms_drv.h"
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "vkms_drv.h"
 
 static const struct drm_connector_funcs vkms_connector_funcs = {
 	.fill_modes = drm_helper_probe_single_connector_modes,
@@ -28,74 +32,116 @@ static int vkms_conn_get_modes(struct drm_connector *connector)
 }
 
 static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
-	.get_modes    = vkms_conn_get_modes,
+	.get_modes = vkms_conn_get_modes,
 };
 
-static int vkms_add_overlay_plane(struct vkms_device *vkmsdev, int index,
-				  struct drm_crtc *crtc)
+static struct drm_connector *
+vkms_connector_init(struct vkms_device *vkms_device)
 {
-	struct vkms_plane *overlay;
+	struct drm_connector *connector;
+	int ret;
 
-	overlay = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_OVERLAY, index);
-	if (IS_ERR(overlay))
-		return PTR_ERR(overlay);
+	if (vkms_device->output.num_connectors >= VKMS_MAX_OUTPUT_OBJECTS)
+		return ERR_PTR(-ENOMEM);
 
-	if (!overlay->base.possible_crtcs)
-		overlay->base.possible_crtcs = drm_crtc_mask(crtc);
+	connector = &vkms_device->output
+			     .connectors[vkms_device->output.num_connectors++];
+	ret = drm_connector_init(&vkms_device->drm, connector,
+				 &vkms_connector_funcs,
+				 DRM_MODE_CONNECTOR_VIRTUAL);
+	if (ret) {
+		memset(connector, 0, sizeof(*connector));
+		vkms_device->output.num_connectors -= 1;
+		return ERR_PTR(ret);
+	}
 
-	return 0;
+	drm_connector_helper_add(connector, &vkms_conn_helper_funcs);
+
+	return connector;
+}
+
+static struct drm_encoder *vkms_encoder_init(struct vkms_device *vkms_device)
+{
+	struct drm_encoder *encoder;
+	int ret;
+
+	if (vkms_device->output.num_encoders >= VKMS_MAX_OUTPUT_OBJECTS)
+		return ERR_PTR(-ENOMEM);
+
+	encoder = &vkms_device->output
+			   .encoders[vkms_device->output.num_encoders++];
+	ret = drm_encoder_init(&vkms_device->drm, encoder, &vkms_encoder_funcs,
+			       DRM_MODE_ENCODER_VIRTUAL, NULL);
+	if (ret) {
+		memset(encoder, 0, sizeof(*encoder));
+		vkms_device->output.num_encoders -= 1;
+		return ERR_PTR(ret);
+	}
+	return encoder;
 }
 
 int vkms_output_init(struct vkms_device *vkmsdev, int index)
 {
 	struct vkms_output *output = &vkmsdev->output;
 	struct drm_device *dev = &vkmsdev->drm;
-	struct drm_connector *connector = &output->connector;
-	struct drm_encoder *encoder = &output->encoder;
-	struct drm_crtc *crtc = &output->crtc;
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+	struct vkms_crtc *vkms_crtc;
 	struct vkms_plane *primary, *cursor = NULL;
 	int ret;
 	int writeback;
 	unsigned int n;
 
-	primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY, index);
+	primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY);
 	if (IS_ERR(primary))
 		return PTR_ERR(primary);
 
 	if (vkmsdev->config.overlay) {
 		for (n = 0; n < NUM_OVERLAY_PLANES; n++) {
-			ret = vkms_add_overlay_plane(vkmsdev, index, crtc);
-			if (ret)
-				return ret;
+			struct vkms_plane *overlay = vkms_plane_init(
+				vkmsdev, DRM_PLANE_TYPE_OVERLAY);
+			if (IS_ERR(overlay)) {
+				ret = PTR_ERR(overlay);
+				goto err_planes;
+			}
 		}
 	}
 
 	if (vkmsdev->config.cursor) {
-		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR, index);
-		if (IS_ERR(cursor))
-			return PTR_ERR(cursor);
+		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR);
+		if (IS_ERR(cursor)) {
+			ret = PTR_ERR(cursor);
+			goto err_planes;
+		}
 	}
 
-	ret = vkms_crtc_init(dev, crtc, &primary->base, &cursor->base);
-	if (ret)
-		return ret;
+	vkms_crtc = vkms_crtc_init(vkmsdev, &primary->base,
+				   cursor ? &cursor->base : NULL);
+	if (IS_ERR(vkms_crtc)) {
+		DRM_ERROR("Failed to init crtc\n");
+		ret = PTR_ERR(vkms_crtc);
+		goto err_planes;
+	}
 
-	ret = drm_connector_init(dev, connector, &vkms_connector_funcs,
-				 DRM_MODE_CONNECTOR_VIRTUAL);
-	if (ret) {
+	for (int i = 0; i < vkmsdev->output.num_planes; i++) {
+		vkmsdev->output.planes[i].base.possible_crtcs |=
+			drm_crtc_mask(&vkms_crtc->base);
+	}
+
+	connector = vkms_connector_init(vkmsdev);
+	if (IS_ERR(connector)) {
 		DRM_ERROR("Failed to init connector\n");
+		ret = PTR_ERR(connector);
 		goto err_connector;
 	}
 
-	drm_connector_helper_add(connector, &vkms_conn_helper_funcs);
-
-	ret = drm_encoder_init(dev, encoder, &vkms_encoder_funcs,
-			       DRM_MODE_ENCODER_VIRTUAL, NULL);
-	if (ret) {
+	encoder = vkms_encoder_init(vkmsdev);
+	if (IS_ERR(encoder)) {
 		DRM_ERROR("Failed to init encoder\n");
+		ret = PTR_ERR(encoder);
 		goto err_encoder;
 	}
-	encoder->possible_crtcs = 1;
+	encoder->possible_crtcs |= drm_crtc_mask(&vkms_crtc->base);
 
 	ret = drm_connector_attach_encoder(connector, encoder);
 	if (ret) {
@@ -104,7 +150,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
 	}
 
 	if (vkmsdev->config.writeback) {
-		writeback = vkms_enable_writeback_connector(vkmsdev);
+		writeback = vkms_enable_writeback_connector(vkmsdev, vkms_crtc);
 		if (writeback)
 			DRM_ERROR("Failed to init writeback connector\n");
 	}
@@ -120,7 +166,14 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
 	drm_connector_cleanup(connector);
 
 err_connector:
-	drm_crtc_cleanup(crtc);
+	drm_crtc_cleanup(&vkms_crtc->base);
+
+err_planes:
+	for (int i = 0; i < output->num_planes; i++) {
+		drm_plane_cleanup(&output->planes[i].base);
+	}
+
+	memset(output, 0, sizeof(*output));
 
 	return ret;
 }
diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c
index e5c625ab8e3e..f69621822c1b 100644
--- a/drivers/gpu/drm/vkms/vkms_plane.c
+++ b/drivers/gpu/drm/vkms/vkms_plane.c
@@ -8,6 +8,8 @@
 #include <drm/drm_fourcc.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_plane_helper.h>
 
 #include "vkms_drv.h"
 #include "vkms_formats.h"
@@ -65,6 +67,20 @@ static void vkms_plane_destroy_state(struct drm_plane *plane,
 	kfree(vkms_state);
 }
 
+static void vkms_plane_destroy(struct drm_plane *plane)
+{
+	struct vkms_plane *vkms_plane =
+		container_of(plane, struct vkms_plane, base);
+
+	if (plane->state) {
+		vkms_plane_destroy_state(plane, plane->state);
+		plane->state = NULL;
+	}
+
+	drm_plane_cleanup(plane);
+	memset(vkms_plane, 0, sizeof(struct vkms_plane));
+}
+
 static void vkms_plane_reset(struct drm_plane *plane)
 {
 	struct vkms_plane_state *vkms_state;
@@ -84,11 +100,12 @@ static void vkms_plane_reset(struct drm_plane *plane)
 }
 
 static const struct drm_plane_funcs vkms_plane_funcs = {
-	.update_plane		= drm_atomic_helper_update_plane,
-	.disable_plane		= drm_atomic_helper_disable_plane,
-	.reset			= vkms_plane_reset,
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = vkms_plane_destroy,
+	.reset = vkms_plane_reset,
 	.atomic_duplicate_state = vkms_plane_duplicate_state,
-	.atomic_destroy_state	= vkms_plane_destroy_state,
+	.atomic_destroy_state = vkms_plane_destroy_state,
 };
 
 static void vkms_plane_atomic_update(struct drm_plane *plane,
@@ -198,17 +215,22 @@ static const struct drm_plane_helper_funcs vkms_plane_helper_funcs = {
 };
 
 struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
-				   enum drm_plane_type type, int index)
+				   enum drm_plane_type type)
 {
 	struct drm_device *dev = &vkmsdev->drm;
+	struct vkms_output *output = &vkmsdev->output;
 	struct vkms_plane *plane;
+	int ret;
 
-	plane = drmm_universal_plane_alloc(dev, struct vkms_plane, base, 1 << index,
-					   &vkms_plane_funcs,
-					   vkms_formats, ARRAY_SIZE(vkms_formats),
-					   NULL, type, NULL);
-	if (IS_ERR(plane))
-		return plane;
+	if (output->num_planes >= VKMS_MAX_PLANES)
+		return ERR_PTR(-ENOMEM);
+
+	plane = &output->planes[output->num_planes++];
+	ret = drm_universal_plane_init(dev, &plane->base, 0, &vkms_plane_funcs,
+				       vkms_formats, ARRAY_SIZE(vkms_formats),
+				       NULL, type, NULL);
+	if (ret)
+		return ERR_PTR(ret);
 
 	drm_plane_helper_add(&plane->base, &vkms_plane_helper_funcs);
 
diff --git a/drivers/gpu/drm/vkms/vkms_writeback.c b/drivers/gpu/drm/vkms/vkms_writeback.c
index 84a51cd281b9..b90bcbd0a0e0 100644
--- a/drivers/gpu/drm/vkms/vkms_writeback.c
+++ b/drivers/gpu/drm/vkms/vkms_writeback.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0+
 
 #include <linux/iosys-map.h>
+#include <linux/kernel.h>
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_edid.h>
@@ -101,7 +102,8 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
 				struct drm_writeback_job *job)
 {
 	struct vkms_writeback_job *vkmsjob = job->priv;
-	struct vkms_device *vkmsdev;
+	struct vkms_crtc *vkms_crtc =
+		container_of(connector, struct vkms_crtc, wb_connector);
 
 	if (!job->fb)
 		return;
@@ -110,8 +112,7 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
 
 	drm_framebuffer_put(vkmsjob->wb_frame_info.fb);
 
-	vkmsdev = drm_device_to_vkms_device(job->fb->dev);
-	vkms_set_composer(&vkmsdev->output, false);
+	vkms_set_composer(vkms_crtc, false);
 	kfree(vkmsjob);
 }
 
@@ -120,11 +121,11 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
 {
 	struct drm_connector_state *connector_state = drm_atomic_get_new_connector_state(state,
 											 conn);
-	struct vkms_device *vkmsdev = drm_device_to_vkms_device(conn->dev);
-	struct vkms_output *output = &vkmsdev->output;
-	struct drm_writeback_connector *wb_conn = &output->wb_connector;
+	struct vkms_crtc *vkms_crtc =
+		drm_crtc_to_vkms_crtc(connector_state->crtc);
+	struct drm_writeback_connector *wb_conn = &vkms_crtc->wb_connector;
 	struct drm_connector_state *conn_state = wb_conn->base.state;
-	struct vkms_crtc_state *crtc_state = output->composer_state;
+	struct vkms_crtc_state *crtc_state = vkms_crtc->composer_state;
 	struct drm_framebuffer *fb = connector_state->writeback_job->fb;
 	u16 crtc_height = crtc_state->base.crtc->mode.vdisplay;
 	u16 crtc_width = crtc_state->base.crtc->mode.hdisplay;
@@ -135,18 +136,18 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
 	if (!conn_state)
 		return;
 
-	vkms_set_composer(&vkmsdev->output, true);
+	vkms_set_composer(vkms_crtc, true);
 
 	active_wb = conn_state->writeback_job->priv;
 	wb_frame_info = &active_wb->wb_frame_info;
 
-	spin_lock_irq(&output->composer_lock);
+	spin_lock_irq(&vkms_crtc->composer_lock);
 	crtc_state->active_writeback = active_wb;
 	wb_frame_info->offset = fb->offsets[0];
 	wb_frame_info->pitch = fb->pitches[0];
 	wb_frame_info->cpp = fb->format->cpp[0];
 	crtc_state->wb_pending = true;
-	spin_unlock_irq(&output->composer_lock);
+	spin_unlock_irq(&vkms_crtc->composer_lock);
 	drm_writeback_queue_job(wb_conn, connector_state);
 	active_wb->wb_write = get_line_to_frame_function(wb_format);
 	drm_rect_init(&wb_frame_info->src, 0, 0, crtc_width, crtc_height);
@@ -160,9 +161,10 @@ static const struct drm_connector_helper_funcs vkms_wb_conn_helper_funcs = {
 	.atomic_commit = vkms_wb_atomic_commit,
 };
 
-int vkms_enable_writeback_connector(struct vkms_device *vkmsdev)
+int vkms_enable_writeback_connector(struct vkms_device *vkmsdev,
+				    struct vkms_crtc *vkms_crtc)
 {
-	struct drm_writeback_connector *wb = &vkmsdev->output.wb_connector;
+	struct drm_writeback_connector *wb = &vkms_crtc->wb_connector;
 
 	drm_connector_helper_add(&wb->base, &vkms_wb_conn_helper_funcs);
 
-- 
2.41.0.162.gfafddb0af9-goog


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 3/6] drm/vkms: Provide platform data when creating VKMS devices
  2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
  2023-06-23 22:23 ` [PATCH v2 1/6] drm/vkms: Back VKMS with DRM memory management instead of static objects Jim Shargo
  2023-06-23 22:23 ` [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device Jim Shargo
@ 2023-06-23 22:23 ` Jim Shargo
  2023-06-23 22:23 ` [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS Jim Shargo
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 21+ messages in thread
From: Jim Shargo @ 2023-06-23 22:23 UTC (permalink / raw)
  To: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: Jim Shargo, dri-devel, linux-doc, linux-kernel

This is a small refactor to make ConfigFS support easier. This should be
a no-op refactor.

Signed-off-by: Jim Shargo <jshargo@chromium.org>
---
 drivers/gpu/drm/vkms/vkms_drv.c    | 13 +++++++++++--
 drivers/gpu/drm/vkms/vkms_drv.h    |  9 ++++++---
 drivers/gpu/drm/vkms/vkms_output.c |  2 +-
 3 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
index aa2bee82b757..d43121addf66 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -152,12 +152,14 @@ 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 vkms_output_init(vkmsdev, 0);
+	return vkmsdev->is_default ? vkms_output_init_default(vkmsdev) :
+				     -ENOSYS;
 }
 
 static int vkms_platform_probe(struct platform_device *pdev)
 {
 	int ret;
+	struct vkms_device_setup *vkms_device_setup = pdev->dev.platform_data;
 	struct vkms_device *vkms_device;
 	void *grp;
 
@@ -177,6 +179,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;
 
 	ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev,
 					   DMA_BIT_MASK(64));
@@ -240,6 +243,9 @@ static int __init vkms_init(void)
 {
 	int ret;
 	struct platform_device *pdev;
+	struct vkms_device_setup vkms_device_setup = {
+		.is_default = true,
+	};
 
 	ret = platform_driver_register(&vkms_platform_driver);
 	if (ret) {
@@ -247,8 +253,11 @@ static int __init vkms_init(void)
 		return ret;
 	}
 
-	pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
+	pdev = platform_device_register_data(NULL, DRIVER_NAME, 0,
+					     &vkms_device_setup,
+					     sizeof(vkms_device_setup));
 	if (IS_ERR(pdev)) {
+		DRM_ERROR("Unable to register default vkms device\n");
 		platform_driver_unregister(&vkms_platform_driver);
 		return PTR_ERR(pdev);
 	}
diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
index 4c3274838ec7..84b35aa1dc5a 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.h
+++ b/drivers/gpu/drm/vkms/vkms_drv.h
@@ -121,17 +121,20 @@ struct vkms_output {
 	struct vkms_plane planes[VKMS_MAX_PLANES];
 };
 
-struct vkms_device;
-
 struct vkms_config {
 	bool writeback;
 	bool cursor;
 	bool overlay;
 };
 
+struct vkms_device_setup {
+	bool is_default;
+};
+
 struct vkms_device {
 	struct drm_device drm;
 	struct platform_device *platform;
+	bool is_default;
 	struct vkms_output output;
 	struct vkms_config config;
 };
@@ -155,7 +158,7 @@ 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 index);
+int vkms_output_init_default(struct vkms_device *vkmsdev);
 
 struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
 				   enum drm_plane_type type);
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 6b4a77780d3c..3b5e272a8b33 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -80,7 +80,7 @@ static struct drm_encoder *vkms_encoder_init(struct vkms_device *vkms_device)
 	return encoder;
 }
 
-int vkms_output_init(struct vkms_device *vkmsdev, int index)
+int vkms_output_init_default(struct vkms_device *vkmsdev)
 {
 	struct vkms_output *output = &vkmsdev->output;
 	struct drm_device *dev = &vkmsdev->drm;
-- 
2.41.0.162.gfafddb0af9-goog


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS
  2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
                   ` (2 preceding siblings ...)
  2023-06-23 22:23 ` [PATCH v2 3/6] drm/vkms: Provide platform data when creating VKMS devices Jim Shargo
@ 2023-06-23 22:23 ` Jim Shargo
  2023-06-25 18:19   ` Maira Canal
  2023-06-28  2:00   ` kernel test robot
  2023-06-23 22:23 ` [PATCH v2 5/6] drm/vkms: Support enabling ConfigFS devices Jim Shargo
                   ` (3 subsequent siblings)
  7 siblings, 2 replies; 21+ messages in thread
From: Jim Shargo @ 2023-06-23 22:23 UTC (permalink / raw)
  To: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: Jim Shargo, dri-devel, linux-doc, linux-kernel

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 <jshargo@chromium.org>
---
 Documentation/gpu/vkms.rst           |  17 +-
 drivers/gpu/drm/Kconfig              |   1 +
 drivers/gpu/drm/vkms/Makefile        |   1 +
 drivers/gpu/drm/vkms/vkms_configfs.c | 646 +++++++++++++++++++++++++++
 drivers/gpu/drm/vkms/vkms_drv.c      |  52 ++-
 drivers/gpu/drm/vkms/vkms_drv.h      |  92 +++-
 drivers/gpu/drm/vkms/vkms_output.c   |   5 +
 7 files changed, 799 insertions(+), 15 deletions(-)
 create mode 100644 drivers/gpu/drm/vkms/vkms_configfs.c

diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst
index ba04ac7c2167..2c342ef0fb7b 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,15 @@ project.
 Runtime Configuration
 ---------------------
 
-We want to be able to reconfigure vkms instance without having to reload the
-module. Use/Test-cases:
+We want to 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/Kconfig b/drivers/gpu/drm/Kconfig
index afb3b2f5f425..71eb913b378f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -262,6 +262,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 1b28a6a32948..6b83907ad554 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 000000000000..544024735d19
--- /dev/null
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -0,0 +1,646 @@
+// 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 in addition to an
+ * immutable "default" device created by the driver at initialization time. Note
+ * that the default device 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
+ *       |-- default
+ *       |   `-- enabled
+ *       `-- 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/planes/cursor
+ *   echo $DRM_PLANE_TYPE_CURSOR > /config/vkms/test/planes/cursor/type
+ * 
+ *   mkdir /config/vkms/test/planes/overlay
+ *   echo $DRM_PLANE_TYPE_OVERLAY > /config/vkms/test/planes/overlay/type
+ * 
+ *   mkdir /config/vkms/test/crtcs/crtc
+ *   mkdir /config/vkms/test/crtcs/crtc_other
+ *   mkdir /config/vkms/test/encoders/encoder
+ *   mkdir /config/vkms/test/connectors/connector
+ *
+ * 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/crtcs/crtc /config/vkms/test/encoders/encoder/possible_crtcs/
+ *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/primary/possible_crtcs/
+ *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/cursor/possible_crtcs/
+ *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/overlay/possible_crtcs/
+ * 
+ *   ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/overlay/possible_crtcs/
+ *   ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/other_primary/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/connectors/connector
+ *
+ *   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 d43121addf66..1b5b7143792f 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -9,7 +9,10 @@
  * the GPU in DRM API tests.
  */
 
+#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>
@@ -152,8 +155,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) :
-				     -ENOSYS;
+	return vkmsdev->configfs ? vkms_output_init(vkmsdev) :
+				   vkms_output_init_default(vkmsdev);
 }
 
 static int vkms_platform_probe(struct platform_device *pdev)
@@ -165,6 +168,7 @@ static int vkms_platform_probe(struct platform_device *pdev)
 
 	grp = devres_open_group(&pdev->dev, NULL, GFP_KERNEL);
 	if (!grp) {
+		DRM_ERROR("Could not open devres group\n");
 		return -ENOMEM;
 	}
 
@@ -179,7 +183,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));
@@ -239,12 +243,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);
@@ -262,6 +297,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;
 }
 
@@ -269,6 +311,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 84b35aa1dc5a..3634eeeb4548 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
@@ -127,14 +129,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;
 };
@@ -153,11 +206,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,
@@ -179,4 +263,8 @@ void vkms_compose_row(struct line_buffer *stage_buffer, struct vkms_plane_state
 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 3b5e272a8b33..c9e6c653de19 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -177,3 +177,8 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
 
 	return ret;
 }
+
+int vkms_output_init(struct vkms_device *vkmsdev)
+{
+	return -ENOTSUPP;
+}
-- 
2.41.0.162.gfafddb0af9-goog


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 5/6] drm/vkms: Support enabling ConfigFS devices
  2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
                   ` (3 preceding siblings ...)
  2023-06-23 22:23 ` [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS Jim Shargo
@ 2023-06-23 22:23 ` Jim Shargo
  2023-08-15 14:01   ` Marius Vlad
  2023-06-23 22:23 ` [PATCH v2 6/6] drm/vkms: Add a module param to enable/disable the default device Jim Shargo
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 21+ messages in thread
From: Jim Shargo @ 2023-06-23 22:23 UTC (permalink / raw)
  To: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: Jim Shargo, dri-devel, linux-doc, linux-kernel

VKMS now supports creating and using virtual devices!

In addition to the enabling logic, this commit also prevents users from
adding new objects once a card is registered.

Signed-off-by: Jim Shargo <jshargo@chromium.org>
---
 drivers/gpu/drm/vkms/vkms_configfs.c |  37 +++--
 drivers/gpu/drm/vkms/vkms_crtc.c     |   4 +-
 drivers/gpu/drm/vkms/vkms_drv.c      |   3 +-
 drivers/gpu/drm/vkms/vkms_drv.h      |   2 +-
 drivers/gpu/drm/vkms/vkms_output.c   | 201 +++++++++++++++++++++++----
 5 files changed, 202 insertions(+), 45 deletions(-)

diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index 544024735d19..f5eed6d23dcd 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -504,29 +504,40 @@ static ssize_t device_enabled_store(struct config_item *item, const char *buf,
 {
 	struct vkms_configfs *configfs = item_to_configfs(item);
 	struct vkms_device *device;
-	int value, ret;
+	int enabled, ret;
 
-	ret = kstrtoint(buf, 0, &value);
+	ret = kstrtoint(buf, 0, &enabled);
 	if (ret)
 		return ret;
 
-	if (value != 1)
-		return -EINVAL;
-
-	mutex_lock(&configfs->lock);
-
-	if (configfs->vkms_device) {
+	if (enabled == 0) {
+		mutex_lock(&configfs->lock);
+		if (configfs->vkms_device) {
+			vkms_remove_device(configfs->vkms_device);
+			configfs->vkms_device = NULL;
+		}
 		mutex_unlock(&configfs->lock);
+
 		return len;
 	}
 
-	device = vkms_add_device(configfs);
-	mutex_unlock(&configfs->lock);
+	if (enabled == 1) {
+		mutex_lock(&configfs->lock);
+		if (!configfs->vkms_device) {
+			device = vkms_add_device(configfs);
+			if (IS_ERR(device)) {
+				mutex_unlock(&configfs->lock);
+				return -PTR_ERR(device);
+			}
+
+			configfs->vkms_device = device;
+		}
+		mutex_unlock(&configfs->lock);
 
-	if (IS_ERR(device))
-		return -PTR_ERR(device);
+		return len;
+	}
 
-	return len;
+	return -EINVAL;
 }
 
 CONFIGFS_ATTR(device_, enabled);
diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
index d91e49c53adc..5ebb5264f6ef 100644
--- a/drivers/gpu/drm/vkms/vkms_crtc.c
+++ b/drivers/gpu/drm/vkms/vkms_crtc.c
@@ -278,7 +278,7 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
 
 struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
 				 struct drm_plane *primary,
-				 struct drm_plane *cursor)
+				 struct drm_plane *cursor, const char *name)
 {
 	struct drm_device *dev = &vkmsdev->drm;
 	struct vkms_crtc *vkms_crtc;
@@ -290,7 +290,7 @@ struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
 	vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
 
 	ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, primary, cursor,
-					 &vkms_crtc_funcs, NULL);
+					 &vkms_crtc_funcs, name);
 	if (ret) {
 		DRM_ERROR("Failed to init CRTC\n");
 		goto out_error;
diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
index 1b5b7143792f..314a04659c5f 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -210,7 +210,7 @@ static int vkms_platform_probe(struct platform_device *pdev)
 	ret = drm_dev_register(&vkms_device->drm, 0);
 	if (ret) {
 		DRM_ERROR("Unable to register device with id %d\n", pdev->id);
-		return ret;
+		goto out_release_group;
 	}
 
 	drm_fbdev_generic_setup(&vkms_device->drm, 0);
@@ -256,6 +256,7 @@ struct vkms_device *vkms_add_device(struct vkms_configfs *configfs)
 			dev, &vkms_platform_driver.driver))) {
 		pdev = to_platform_device(dev);
 		max_id = max(max_id, pdev->id);
+		put_device(dev);
 	}
 
 	pdev = platform_device_register_data(NULL, DRIVER_NAME, max_id + 1,
diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
index 3634eeeb4548..3d592d085f49 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.h
+++ b/drivers/gpu/drm/vkms/vkms_drv.h
@@ -239,7 +239,7 @@ 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);
+				 struct drm_plane *cursor, const char *name);
 
 int vkms_output_init(struct vkms_device *vkmsdev);
 int vkms_output_init_default(struct vkms_device *vkmsdev);
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index c9e6c653de19..806ff01954ad 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -2,8 +2,10 @@
 
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_encoder.h>
+#include <drm/drm_plane.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_simple_kms_helper.h>
 
@@ -82,7 +84,6 @@ static struct drm_encoder *vkms_encoder_init(struct vkms_device *vkms_device)
 
 int vkms_output_init_default(struct vkms_device *vkmsdev)
 {
-	struct vkms_output *output = &vkmsdev->output;
 	struct drm_device *dev = &vkmsdev->drm;
 	struct drm_connector *connector;
 	struct drm_encoder *encoder;
@@ -101,8 +102,7 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
 			struct vkms_plane *overlay = vkms_plane_init(
 				vkmsdev, DRM_PLANE_TYPE_OVERLAY);
 			if (IS_ERR(overlay)) {
-				ret = PTR_ERR(overlay);
-				goto err_planes;
+				return PTR_ERR(overlay);
 			}
 		}
 	}
@@ -110,17 +110,16 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
 	if (vkmsdev->config.cursor) {
 		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR);
 		if (IS_ERR(cursor)) {
-			ret = PTR_ERR(cursor);
-			goto err_planes;
+			return PTR_ERR(cursor);
 		}
 	}
 
 	vkms_crtc = vkms_crtc_init(vkmsdev, &primary->base,
-				   cursor ? &cursor->base : NULL);
+				   cursor ? &cursor->base : NULL,
+				   "crtc-default");
 	if (IS_ERR(vkms_crtc)) {
 		DRM_ERROR("Failed to init crtc\n");
-		ret = PTR_ERR(vkms_crtc);
-		goto err_planes;
+		return PTR_ERR(vkms_crtc);
 	}
 
 	for (int i = 0; i < vkmsdev->output.num_planes; i++) {
@@ -131,22 +130,20 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
 	connector = vkms_connector_init(vkmsdev);
 	if (IS_ERR(connector)) {
 		DRM_ERROR("Failed to init connector\n");
-		ret = PTR_ERR(connector);
-		goto err_connector;
+		return PTR_ERR(connector);
 	}
 
 	encoder = vkms_encoder_init(vkmsdev);
 	if (IS_ERR(encoder)) {
 		DRM_ERROR("Failed to init encoder\n");
-		ret = PTR_ERR(encoder);
-		goto err_encoder;
+		return PTR_ERR(encoder);
 	}
 	encoder->possible_crtcs |= drm_crtc_mask(&vkms_crtc->base);
 
 	ret = drm_connector_attach_encoder(connector, encoder);
 	if (ret) {
 		DRM_ERROR("Failed to attach connector to encoder\n");
-		goto err_attach;
+		return ret;
 	}
 
 	if (vkmsdev->config.writeback) {
@@ -158,27 +155,175 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
 	drm_mode_config_reset(dev);
 
 	return 0;
+}
 
-err_attach:
-	drm_encoder_cleanup(encoder);
+static bool is_object_linked(struct vkms_config_links *links, unsigned long idx)
+{
+	return links->linked_object_bitmap & (1 << idx);
+}
 
-err_encoder:
-	drm_connector_cleanup(connector);
+int vkms_output_init(struct vkms_device *vkmsdev)
+{
+	struct drm_device *dev = &vkmsdev->drm;
+	struct vkms_configfs *configfs = vkmsdev->configfs;
+	struct vkms_output *output = &vkmsdev->output;
+	struct plane_map {
+		struct vkms_config_plane *config_plane;
+		struct vkms_plane *plane;
+	} plane_map[VKMS_MAX_PLANES] = { 0 };
+	struct encoder_map {
+		struct vkms_config_encoder *config_encoder;
+		struct drm_encoder *encoder;
+	} encoder_map[VKMS_MAX_OUTPUT_OBJECTS] = { 0 };
+	struct config_item *item;
+	int map_idx = 0;
+
+	list_for_each_entry(item, &configfs->planes_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_plane *config_plane =
+			item_to_config_plane(item);
+		struct vkms_plane *plane =
+			vkms_plane_init(vkmsdev, config_plane->type);
+
+		if (IS_ERR(plane)) {
+			DRM_ERROR("Unable to init plane from config: %s",
+				  item->ci_name);
+			return PTR_ERR(plane);
+		}
 
-err_connector:
-	drm_crtc_cleanup(&vkms_crtc->base);
+		plane_map[map_idx].config_plane = config_plane;
+		plane_map[map_idx].plane = plane;
+		map_idx += 1;
+	}
 
-err_planes:
-	for (int i = 0; i < output->num_planes; i++) {
-		drm_plane_cleanup(&output->planes[i].base);
+	map_idx = 0;
+	list_for_each_entry(item, &configfs->encoders_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_encoder *config_encoder =
+			item_to_config_encoder(item);
+		struct drm_encoder *encoder = vkms_encoder_init(vkmsdev);
+
+		if (IS_ERR(encoder)) {
+			DRM_ERROR("Failed to init config encoder: %s",
+				  item->ci_name);
+			return PTR_ERR(encoder);
+		}
+		encoder_map[map_idx].config_encoder = config_encoder;
+		encoder_map[map_idx].encoder = encoder;
+		map_idx += 1;
 	}
 
-	memset(output, 0, sizeof(*output));
+	list_for_each_entry(item, &configfs->connectors_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_connector *config_connector =
+			item_to_config_connector(item);
+		struct drm_connector *connector = vkms_connector_init(vkmsdev);
 
-	return ret;
-}
+		if (IS_ERR(connector)) {
+			DRM_ERROR("Failed to init connector from config: %s",
+				  item->ci_name);
+			return PTR_ERR(connector);
+		}
 
-int vkms_output_init(struct vkms_device *vkmsdev)
-{
-	return -ENOTSUPP;
+		for (int j = 0; j < output->num_connectors; j++) {
+			struct encoder_map *encoder = &encoder_map[j];
+
+			if (is_object_linked(
+				    &config_connector->possible_encoders,
+				    encoder->config_encoder
+					    ->encoder_config_idx)) {
+				drm_connector_attach_encoder(connector,
+							     encoder->encoder);
+			}
+		}
+	}
+
+	list_for_each_entry(item, &configfs->crtcs_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_crtc *config_crtc =
+			item_to_config_crtc(item);
+		struct vkms_crtc *vkms_crtc;
+		struct drm_plane *primary = NULL, *cursor = NULL;
+
+		for (int j = 0; j < output->num_planes; j++) {
+			struct plane_map *plane_entry = &plane_map[j];
+			struct drm_plane *plane = &plane_entry->plane->base;
+
+			if (!is_object_linked(
+				    &plane_entry->config_plane->possible_crtcs,
+				    config_crtc->crtc_config_idx)) {
+				continue;
+			}
+
+			if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
+				if (primary) {
+					DRM_WARN(
+						"Too many primary planes found for crtc %s.",
+						item->ci_name);
+					return EINVAL;
+				}
+				primary = plane;
+			} else if (plane->type == DRM_PLANE_TYPE_CURSOR) {
+				if (cursor) {
+					DRM_WARN(
+						"Too many cursor planes found for crtc %s.",
+						item->ci_name);
+					return EINVAL;
+				}
+				cursor = plane;
+			}
+		}
+
+		if (!primary) {
+			DRM_WARN("No primary plane configured for crtc %s",
+				 item->ci_name);
+			return EINVAL;
+		}
+
+		vkms_crtc =
+			vkms_crtc_init(vkmsdev, primary, cursor, item->ci_name);
+		if (IS_ERR(vkms_crtc)) {
+			DRM_WARN("Unable to init crtc from config: %s",
+				 item->ci_name);
+			return PTR_ERR(vkms_crtc);
+		}
+
+		for (int j = 0; j < output->num_planes; j++) {
+			struct plane_map *plane_entry = &plane_map[j];
+
+			if (!plane_entry->plane)
+				break;
+
+			if (is_object_linked(
+				    &plane_entry->config_plane->possible_crtcs,
+				    config_crtc->crtc_config_idx)) {
+				plane_entry->plane->base.possible_crtcs |=
+					drm_crtc_mask(&vkms_crtc->base);
+			}
+		}
+
+		for (int j = 0; j < output->num_encoders; j++) {
+			struct encoder_map *encoder_entry = &encoder_map[j];
+
+			if (is_object_linked(&encoder_entry->config_encoder
+						      ->possible_crtcs,
+					     config_crtc->crtc_config_idx)) {
+				encoder_entry->encoder->possible_crtcs |=
+					drm_crtc_mask(&vkms_crtc->base);
+			}
+		}
+
+		if (vkmsdev->config.writeback) {
+			int ret = vkms_enable_writeback_connector(vkmsdev,
+								  vkms_crtc);
+			if (ret)
+				DRM_WARN(
+					"Failed to init writeback connector for config crtc: %s. Error code %d",
+					item->ci_name, ret);
+		}
+	}
+
+	drm_mode_config_reset(dev);
+
+	return 0;
 }
-- 
2.41.0.162.gfafddb0af9-goog


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 6/6] drm/vkms: Add a module param to enable/disable the default device
  2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
                   ` (4 preceding siblings ...)
  2023-06-23 22:23 ` [PATCH v2 5/6] drm/vkms: Support enabling ConfigFS devices Jim Shargo
@ 2023-06-23 22:23 ` Jim Shargo
  2023-06-25 18:04   ` Maira Canal
  2023-08-08  2:42 ` Brandon Pollack
  2023-08-08  2:42 ` [PATCH] Initial backport of vkms changes from 6.4, including jshargo and brpols configs changes Brandon Pollack
  7 siblings, 1 reply; 21+ messages in thread
From: Jim Shargo @ 2023-06-23 22:23 UTC (permalink / raw)
  To: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: Jim Shargo, dri-devel, linux-doc, linux-kernel

In many testing circumstances, we will want to just create a new device
and test against that. If we create a default device, it can be annoying
to have to manually select the new device instead of choosing the only
one that exists.

The param, enable_default, is defaulted to true to maintain backwards
compatibility.

Signed-off-by: Jim Shargo <jshargo@chromium.org>
---
 drivers/gpu/drm/vkms/vkms_drv.c | 44 ++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
index 314a04659c5f..1cb56c090a65 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -42,17 +42,26 @@
 #define DRIVER_MAJOR	1
 #define DRIVER_MINOR	0
 
+static bool enable_default_device = true;
+module_param_named(enable_default_device, enable_default_device, bool, 0444);
+MODULE_PARM_DESC(enable_default_device,
+		 "Enable/Disable creating the default device");
+
 static bool enable_cursor = true;
 module_param_named(enable_cursor, enable_cursor, bool, 0444);
-MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
+MODULE_PARM_DESC(enable_cursor,
+		 "Enable/Disable cursor support for the default device");
 
 static bool enable_writeback = true;
 module_param_named(enable_writeback, enable_writeback, bool, 0444);
-MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback connector support");
+MODULE_PARM_DESC(
+	enable_writeback,
+	"Enable/Disable writeback connector support for the default device");
 
 static bool enable_overlay;
 module_param_named(enable_overlay, enable_overlay, bool, 0444);
-MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
+MODULE_PARM_DESC(enable_overlay,
+		 "Enable/Disable overlay support for the default device");
 
 DEFINE_DRM_GEM_FOPS(vkms_driver_fops);
 
@@ -278,10 +287,7 @@ void vkms_remove_device(struct vkms_device *vkms_device)
 static int __init vkms_init(void)
 {
 	int ret;
-	struct platform_device *pdev;
-	struct vkms_device_setup vkms_device_setup = {
-		.configfs = NULL,
-	};
+	struct platform_device *default_pdev = NULL;
 
 	ret = platform_driver_register(&vkms_platform_driver);
 	if (ret) {
@@ -289,19 +295,27 @@ static int __init vkms_init(void)
 		return ret;
 	}
 
-	pdev = platform_device_register_data(NULL, DRIVER_NAME, 0,
-					     &vkms_device_setup,
-					     sizeof(vkms_device_setup));
-	if (IS_ERR(pdev)) {
-		DRM_ERROR("Unable to register default vkms device\n");
-		platform_driver_unregister(&vkms_platform_driver);
-		return PTR_ERR(pdev);
+	if (enable_default_device) {
+		struct vkms_device_setup vkms_device_setup = {
+			.configfs = NULL,
+		};
+
+		default_pdev = platform_device_register_data(
+			NULL, DRIVER_NAME, 0, &vkms_device_setup,
+			sizeof(vkms_device_setup));
+		if (IS_ERR(default_pdev)) {
+			DRM_ERROR("Unable to register default vkms device\n");
+			platform_driver_unregister(&vkms_platform_driver);
+			return PTR_ERR(default_pdev);
+		}
 	}
 
 	ret = vkms_init_configfs();
 	if (ret) {
 		DRM_ERROR("Unable to initialize configfs\n");
-		platform_device_unregister(pdev);
+		if (default_pdev)
+			platform_device_unregister(default_pdev);
+
 		platform_driver_unregister(&vkms_platform_driver);
 	}
 
-- 
2.41.0.162.gfafddb0af9-goog


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 1/6] drm/vkms: Back VKMS with DRM memory management instead of static objects
  2023-06-23 22:23 ` [PATCH v2 1/6] drm/vkms: Back VKMS with DRM memory management instead of static objects Jim Shargo
@ 2023-06-25 17:40   ` Maira Canal
  0 siblings, 0 replies; 21+ messages in thread
From: Maira Canal @ 2023-06-25 17:40 UTC (permalink / raw)
  To: Jim Shargo, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: dri-devel, linux-doc, linux-kernel

Hi Jim,

Thanks for working on this great feature for the VKMS!

On 6/23/23 19:23, Jim Shargo wrote:
> This is a small refactor to make ConfigFS support easier. Once we
> support ConfigFS, there can be multiple devices instantiated by the
> driver, and so moving everything into managed memory makes things much
> easier.
> 
> This should be a no-op refactor.
> 
> Signed-off-by: Jim Shargo <jshargo@chromium.org>
> ---
>   drivers/gpu/drm/vkms/vkms_drv.c    | 130 +++++++++++++++--------------
>   drivers/gpu/drm/vkms/vkms_drv.h    |   4 +-
>   drivers/gpu/drm/vkms/vkms_output.c |   6 +-
>   3 files changed, 72 insertions(+), 68 deletions(-)
> 
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
> index e3c9c9571c8d..f07454fff046 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.c
> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
> @@ -9,10 +9,12 @@
>    * the GPU in DRM API tests.
>    */
>   
> +#include <linux/device.h>
>   #include <linux/module.h>
>   #include <linux/platform_device.h>
>   #include <linux/dma-mapping.h>
>   
> +#include <drm/drm_device.h>
>   #include <drm/drm_gem.h>
>   #include <drm/drm_atomic.h>
>   #include <drm/drm_atomic_helper.h>
> @@ -37,8 +39,6 @@
>   #define DRIVER_MAJOR	1
>   #define DRIVER_MINOR	0
>   
> -static struct vkms_config *default_config;
> -
>   static bool enable_cursor = true;
>   module_param_named(enable_cursor, enable_cursor, bool, 0444);
>   MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
> @@ -92,13 +92,13 @@ static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
>   
>   static int vkms_config_show(struct seq_file *m, void *data)
>   {
> -	struct drm_debugfs_entry *entry = m->private;
> -	struct drm_device *dev = entry->dev;
> -	struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
> +	struct drm_info_node *drm_info = m->private;

The VKMS already moved to the new DRM debugfs interface, there is no
need to move back to the old interface. I believe you should keep
using struct drm_debugfs_entry.

Also, that's probably the reason why the vkms_config file is not being
properly updated:

[root@fedora ~]# modprobe vkms enable_overlay=1 enable_cursor=1 
enable_writeback=1
[root@fedora ~]# cd /sys/kernel/debug/dri/0
[root@fedora 0]# cat vkms_config
writeback=0
cursor=0
overlay=0

> +	struct vkms_device *vkms_device =
> +		drm_device_to_vkms_device(drm_info->minor->dev);
>   
> -	seq_printf(m, "writeback=%d\n", vkmsdev->config->writeback);
> -	seq_printf(m, "cursor=%d\n", vkmsdev->config->cursor);
> -	seq_printf(m, "overlay=%d\n", vkmsdev->config->overlay);
> +	seq_printf(m, "writeback=%d\n", vkms_device->config.writeback);
> +	seq_printf(m, "cursor=%d\n", vkms_device->config.cursor);
> +	seq_printf(m, "overlay=%d\n", vkms_device->config.overlay);
>   
>   	return 0;
>   }
> @@ -155,114 +155,120 @@ static int vkms_modeset_init(struct vkms_device *vkmsdev)
>   	return vkms_output_init(vkmsdev, 0);
>   }
>   
> -static int vkms_create(struct vkms_config *config)
> +static int vkms_platform_probe(struct platform_device *pdev)
>   {
>   	int ret;
> -	struct platform_device *pdev;
>   	struct vkms_device *vkms_device;
> +	void *grp;
>   
> -	pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
> -	if (IS_ERR(pdev))
> -		return PTR_ERR(pdev);
> -
> -	if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) {
> -		ret = -ENOMEM;
> -		goto out_unregister;
> +	grp = devres_open_group(&pdev->dev, NULL, GFP_KERNEL);
> +	if (!grp) {
> +		return -ENOMEM;
>   	}
>   
>   	vkms_device = devm_drm_dev_alloc(&pdev->dev, &vkms_driver,
>   					 struct vkms_device, drm);
>   	if (IS_ERR(vkms_device)) {
>   		ret = PTR_ERR(vkms_device);
> -		goto out_devres;
> +		goto out_release_group;
>   	}
> +
>   	vkms_device->platform = pdev;
> -	vkms_device->config = config;
> -	config->dev = vkms_device;
> +	vkms_device->config.cursor = enable_cursor;
> +	vkms_device->config.writeback = enable_writeback;
> +	vkms_device->config.overlay = enable_overlay;
>   
>   	ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev,
>   					   DMA_BIT_MASK(64));
> -
>   	if (ret) {
>   		DRM_ERROR("Could not initialize DMA support\n");
> -		goto out_devres;
> +		goto out_release_group;
>   	}
>   
>   	ret = drm_vblank_init(&vkms_device->drm, 1);
>   	if (ret) {
>   		DRM_ERROR("Failed to vblank\n");
> -		goto out_devres;
> +		goto out_release_group;
>   	}
>   
>   	ret = vkms_modeset_init(vkms_device);
> -	if (ret)
> -		goto out_devres;
> +	if (ret) {
> +		DRM_ERROR("Unable to initialize modesetting\n");
> +		goto out_release_group;
> +	}
>   
>   	drm_debugfs_add_files(&vkms_device->drm, vkms_config_debugfs_list,
>   			      ARRAY_SIZE(vkms_config_debugfs_list));
>   
>   	ret = drm_dev_register(&vkms_device->drm, 0);
> -	if (ret)
> -		goto out_devres;
> +	if (ret) {
> +		DRM_ERROR("Unable to register device\n");
> +		return ret;

Shouldn't you go to out_release_group inside of just returning?

Best Regards,
- Maíra

> +	}
>   
>   	drm_fbdev_generic_setup(&vkms_device->drm, 0);
> +	platform_set_drvdata(pdev, vkms_device);
> +	devres_close_group(&pdev->dev, grp);
>   
>   	return 0;
>   
> -out_devres:
> -	devres_release_group(&pdev->dev, NULL);
> -out_unregister:
> -	platform_device_unregister(pdev);
> +out_release_group:
> +	devres_release_group(&pdev->dev, grp);
>   	return ret;
>   }
>   
> -static int __init vkms_init(void)
> +static int vkms_platform_remove(struct platform_device *pdev)
>   {
> -	int ret;
> -	struct vkms_config *config;
> -
> -	config = kmalloc(sizeof(*config), GFP_KERNEL);
> -	if (!config)
> -		return -ENOMEM;
> -
> -	default_config = config;
> +	struct vkms_device *vkms_device;
>   
> -	config->cursor = enable_cursor;
> -	config->writeback = enable_writeback;
> -	config->overlay = enable_overlay;
> +	vkms_device = platform_get_drvdata(pdev);
> +	if (!vkms_device)
> +		return 0;
>   
> -	ret = vkms_create(config);
> -	if (ret)
> -		kfree(config);
> -
> -	return ret;
> +	drm_dev_unregister(&vkms_device->drm);
> +	drm_atomic_helper_shutdown(&vkms_device->drm);
> +	return 0;
>   }
>   
> -static void vkms_destroy(struct vkms_config *config)
> +static struct platform_driver vkms_platform_driver = {
> +	.probe = vkms_platform_probe,
> +	.remove = vkms_platform_remove,
> +	.driver.name = DRIVER_NAME,
> +};
> +
> +static int __init vkms_init(void)
>   {
> +	int ret;
>   	struct platform_device *pdev;
>   
> -	if (!config->dev) {
> -		DRM_INFO("vkms_device is NULL.\n");
> -		return;
> +	ret = platform_driver_register(&vkms_platform_driver);
> +	if (ret) {
> +		DRM_ERROR("Unable to register platform driver\n");
> +		return ret;
>   	}
>   
> -	pdev = config->dev->platform;
> -
> -	drm_dev_unregister(&config->dev->drm);
> -	drm_atomic_helper_shutdown(&config->dev->drm);
> -	devres_release_group(&pdev->dev, NULL);
> -	platform_device_unregister(pdev);
> +	pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
> +	if (IS_ERR(pdev)) {
> +		platform_driver_unregister(&vkms_platform_driver);
> +		return PTR_ERR(pdev);
> +	}
>   
> -	config->dev = NULL;
> +	return 0;
>   }
>   
>   static void __exit vkms_exit(void)
>   {
> -	if (default_config->dev)
> -		vkms_destroy(default_config);
> +	struct device *dev;
> +
> +	while ((dev = platform_find_device_by_driver(
> +			NULL, &vkms_platform_driver.driver))) {
> +		// platform_find_device_by_driver increments the refcount. Drop
> +		// it so we don't leak memory.
> +		put_device(dev);
> +		platform_device_unregister(to_platform_device(dev));
> +	}
>   
> -	kfree(default_config);
> +	platform_driver_unregister(&vkms_platform_driver);
>   }
>   
>   module_init(vkms_init);
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
> index 5f1a0a44a78c..e87c8aea6fb3 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.h
> +++ b/drivers/gpu/drm/vkms/vkms_drv.h
> @@ -114,15 +114,13 @@ struct vkms_config {
>   	bool writeback;
>   	bool cursor;
>   	bool overlay;
> -	/* only set when instantiated */
> -	struct vkms_device *dev;
>   };
>   
>   struct vkms_device {
>   	struct drm_device drm;
>   	struct platform_device *platform;
>   	struct vkms_output output;
> -	const struct vkms_config *config;
> +	struct vkms_config config;
>   };
>   
>   #define drm_crtc_to_vkms_output(target) \
> diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
> index 5ce70dd946aa..963a64cf068b 100644
> --- a/drivers/gpu/drm/vkms/vkms_output.c
> +++ b/drivers/gpu/drm/vkms/vkms_output.c
> @@ -62,7 +62,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
>   	if (IS_ERR(primary))
>   		return PTR_ERR(primary);
>   
> -	if (vkmsdev->config->overlay) {
> +	if (vkmsdev->config.overlay) {
>   		for (n = 0; n < NUM_OVERLAY_PLANES; n++) {
>   			ret = vkms_add_overlay_plane(vkmsdev, index, crtc);
>   			if (ret)
> @@ -70,7 +70,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
>   		}
>   	}
>   
> -	if (vkmsdev->config->cursor) {
> +	if (vkmsdev->config.cursor) {
>   		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR, index);
>   		if (IS_ERR(cursor))
>   			return PTR_ERR(cursor);
> @@ -103,7 +103,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
>   		goto err_attach;
>   	}
>   
> -	if (vkmsdev->config->writeback) {
> +	if (vkmsdev->config.writeback) {
>   		writeback = vkms_enable_writeback_connector(vkmsdev);
>   		if (writeback)
>   			DRM_ERROR("Failed to init writeback connector\n");

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device
  2023-06-23 22:23 ` [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device Jim Shargo
@ 2023-06-25 17:57   ` Maira Canal
  2023-08-15 11:29   ` Marius Vlad
  1 sibling, 0 replies; 21+ messages in thread
From: Maira Canal @ 2023-06-25 17:57 UTC (permalink / raw)
  To: Jim Shargo, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: dri-devel, linux-doc, linux-kernel

Hi Jim,

On 6/23/23 19:23, Jim Shargo wrote:
> This change supports multiple CRTCs, encoders, connectors instead of one
> of each per device.
> 
> Since ConfigFS-based devices will support multiple crtcs, it's useful to
> move all of the writeback/composition data from being per-"output" to
> being per-CRTC.
> 
> Since there's still only ever one CRTC, this should be a no-op refactor.
> 
> Signed-off-by: Jim Shargo <jshargo@chromium.org>
> ---
>   drivers/gpu/drm/vkms/vkms_composer.c  |  28 +++---
>   drivers/gpu/drm/vkms/vkms_crtc.c      |  95 +++++++++++---------
>   drivers/gpu/drm/vkms/vkms_drv.c       |  14 +--
>   drivers/gpu/drm/vkms/vkms_drv.h       |  65 +++++++++-----
>   drivers/gpu/drm/vkms/vkms_output.c    | 123 ++++++++++++++++++--------
>   drivers/gpu/drm/vkms/vkms_plane.c     |  44 ++++++---
>   drivers/gpu/drm/vkms/vkms_writeback.c |  26 +++---
>   7 files changed, 252 insertions(+), 143 deletions(-)
> 
> diff --git a/drivers/gpu/drm/vkms/vkms_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c
> index 1636ce3a79f9..75fa42e0ec07 100644
> --- a/drivers/gpu/drm/vkms/vkms_composer.c
> +++ b/drivers/gpu/drm/vkms/vkms_composer.c
> @@ -230,13 +230,13 @@ void vkms_composer_worker(struct work_struct *work)
>   						composer_work);
>   	struct drm_crtc *crtc = crtc_state->base.crtc;
>   	struct vkms_writeback_job *active_wb = crtc_state->active_writeback;
> -	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>   	bool crc_pending, wb_pending;
>   	u64 frame_start, frame_end;
>   	u32 crc32 = 0;
>   	int ret;
>   
> -	spin_lock_irq(&out->composer_lock);
> +	spin_lock_irq(&vkms_crtc->composer_lock);
>   	frame_start = crtc_state->frame_start;
>   	frame_end = crtc_state->frame_end;
>   	crc_pending = crtc_state->crc_pending;
> @@ -244,7 +244,7 @@ void vkms_composer_worker(struct work_struct *work)
>   	crtc_state->frame_start = 0;
>   	crtc_state->frame_end = 0;
>   	crtc_state->crc_pending = false;
> -	spin_unlock_irq(&out->composer_lock);
> +	spin_unlock_irq(&vkms_crtc->composer_lock);
>   
>   	/*
>   	 * We raced with the vblank hrtimer and previous work already computed
> @@ -262,10 +262,10 @@ void vkms_composer_worker(struct work_struct *work)
>   		return;
>   
>   	if (wb_pending) {
> -		drm_writeback_signal_completion(&out->wb_connector, 0);
> -		spin_lock_irq(&out->composer_lock);
> +		drm_writeback_signal_completion(&vkms_crtc->wb_connector, 0);
> +		spin_lock_irq(&vkms_crtc->composer_lock);
>   		crtc_state->wb_pending = false;
> -		spin_unlock_irq(&out->composer_lock);
> +		spin_unlock_irq(&vkms_crtc->composer_lock);
>   	}
>   
>   	/*
> @@ -315,25 +315,25 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *src_name,
>   	return 0;
>   }
>   
> -void vkms_set_composer(struct vkms_output *out, bool enabled)
> +void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled)
>   {
>   	bool old_enabled;
>   
>   	if (enabled)
> -		drm_crtc_vblank_get(&out->crtc);
> +		drm_crtc_vblank_get(&vkms_crtc->base);
>   
> -	spin_lock_irq(&out->lock);
> -	old_enabled = out->composer_enabled;
> -	out->composer_enabled = enabled;
> -	spin_unlock_irq(&out->lock);
> +	spin_lock_irq(&vkms_crtc->lock);
> +	old_enabled = vkms_crtc->composer_enabled;
> +	vkms_crtc->composer_enabled = enabled;
> +	spin_unlock_irq(&vkms_crtc->lock);
>   
>   	if (old_enabled)
> -		drm_crtc_vblank_put(&out->crtc);
> +		drm_crtc_vblank_put(&vkms_crtc->base);
>   }
>   
>   int vkms_set_crc_source(struct drm_crtc *crtc, const char *src_name)
>   {
> -	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *out = drm_crtc_to_vkms_crtc(crtc);
>   	bool enabled = false;
>   	int ret = 0;
>   
> diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
> index 515f6772b866..d91e49c53adc 100644
> --- a/drivers/gpu/drm/vkms/vkms_crtc.c
> +++ b/drivers/gpu/drm/vkms/vkms_crtc.c
> @@ -11,35 +11,34 @@
>   
>   static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
>   {
> -	struct vkms_output *output = container_of(timer, struct vkms_output,
> -						  vblank_hrtimer);
> -	struct drm_crtc *crtc = &output->crtc;
> +	struct vkms_crtc *vkms_crtc = timer_to_vkms_crtc(timer);
> +	struct drm_crtc *crtc = &vkms_crtc->base;
>   	struct vkms_crtc_state *state;
>   	u64 ret_overrun;
>   	bool ret, fence_cookie;
>   
>   	fence_cookie = dma_fence_begin_signalling();
>   
> -	ret_overrun = hrtimer_forward_now(&output->vblank_hrtimer,
> -					  output->period_ns);
> +	ret_overrun = hrtimer_forward_now(&vkms_crtc->vblank_hrtimer,
> +					  vkms_crtc->period_ns);
>   	if (ret_overrun != 1)
>   		pr_warn("%s: vblank timer overrun\n", __func__);
>   
> -	spin_lock(&output->lock);
> +	spin_lock(&vkms_crtc->lock);
>   	ret = drm_crtc_handle_vblank(crtc);
>   	if (!ret)
>   		DRM_ERROR("vkms failure on handling vblank");
>   
> -	state = output->composer_state;
> -	spin_unlock(&output->lock);
> +	state = vkms_crtc->composer_state;
> +	spin_unlock(&vkms_crtc->lock);
>   
> -	if (state && output->composer_enabled) {
> +	if (state && vkms_crtc->composer_enabled) {
>   		u64 frame = drm_crtc_accurate_vblank_count(crtc);
>   
>   		/* update frame_start only if a queued vkms_composer_worker()
>   		 * has read the data
>   		 */
> -		spin_lock(&output->composer_lock);
> +		spin_lock(&vkms_crtc->composer_lock);
>   		if (!state->crc_pending)
>   			state->frame_start = frame;
>   		else
> @@ -47,9 +46,10 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
>   					 state->frame_start, frame);
>   		state->frame_end = frame;
>   		state->crc_pending = true;
> -		spin_unlock(&output->composer_lock);
> +		spin_unlock(&vkms_crtc->composer_lock);
>   
> -		ret = queue_work(output->composer_workq, &state->composer_work);
> +		ret = queue_work(vkms_crtc->composer_workq,
> +				 &state->composer_work);
>   		if (!ret)
>   			DRM_DEBUG_DRIVER("Composer worker already queued\n");
>   	}
> @@ -62,25 +62,27 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
>   static int vkms_enable_vblank(struct drm_crtc *crtc)
>   {
>   	struct drm_device *dev = crtc->dev;
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>   	unsigned int pipe = drm_crtc_index(crtc);
>   	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
> -	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
>   
>   	drm_calc_timestamping_constants(crtc, &crtc->mode);
>   
> -	hrtimer_init(&out->vblank_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> -	out->vblank_hrtimer.function = &vkms_vblank_simulate;
> -	out->period_ns = ktime_set(0, vblank->framedur_ns);
> -	hrtimer_start(&out->vblank_hrtimer, out->period_ns, HRTIMER_MODE_REL);
> +	hrtimer_init(&vkms_crtc->vblank_hrtimer, CLOCK_MONOTONIC,
> +		     HRTIMER_MODE_REL);
> +	vkms_crtc->vblank_hrtimer.function = &vkms_vblank_simulate;
> +	vkms_crtc->period_ns = ktime_set(0, vblank->framedur_ns);
> +	hrtimer_start(&vkms_crtc->vblank_hrtimer, vkms_crtc->period_ns,
> +		      HRTIMER_MODE_REL);
>   
>   	return 0;
>   }
>   
>   static void vkms_disable_vblank(struct drm_crtc *crtc)
>   {
> -	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>   
> -	hrtimer_cancel(&out->vblank_hrtimer);
> +	hrtimer_cancel(&vkms_crtc->vblank_hrtimer);
>   }
>   
>   static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
> @@ -88,9 +90,8 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
>   				      bool in_vblank_irq)
>   {
>   	struct drm_device *dev = crtc->dev;
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>   	unsigned int pipe = crtc->index;
> -	struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
> -	struct vkms_output *output = &vkmsdev->output;
>   	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
>   
>   	if (!READ_ONCE(vblank->enabled)) {
> @@ -98,7 +99,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
>   		return true;
>   	}
>   
> -	*vblank_time = READ_ONCE(output->vblank_hrtimer.node.expires);
> +	*vblank_time = READ_ONCE(vkms_crtc->vblank_hrtimer.node.expires);
>   
>   	if (WARN_ON(*vblank_time == vblank->time))
>   		return true;
> @@ -110,7 +111,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
>   	 * the vblank core expects. Therefore we need to always correct the
>   	 * timestampe by one frame.
>   	 */
> -	*vblank_time -= output->period_ns;
> +	*vblank_time -= vkms_crtc->period_ns;
>   
>   	return true;
>   }
> @@ -236,18 +237,18 @@ static void vkms_crtc_atomic_disable(struct drm_crtc *crtc,
>   static void vkms_crtc_atomic_begin(struct drm_crtc *crtc,
>   				   struct drm_atomic_state *state)
>   {
> -	struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>   
>   	/* This lock is held across the atomic commit to block vblank timer
>   	 * from scheduling vkms_composer_worker until the composer is updated
>   	 */
> -	spin_lock_irq(&vkms_output->lock);
> +	spin_lock_irq(&vkms_crtc->lock);
>   }
>   
>   static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
>   				   struct drm_atomic_state *state)
>   {
> -	struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>   
>   	if (crtc->state->event) {
>   		spin_lock(&crtc->dev->event_lock);
> @@ -262,9 +263,9 @@ static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
>   		crtc->state->event = NULL;
>   	}
>   
> -	vkms_output->composer_state = to_vkms_crtc_state(crtc->state);
> +	vkms_crtc->composer_state = to_vkms_crtc_state(crtc->state);
>   
> -	spin_unlock_irq(&vkms_output->lock);
> +	spin_unlock_irq(&vkms_crtc->lock);
>   }
>   
>   static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
> @@ -275,27 +276,41 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
>   	.atomic_disable	= vkms_crtc_atomic_disable,
>   };
>   
> -int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
> -		   struct drm_plane *primary, struct drm_plane *cursor)
> +struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
> +				 struct drm_plane *primary,
> +				 struct drm_plane *cursor)
>   {
> -	struct vkms_output *vkms_out = drm_crtc_to_vkms_output(crtc);
> +	struct drm_device *dev = &vkmsdev->drm;
> +	struct vkms_crtc *vkms_crtc;
>   	int ret;
>   
> -	ret = drmm_crtc_init_with_planes(dev, crtc, primary, cursor,
> +	if (vkmsdev->output.num_crtcs >= VKMS_MAX_OUTPUT_OBJECTS) {
> +		return ERR_PTR(-ENOMEM);
> +	}
> +	vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
> +
> +	ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, primary, cursor,
>   					 &vkms_crtc_funcs, NULL);
>   	if (ret) {
>   		DRM_ERROR("Failed to init CRTC\n");
> -		return ret;
> +		goto out_error;
>   	}
>   
> -	drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs);
> +	drm_crtc_helper_add(&vkms_crtc->base, &vkms_crtc_helper_funcs);
>   
> -	spin_lock_init(&vkms_out->lock);
> -	spin_lock_init(&vkms_out->composer_lock);
> +	spin_lock_init(&vkms_crtc->lock);
> +	spin_lock_init(&vkms_crtc->composer_lock);
>   
> -	vkms_out->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
> -	if (!vkms_out->composer_workq)
> -		return -ENOMEM;
> +	vkms_crtc->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
> +	if (!vkms_crtc->composer_workq) {
> +		ret = -ENOMEM;
> +		goto out_error;
> +	}
> +
> +	return vkms_crtc;
>   
> -	return ret;
> +out_error:
> +	memset(vkms_crtc, 0, sizeof(*vkms_crtc));
> +	vkmsdev->output.num_crtcs -= 1;
> +	return ERR_PTR(ret);
>   }
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
> index f07454fff046..aa2bee82b757 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.c
> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
> @@ -57,8 +57,8 @@ static void vkms_release(struct drm_device *dev)
>   {
>   	struct vkms_device *vkms = drm_device_to_vkms_device(dev);
>   
> -	if (vkms->output.composer_workq)
> -		destroy_workqueue(vkms->output.composer_workq);
> +	for (int i = 0; i < vkms->output.num_crtcs; i++)
> +		destroy_workqueue(vkms->output.crtcs[i].composer_workq);

Is there no need to check if vkms->output.crtcs[i].composer_workq is
NULL?

>   }
>   
>   static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
> @@ -185,15 +185,15 @@ static int vkms_platform_probe(struct platform_device *pdev)
>   		goto out_release_group;
>   	}
>   
> -	ret = drm_vblank_init(&vkms_device->drm, 1);
> +	ret = vkms_modeset_init(vkms_device);
>   	if (ret) {
> -		DRM_ERROR("Failed to vblank\n");
> +		DRM_ERROR("Unable to initialize modesetting\n");
>   		goto out_release_group;
>   	}
>   
> -	ret = vkms_modeset_init(vkms_device);
> +	ret = drm_vblank_init(&vkms_device->drm, vkms_device->output.num_crtcs);
>   	if (ret) {
> -		DRM_ERROR("Unable to initialize modesetting\n");
> +		DRM_ERROR("Failed to vblank\n");
>   		goto out_release_group;
>   	}
>   
> @@ -202,7 +202,7 @@ static int vkms_platform_probe(struct platform_device *pdev)
>   
>   	ret = drm_dev_register(&vkms_device->drm, 0);
>   	if (ret) {
> -		DRM_ERROR("Unable to register device\n");
> +		DRM_ERROR("Unable to register device with id %d\n", pdev->id);

I believe this belongs to the first patch.

>   		return ret;
>   	}
>   
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
> index e87c8aea6fb3..4c3274838ec7 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.h
> +++ b/drivers/gpu/drm/vkms/vkms_drv.h
> @@ -23,6 +23,9 @@
>   
>   #define NUM_OVERLAY_PLANES 8
>   
> +#define VKMS_MAX_OUTPUT_OBJECTS 16
> +#define VKMS_MAX_PLANES (3 * VKMS_MAX_OUTPUT_OBJECTS)
> +
>   struct vkms_frame_info {
>   	struct drm_framebuffer *fb;
>   	struct drm_rect src, dst;
> @@ -65,6 +68,25 @@ struct vkms_plane {
>   	struct drm_plane base;
>   };
>   
> +struct vkms_crtc {
> +	struct drm_crtc base;
> +
> +	struct drm_writeback_connector wb_connector;
> +	struct hrtimer vblank_hrtimer;
> +	ktime_t period_ns;
> +	struct drm_pending_vblank_event *event;
> +	/* ordered wq for composer_work */
> +	struct workqueue_struct *composer_workq;
> +	/* protects concurrent access to composer */
> +	spinlock_t lock;
> +
> +	/* protected by @lock */
> +	bool composer_enabled;
> +	struct vkms_crtc_state *composer_state;
> +
> +	spinlock_t composer_lock;
> +};
> +
>   /**
>    * vkms_crtc_state - Driver specific CRTC state
>    * @base: base CRTC state

[...]

> diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c
> index e5c625ab8e3e..f69621822c1b 100644
> --- a/drivers/gpu/drm/vkms/vkms_plane.c
> +++ b/drivers/gpu/drm/vkms/vkms_plane.c
> @@ -8,6 +8,8 @@
>   #include <drm/drm_fourcc.h>
>   #include <drm/drm_gem_atomic_helper.h>
>   #include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_plane_helper.h>
>   
>   #include "vkms_drv.h"
>   #include "vkms_formats.h"
> @@ -65,6 +67,20 @@ static void vkms_plane_destroy_state(struct drm_plane *plane,
>   	kfree(vkms_state);
>   }
>   
> +static void vkms_plane_destroy(struct drm_plane *plane)
> +{
> +	struct vkms_plane *vkms_plane =
> +		container_of(plane, struct vkms_plane, base);
> +
> +	if (plane->state) {
> +		vkms_plane_destroy_state(plane, plane->state);
> +		plane->state = NULL;
> +	}
> +
> +	drm_plane_cleanup(plane);
> +	memset(vkms_plane, 0, sizeof(struct vkms_plane));
> +}
> +
>   static void vkms_plane_reset(struct drm_plane *plane)
>   {
>   	struct vkms_plane_state *vkms_state;
> @@ -84,11 +100,12 @@ static void vkms_plane_reset(struct drm_plane *plane)
>   }
>   
>   static const struct drm_plane_funcs vkms_plane_funcs = {
> -	.update_plane		= drm_atomic_helper_update_plane,
> -	.disable_plane		= drm_atomic_helper_disable_plane,
> -	.reset			= vkms_plane_reset,

I believe that there is no need to change this lines.

> +	.update_plane = drm_atomic_helper_update_plane,
> +	.disable_plane = drm_atomic_helper_disable_plane,
> +	.destroy = vkms_plane_destroy,
> +	.reset = vkms_plane_reset,
>   	.atomic_duplicate_state = vkms_plane_duplicate_state,
> -	.atomic_destroy_state	= vkms_plane_destroy_state,
> +	.atomic_destroy_state = vkms_plane_destroy_state,
>   };
>   
>   static void vkms_plane_atomic_update(struct drm_plane *plane,
> @@ -198,17 +215,22 @@ static const struct drm_plane_helper_funcs vkms_plane_helper_funcs = {
>   };
>   
>   struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
> -				   enum drm_plane_type type, int index)
> +				   enum drm_plane_type type)
>   {
>   	struct drm_device *dev = &vkmsdev->drm;
> +	struct vkms_output *output = &vkmsdev->output;
>   	struct vkms_plane *plane;
> +	int ret;
>   
> -	plane = drmm_universal_plane_alloc(dev, struct vkms_plane, base, 1 << index,
> -					   &vkms_plane_funcs,
> -					   vkms_formats, ARRAY_SIZE(vkms_formats),
> -					   NULL, type, NULL);
> -	if (IS_ERR(plane))
> -		return plane;
> +	if (output->num_planes >= VKMS_MAX_PLANES)
> +		return ERR_PTR(-ENOMEM);
> +
> +	plane = &output->planes[output->num_planes++];
> +	ret = drm_universal_plane_init(dev, &plane->base, 0, &vkms_plane_funcs,
> +				       vkms_formats, ARRAY_SIZE(vkms_formats),
> +				       NULL, type, NULL);

Wouldn't be possible to use drmm_universal_plane_alloc?

Best Regards,
- Maíra

> +	if (ret)
> +		return ERR_PTR(ret);
>   
>   	drm_plane_helper_add(&plane->base, &vkms_plane_helper_funcs);
>   
> diff --git a/drivers/gpu/drm/vkms/vkms_writeback.c b/drivers/gpu/drm/vkms/vkms_writeback.c
> index 84a51cd281b9..b90bcbd0a0e0 100644
> --- a/drivers/gpu/drm/vkms/vkms_writeback.c
> +++ b/drivers/gpu/drm/vkms/vkms_writeback.c
> @@ -1,6 +1,7 @@
>   // SPDX-License-Identifier: GPL-2.0+
>   
>   #include <linux/iosys-map.h>
> +#include <linux/kernel.h>
>   
>   #include <drm/drm_atomic.h>
>   #include <drm/drm_edid.h>
> @@ -101,7 +102,8 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
>   				struct drm_writeback_job *job)
>   {
>   	struct vkms_writeback_job *vkmsjob = job->priv;
> -	struct vkms_device *vkmsdev;
> +	struct vkms_crtc *vkms_crtc =
> +		container_of(connector, struct vkms_crtc, wb_connector);
>   
>   	if (!job->fb)
>   		return;
> @@ -110,8 +112,7 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
>   
>   	drm_framebuffer_put(vkmsjob->wb_frame_info.fb);
>   
> -	vkmsdev = drm_device_to_vkms_device(job->fb->dev);
> -	vkms_set_composer(&vkmsdev->output, false);
> +	vkms_set_composer(vkms_crtc, false);
>   	kfree(vkmsjob);
>   }
>   
> @@ -120,11 +121,11 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
>   {
>   	struct drm_connector_state *connector_state = drm_atomic_get_new_connector_state(state,
>   											 conn);
> -	struct vkms_device *vkmsdev = drm_device_to_vkms_device(conn->dev);
> -	struct vkms_output *output = &vkmsdev->output;
> -	struct drm_writeback_connector *wb_conn = &output->wb_connector;
> +	struct vkms_crtc *vkms_crtc =
> +		drm_crtc_to_vkms_crtc(connector_state->crtc);
> +	struct drm_writeback_connector *wb_conn = &vkms_crtc->wb_connector;
>   	struct drm_connector_state *conn_state = wb_conn->base.state;
> -	struct vkms_crtc_state *crtc_state = output->composer_state;
> +	struct vkms_crtc_state *crtc_state = vkms_crtc->composer_state;
>   	struct drm_framebuffer *fb = connector_state->writeback_job->fb;
>   	u16 crtc_height = crtc_state->base.crtc->mode.vdisplay;
>   	u16 crtc_width = crtc_state->base.crtc->mode.hdisplay;
> @@ -135,18 +136,18 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
>   	if (!conn_state)
>   		return;
>   
> -	vkms_set_composer(&vkmsdev->output, true);
> +	vkms_set_composer(vkms_crtc, true);
>   
>   	active_wb = conn_state->writeback_job->priv;
>   	wb_frame_info = &active_wb->wb_frame_info;
>   
> -	spin_lock_irq(&output->composer_lock);
> +	spin_lock_irq(&vkms_crtc->composer_lock);
>   	crtc_state->active_writeback = active_wb;
>   	wb_frame_info->offset = fb->offsets[0];
>   	wb_frame_info->pitch = fb->pitches[0];
>   	wb_frame_info->cpp = fb->format->cpp[0];
>   	crtc_state->wb_pending = true;
> -	spin_unlock_irq(&output->composer_lock);
> +	spin_unlock_irq(&vkms_crtc->composer_lock);
>   	drm_writeback_queue_job(wb_conn, connector_state);
>   	active_wb->wb_write = get_line_to_frame_function(wb_format);
>   	drm_rect_init(&wb_frame_info->src, 0, 0, crtc_width, crtc_height);
> @@ -160,9 +161,10 @@ static const struct drm_connector_helper_funcs vkms_wb_conn_helper_funcs = {
>   	.atomic_commit = vkms_wb_atomic_commit,
>   };
>   
> -int vkms_enable_writeback_connector(struct vkms_device *vkmsdev)
> +int vkms_enable_writeback_connector(struct vkms_device *vkmsdev,
> +				    struct vkms_crtc *vkms_crtc)
>   {
> -	struct drm_writeback_connector *wb = &vkmsdev->output.wb_connector;
> +	struct drm_writeback_connector *wb = &vkms_crtc->wb_connector;
>   
>   	drm_connector_helper_add(&wb->base, &vkms_wb_conn_helper_funcs);
>   

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 6/6] drm/vkms: Add a module param to enable/disable the default device
  2023-06-23 22:23 ` [PATCH v2 6/6] drm/vkms: Add a module param to enable/disable the default device Jim Shargo
@ 2023-06-25 18:04   ` Maira Canal
  2023-08-18  5:39     ` Brandon Ross Pollack
  0 siblings, 1 reply; 21+ messages in thread
From: Maira Canal @ 2023-06-25 18:04 UTC (permalink / raw)
  To: Jim Shargo, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: dri-devel, linux-doc, linux-kernel

Hi Jim,

On 6/23/23 19:23, Jim Shargo wrote:
> In many testing circumstances, we will want to just create a new device
> and test against that. If we create a default device, it can be annoying
> to have to manually select the new device instead of choosing the only
> one that exists.
> 
> The param, enable_default, is defaulted to true to maintain backwards
> compatibility.
> 
> Signed-off-by: Jim Shargo <jshargo@chromium.org>
> ---
>   drivers/gpu/drm/vkms/vkms_drv.c | 44 ++++++++++++++++++++++-----------
>   1 file changed, 29 insertions(+), 15 deletions(-)
> 
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
> index 314a04659c5f..1cb56c090a65 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.c
> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
> @@ -42,17 +42,26 @@
>   #define DRIVER_MAJOR	1
>   #define DRIVER_MINOR	0
>   
> +static bool enable_default_device = true;
> +module_param_named(enable_default_device, enable_default_device, bool, 0444);
> +MODULE_PARM_DESC(enable_default_device,
> +		 "Enable/Disable creating the default device");

Wouldn't be better to just call it "enable_default"?

Also, could you add this parameter to vkms_config debugfs file?

Best Regards,
- Maíra

> +
>   static bool enable_cursor = true;
>   module_param_named(enable_cursor, enable_cursor, bool, 0444);
> -MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
> +MODULE_PARM_DESC(enable_cursor,
> +		 "Enable/Disable cursor support for the default device");
>   
>   static bool enable_writeback = true;
>   module_param_named(enable_writeback, enable_writeback, bool, 0444);
> -MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback connector support");
> +MODULE_PARM_DESC(
> +	enable_writeback,
> +	"Enable/Disable writeback connector support for the default device");
>   
>   static bool enable_overlay;
>   module_param_named(enable_overlay, enable_overlay, bool, 0444);
> -MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
> +MODULE_PARM_DESC(enable_overlay,
> +		 "Enable/Disable overlay support for the default device");
>   
>   DEFINE_DRM_GEM_FOPS(vkms_driver_fops);
>   
> @@ -278,10 +287,7 @@ void vkms_remove_device(struct vkms_device *vkms_device)
>   static int __init vkms_init(void)
>   {
>   	int ret;
> -	struct platform_device *pdev;
> -	struct vkms_device_setup vkms_device_setup = {
> -		.configfs = NULL,
> -	};
> +	struct platform_device *default_pdev = NULL;
>   
>   	ret = platform_driver_register(&vkms_platform_driver);
>   	if (ret) {
> @@ -289,19 +295,27 @@ static int __init vkms_init(void)
>   		return ret;
>   	}
>   
> -	pdev = platform_device_register_data(NULL, DRIVER_NAME, 0,
> -					     &vkms_device_setup,
> -					     sizeof(vkms_device_setup));
> -	if (IS_ERR(pdev)) {
> -		DRM_ERROR("Unable to register default vkms device\n");
> -		platform_driver_unregister(&vkms_platform_driver);
> -		return PTR_ERR(pdev);
> +	if (enable_default_device) {
> +		struct vkms_device_setup vkms_device_setup = {
> +			.configfs = NULL,
> +		};
> +
> +		default_pdev = platform_device_register_data(
> +			NULL, DRIVER_NAME, 0, &vkms_device_setup,
> +			sizeof(vkms_device_setup));
> +		if (IS_ERR(default_pdev)) {
> +			DRM_ERROR("Unable to register default vkms device\n");
> +			platform_driver_unregister(&vkms_platform_driver);
> +			return PTR_ERR(default_pdev);
> +		}
>   	}
>   
>   	ret = vkms_init_configfs();
>   	if (ret) {
>   		DRM_ERROR("Unable to initialize configfs\n");
> -		platform_device_unregister(pdev);
> +		if (default_pdev)
> +			platform_device_unregister(default_pdev);
> +
>   		platform_driver_unregister(&vkms_platform_driver);
>   	}
>   

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS
  2023-06-23 22:23 ` [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS Jim Shargo
@ 2023-06-25 18:19   ` Maira Canal
  2023-06-28  2:00   ` kernel test robot
  1 sibling, 0 replies; 21+ messages in thread
From: Maira Canal @ 2023-06-25 18:19 UTC (permalink / raw)
  To: Jim Shargo, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann
  Cc: dri-devel, linux-doc, linux-kernel

Hi Jim,

On 6/23/23 19:23, Jim Shargo wrote:
> 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 <jshargo@chromium.org>
> ---
>   Documentation/gpu/vkms.rst           |  17 +-
>   drivers/gpu/drm/Kconfig              |   1 +
>   drivers/gpu/drm/vkms/Makefile        |   1 +
>   drivers/gpu/drm/vkms/vkms_configfs.c | 646 +++++++++++++++++++++++++++
>   drivers/gpu/drm/vkms/vkms_drv.c      |  52 ++-
>   drivers/gpu/drm/vkms/vkms_drv.h      |  92 +++-
>   drivers/gpu/drm/vkms/vkms_output.c   |   5 +
>   7 files changed, 799 insertions(+), 15 deletions(-)
>   create mode 100644 drivers/gpu/drm/vkms/vkms_configfs.c
> 
> diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst
> index ba04ac7c2167..2c342ef0fb7b 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,15 @@ project.
>   Runtime Configuration
>   ---------------------
>   
> -We want to be able to reconfigure vkms instance without having to reload the
> -module. Use/Test-cases:
> +We want to vkms instances without having to reload the module. Such

I believe that there is a verb missing here.

> +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/Kconfig b/drivers/gpu/drm/Kconfig
> index afb3b2f5f425..71eb913b378f 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -262,6 +262,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 1b28a6a32948..6b83907ad554 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 000000000000..544024735d19
> --- /dev/null
> +++ b/drivers/gpu/drm/vkms/vkms_configfs.c
> @@ -0,0 +1,646 @@
> +// 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 in addition to an
> + * immutable "default" device created by the driver at initialization time. Note
> + * that the default device 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
> + *       |-- default
> + *       |   `-- enabled
> + *       `-- test
> + *           |-- connectors
> + *           |-- crtcs
> + *           |-- encoders
> + *           |-- planes
> + *           `-- enabled

I followed this steps and ended up with the following tree:

[root@fedora config]# tree vkms
vkms
└── test
     ├── connectors
     ├── crtcs
     ├── enabled
     ├── encoders
     ├── id
     └── planes

I'm not sure if I did something wrong or if the folder "default" really
shouldn't show. If the folder "default" shouldn't show, it would be nice
to remove it from the docs.

> + *
> + * 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/planes/cursor
> + *   echo $DRM_PLANE_TYPE_CURSOR > /config/vkms/test/planes/cursor/type
> + *
> + *   mkdir /config/vkms/test/planes/overlay
> + *   echo $DRM_PLANE_TYPE_OVERLAY > /config/vkms/test/planes/overlay/type
> + *
> + *   mkdir /config/vkms/test/crtcs/crtc
> + *   mkdir /config/vkms/test/crtcs/crtc_other
> + *   mkdir /config/vkms/test/encoders/encoder
> + *   mkdir /config/vkms/test/connectors/connector
> + *
> + * 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/crtcs/crtc /config/vkms/test/encoders/encoder/possible_crtcs/
> + *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/primary/possible_crtcs/
> + *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/cursor/possible_crtcs/
> + *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/overlay/possible_crtcs/
> + *
> + *   ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/overlay/possible_crtcs/
> + *   ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/other_primary/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/connectors/connector
> + *
> + *   rmdir /config/vkms/test
> + */
> +
> +/*

[...]

> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
> index d43121addf66..1b5b7143792f 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.c
> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
> @@ -9,7 +9,10 @@
>    * the GPU in DRM API tests.
>    */
>   
> +#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>
> @@ -152,8 +155,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) :
> -				     -ENOSYS;

Is this middle step really needed?

Best Regards,
- Maíra

> +	return vkmsdev->configfs ? vkms_output_init(vkmsdev) :
> +				   vkms_output_init_default(vkmsdev);
>   }
>   
>   static int vkms_platform_probe(struct platform_device *pdev)
> @@ -165,6 +168,7 @@ static int vkms_platform_probe(struct platform_device *pdev)
>   
>   	grp = devres_open_group(&pdev->dev, NULL, GFP_KERNEL);
>   	if (!grp) {
> +		DRM_ERROR("Could not open devres group\n");
>   		return -ENOMEM;
>   	}
>   
> @@ -179,7 +183,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));
> @@ -239,12 +243,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);
> @@ -262,6 +297,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;
>   }
>   
> @@ -269,6 +311,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 84b35aa1dc5a..3634eeeb4548 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
> @@ -127,14 +129,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;
>   };
> @@ -153,11 +206,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,
> @@ -179,4 +263,8 @@ void vkms_compose_row(struct line_buffer *stage_buffer, struct vkms_plane_state
>   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 3b5e272a8b33..c9e6c653de19 100644
> --- a/drivers/gpu/drm/vkms/vkms_output.c
> +++ b/drivers/gpu/drm/vkms/vkms_output.c
> @@ -177,3 +177,8 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>   
>   	return ret;
>   }
> +
> +int vkms_output_init(struct vkms_device *vkmsdev)
> +{
> +	return -ENOTSUPP;
> +}

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS
  2023-06-23 22:23 ` [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS Jim Shargo
  2023-06-25 18:19   ` Maira Canal
@ 2023-06-28  2:00   ` kernel test robot
  1 sibling, 0 replies; 21+ messages in thread
From: kernel test robot @ 2023-06-28  2:00 UTC (permalink / raw)
  To: Jim Shargo, mairacanal, Daniel Vetter, David Airlie,
	Haneen Mohammed, Jonathan Corbet, Maarten Lankhorst,
	Maxime Ripard, Melissa Wen, Rodrigo Siqueira, Thomas Zimmermann
  Cc: oe-kbuild-all, Jim Shargo, linux-kernel, dri-devel, linux-doc

Hi Jim,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on drm-intel/for-linux-next next-20230627]
[cannot apply to drm-intel/for-linux-next-fixes drm-tip/drm-tip linus/master v6.4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Jim-Shargo/drm-vkms-Back-VKMS-with-DRM-memory-management-instead-of-static-objects/20230624-062659
base:   git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link:    https://lore.kernel.org/r/20230623222353.97283-5-jshargo%40chromium.org
patch subject: [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS
config: xtensa-randconfig-r052-20230627 (https://download.01.org/0day-ci/archive/20230628/202306280759.b1BQCTXw-lkp@intel.com/config)
compiler: xtensa-linux-gcc (GCC) 12.3.0
reproduce: (https://download.01.org/0day-ci/archive/20230628/202306280759.b1BQCTXw-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202306280759.b1BQCTXw-lkp@intel.com/

cocci warnings: (new ones prefixed by >>)
>> drivers/gpu/drm/vkms/vkms_drv.c:266:9-16: WARNING: ERR_CAST can be used with pdev

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 21+ messages in thread

* (no subject)
  2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
                   ` (5 preceding siblings ...)
  2023-06-23 22:23 ` [PATCH v2 6/6] drm/vkms: Add a module param to enable/disable the default device Jim Shargo
@ 2023-08-08  2:42 ` Brandon Pollack
  2023-08-08  2:42 ` [PATCH] Initial backport of vkms changes from 6.4, including jshargo and brpols configs changes Brandon Pollack
  7 siblings, 0 replies; 21+ messages in thread
From: Brandon Pollack @ 2023-08-08  2:42 UTC (permalink / raw)
  To: jshargo
  Cc: airlied, corbet, daniel, dri-devel, hamohammed.sa, linux-doc,
	linux-kernel, maarten.lankhorst, mairacanal, melissa.srw,
	mripard, rodrigosiqueiramelo, tzimmermann

Any progress on this?  Is it ok if yixie@chromium.org and I do the
followups on this patch so that we can also submit the Hotplug patch I
wrote (that's now archived?).



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH] Initial backport of vkms changes from 6.4, including jshargo and brpols configs changes
  2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
                   ` (6 preceding siblings ...)
  2023-08-08  2:42 ` Brandon Pollack
@ 2023-08-08  2:42 ` Brandon Pollack
  7 siblings, 0 replies; 21+ messages in thread
From: Brandon Pollack @ 2023-08-08  2:42 UTC (permalink / raw)
  To: jshargo
  Cc: airlied, corbet, daniel, dri-devel, hamohammed.sa, linux-doc,
	linux-kernel, maarten.lankhorst, mairacanal, melissa.srw,
	mripard, rodrigosiqueiramelo, tzimmermann, Brandon Pollack

WIP: Need to run all tast criticals and test the multidisplay tests that
are WIP.

BUG=b:283357160
TEST=Booted on a betty-arc-r device and ran autologin.py -a
Change-Id: I13cef8cf019744813f51cfffed3d7ccb987834e8

Change-Id: Iae7d788bc4725dfdca044204fa1af27a5a1ec5a8
---
 drivers/gpu/drm/vkms/Makefile         |   1 +
 drivers/gpu/drm/vkms/vkms_composer.c  |  74 ++-
 drivers/gpu/drm/vkms/vkms_configfs.c  | 719 ++++++++++++++++++++++++++
 drivers/gpu/drm/vkms/vkms_crtc.c      |  98 ++--
 drivers/gpu/drm/vkms/vkms_drv.c       | 227 ++++----
 drivers/gpu/drm/vkms/vkms_drv.h       | 191 +++++--
 drivers/gpu/drm/vkms/vkms_formats.c   | 298 +++++------
 drivers/gpu/drm/vkms/vkms_formats.h   |   4 +-
 drivers/gpu/drm/vkms/vkms_output.c    | 351 +++++++++++--
 drivers/gpu/drm/vkms/vkms_plane.c     | 102 ++--
 drivers/gpu/drm/vkms/vkms_writeback.c |  33 +-
 include/drm/drm_fixed.h               |   7 +
 12 files changed, 1638 insertions(+), 467 deletions(-)
 create mode 100644 drivers/gpu/drm/vkms/vkms_configfs.c

diff --git a/drivers/gpu/drm/vkms/Makefile b/drivers/gpu/drm/vkms/Makefile
index 1b28a6a32948..6b83907ad554 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_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c
index 8e53fa80742b..61061a277fca 100644
--- a/drivers/gpu/drm/vkms/vkms_composer.c
+++ b/drivers/gpu/drm/vkms/vkms_composer.c
@@ -4,6 +4,7 @@
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_vblank.h>
@@ -22,7 +23,7 @@ static u16 pre_mul_blend_channel(u16 src, u16 dst, u16 alpha)
 
 /**
  * pre_mul_alpha_blend - alpha blending equation
- * @src_frame_info: source framebuffer's metadata
+ * @frame_info: Source framebuffer's metadata
  * @stage_buffer: The line with the pixels from src_plane
  * @output_buffer: A line buffer that receives all the blends output
  *
@@ -53,10 +54,30 @@ static void pre_mul_alpha_blend(struct vkms_frame_info *frame_info,
 	}
 }
 
-static bool check_y_limit(struct vkms_frame_info *frame_info, int y)
+static int get_y_pos(struct vkms_frame_info *frame_info, int y)
 {
-	if (y >= frame_info->dst.y1 && y < frame_info->dst.y2)
-		return true;
+	if (frame_info->rotation & DRM_MODE_REFLECT_Y)
+		return drm_rect_height(&frame_info->rotated) - y - 1;
+
+	switch (frame_info->rotation & DRM_MODE_ROTATE_MASK) {
+	case DRM_MODE_ROTATE_90:
+		return frame_info->rotated.x2 - y - 1;
+	case DRM_MODE_ROTATE_270:
+		return y + frame_info->rotated.x1;
+	default:
+		return y;
+	}
+}
+
+static bool check_limit(struct vkms_frame_info *frame_info, int pos)
+{
+	if (drm_rotation_90_or_270(frame_info->rotation)) {
+		if (pos >= 0 && pos < drm_rect_width(&frame_info->rotated))
+			return true;
+	} else {
+		if (pos >= frame_info->rotated.y1 && pos < frame_info->rotated.y2)
+			return true;
+	}
 
 	return false;
 }
@@ -69,11 +90,13 @@ static void fill_background(const struct pixel_argb_u16 *background_color,
 }
 
 /**
- * @wb_frame_info: The writeback frame buffer metadata
+ * blend - blend the pixels from all planes and compute crc
+ * @wb: The writeback frame buffer metadata
  * @crtc_state: The crtc state
  * @crc32: The crc output of the final frame
  * @output_buffer: A buffer of a row that will receive the result of the blend(s)
  * @stage_buffer: The line with the pixels from plane being blend to the output
+ * @row_size: The size, in bytes, of a single row
  *
  * This function blends the pixels (Using the `pre_mul_alpha_blend`)
  * from all planes, calculates the crc32 of the output from the former step,
@@ -86,6 +109,7 @@ static void blend(struct vkms_writeback_job *wb,
 {
 	struct vkms_plane_state **plane = crtc_state->active_planes;
 	u32 n_active_planes = crtc_state->num_active_planes;
+	int y_pos;
 
 	const struct pixel_argb_u16 background_color = { .a = 0xffff };
 
@@ -96,10 +120,12 @@ static void blend(struct vkms_writeback_job *wb,
 
 		/* The active planes are composed associatively in z-order. */
 		for (size_t i = 0; i < n_active_planes; i++) {
-			if (!check_y_limit(plane[i]->frame_info, y))
+			y_pos = get_y_pos(plane[i]->frame_info, y);
+
+			if (!check_limit(plane[i]->frame_info, y_pos))
 				continue;
 
-			plane[i]->plane_read(stage_buffer, plane[i]->frame_info, y);
+			vkms_compose_row(stage_buffer, plane[i], y_pos);
 			pre_mul_alpha_blend(plane[i]->frame_info, stage_buffer,
 					    output_buffer);
 		}
@@ -107,7 +133,7 @@ static void blend(struct vkms_writeback_job *wb,
 		*crc32 = crc32_le(*crc32, (void *)output_buffer->pixels, row_size);
 
 		if (wb)
-			wb->wb_write(&wb->wb_frame_info, output_buffer, y);
+			vkms_writeback_row(wb, output_buffer, y_pos);
 	}
 }
 
@@ -118,10 +144,10 @@ static int check_format_funcs(struct vkms_crtc_state *crtc_state,
 	u32 n_active_planes = crtc_state->num_active_planes;
 
 	for (size_t i = 0; i < n_active_planes; i++)
-		if (!planes[i]->plane_read)
+		if (!planes[i]->pixel_read)
 			return -1;
 
-	if (active_wb && !active_wb->wb_write)
+	if (active_wb && !active_wb->pixel_write)
 		return -1;
 
 	return 0;
@@ -204,13 +230,13 @@ void vkms_composer_worker(struct work_struct *work)
 						composer_work);
 	struct drm_crtc *crtc = crtc_state->base.crtc;
 	struct vkms_writeback_job *active_wb = crtc_state->active_writeback;
-	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 	bool crc_pending, wb_pending;
 	u64 frame_start, frame_end;
 	u32 crc32 = 0;
 	int ret;
 
-	spin_lock_irq(&out->composer_lock);
+	spin_lock_irq(&vkms_crtc->composer_lock);
 	frame_start = crtc_state->frame_start;
 	frame_end = crtc_state->frame_end;
 	crc_pending = crtc_state->crc_pending;
@@ -218,7 +244,7 @@ void vkms_composer_worker(struct work_struct *work)
 	crtc_state->frame_start = 0;
 	crtc_state->frame_end = 0;
 	crtc_state->crc_pending = false;
-	spin_unlock_irq(&out->composer_lock);
+	spin_unlock_irq(&vkms_crtc->composer_lock);
 
 	/*
 	 * We raced with the vblank hrtimer and previous work already computed
@@ -236,10 +262,10 @@ void vkms_composer_worker(struct work_struct *work)
 		return;
 
 	if (wb_pending) {
-		drm_writeback_signal_completion(&out->wb_connector, 0);
-		spin_lock_irq(&out->composer_lock);
+		drm_writeback_signal_completion(&vkms_crtc->wb_connector, 0);
+		spin_lock_irq(&vkms_crtc->composer_lock);
 		crtc_state->wb_pending = false;
-		spin_unlock_irq(&out->composer_lock);
+		spin_unlock_irq(&vkms_crtc->composer_lock);
 	}
 
 	/*
@@ -289,25 +315,25 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *src_name,
 	return 0;
 }
 
-void vkms_set_composer(struct vkms_output *out, bool enabled)
+void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled)
 {
 	bool old_enabled;
 
 	if (enabled)
-		drm_crtc_vblank_get(&out->crtc);
+		drm_crtc_vblank_get(&vkms_crtc->base);
 
-	spin_lock_irq(&out->lock);
-	old_enabled = out->composer_enabled;
-	out->composer_enabled = enabled;
-	spin_unlock_irq(&out->lock);
+	spin_lock_irq(&vkms_crtc->lock);
+	old_enabled = vkms_crtc->composer_enabled;
+	vkms_crtc->composer_enabled = enabled;
+	spin_unlock_irq(&vkms_crtc->lock);
 
 	if (old_enabled)
-		drm_crtc_vblank_put(&out->crtc);
+		drm_crtc_vblank_put(&vkms_crtc->base);
 }
 
 int vkms_set_crc_source(struct drm_crtc *crtc, const char *src_name)
 {
-	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *out = drm_crtc_to_vkms_crtc(crtc);
 	bool enabled = false;
 	int ret = 0;
 
diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
new file mode 100644
index 000000000000..a1ac7716ba1a
--- /dev/null
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -0,0 +1,719 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include "drm/drm_probe_helper.h"
+#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 in addition to an
+ * immutable "default" device created by the driver at initialization time. Note
+ * that the default device 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
+ *       |-- default
+ *       |   `-- enabled
+ *       `-- test
+ *           |-- connectors
+ *                `-- connected
+ *           |-- 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/planes/cursor
+ *   echo $DRM_PLANE_TYPE_CURSOR > /config/vkms/test/planes/cursor/type
+ *
+ *   mkdir /config/vkms/test/planes/overlay
+ *   echo $DRM_PLANE_TYPE_OVERLAY > /config/vkms/test/planes/overlay/type
+ *
+ *   mkdir /config/vkms/test/crtcs/crtc
+ *   mkdir /config/vkms/test/crtcs/crtc_other
+ *   mkdir /config/vkms/test/encoders/encoder
+ *   mkdir /config/vkms/test/connectors/connector
+ *
+ * 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/crtcs/crtc /config/vkms/test/encoders/encoder/possible_crtcs/
+ *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/primary/possible_crtcs/
+ *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/cursor/possible_crtcs/
+ *   ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/overlay/possible_crtcs/
+ *
+ *   ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/overlay/possible_crtcs/
+ *   ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/other_primary/possible_crtcs/
+ *
+ * Finally, to enable your configured device, just write 1 to the ``enabled``
+ * file::
+ *
+ *   echo 1 > /config/vkms/test/enabled
+ *
+ * By default no display is "connected" so to connect a connector you'll also
+ * have to write 1 to a connectors "connected" attribute::
+ *
+ *   echo 1 > /config/vkms/test/connectors/connector/connected
+ *
+ * One can verify that this is worked using the `modetest` utility or the
+ * equivalent for your platform.
+ *
+ * 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/connectors/connector
+ *
+ *   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 ssize_t connector_connected_show(struct config_item *item, char *buf)
+{
+	struct vkms_config_connector *connector =
+		item_to_config_connector(item);
+	struct vkms_configfs *configfs = connector_item_to_configfs(item);
+	bool connected = false;
+
+	mutex_lock(&configfs->lock);
+	connected = connector->connected;
+	mutex_unlock(&configfs->lock);
+
+	return sprintf(buf, "%d\n", connected);
+}
+
+static ssize_t connector_connected_store(struct config_item *item,
+					 const char *buf, size_t len)
+{
+	struct vkms_config_connector *connector =
+		item_to_config_connector(item);
+	struct vkms_configfs *configfs = connector_item_to_configfs(item);
+	int val, ret;
+
+	ret = kstrtouint(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val != 1 && val != 0)
+		return -EINVAL;
+
+	mutex_lock(&configfs->lock);
+	connector->connected = val;
+	if (!connector->connector) {
+		pr_info("VKMS Device %s is not yet enabled, connector will be enabled on start",
+			configfs->device_group.cg_item.ci_name);
+	}
+	mutex_unlock(&configfs->lock);
+
+	if (connector->connector)
+		drm_kms_helper_hotplug_event(connector->connector->dev);
+
+	return len;
+}
+
+CONFIGFS_ATTR(connector_, connected);
+
+static struct configfs_attribute *connector_attrs[] = {
+	&connector_attr_connected,
+	NULL,
+};
+
+static struct config_item_type connector_type = {
+	.ct_attrs = connector_attrs,
+	.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\n", 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);
+	connector->connected = false;
+
+	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\n", 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 enabled, ret;
+
+	ret = kstrtoint(buf, 0, &enabled);
+	if (ret)
+		return ret;
+
+	if (enabled == 0) {
+		mutex_lock(&configfs->lock);
+		if (configfs->vkms_device) {
+			vkms_remove_device(configfs->vkms_device);
+			configfs->vkms_device = NULL;
+		}
+		mutex_unlock(&configfs->lock);
+
+		return len;
+	}
+
+	if (enabled == 1) {
+		mutex_lock(&configfs->lock);
+		if (!configfs->vkms_device) {
+			device = vkms_add_device(configfs);
+			if (IS_ERR(device)) {
+				mutex_unlock(&configfs->lock);
+				return -PTR_ERR(device);
+			}
+
+			configfs->vkms_device = device;
+		}
+		mutex_unlock(&configfs->lock);
+
+		return len;
+	}
+
+	return -EINVAL;
+}
+
+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\n", 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_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
index 57bbd32e9beb..5ebb5264f6ef 100644
--- a/drivers/gpu/drm/vkms/vkms_crtc.c
+++ b/drivers/gpu/drm/vkms/vkms_crtc.c
@@ -11,35 +11,34 @@
 
 static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
 {
-	struct vkms_output *output = container_of(timer, struct vkms_output,
-						  vblank_hrtimer);
-	struct drm_crtc *crtc = &output->crtc;
+	struct vkms_crtc *vkms_crtc = timer_to_vkms_crtc(timer);
+	struct drm_crtc *crtc = &vkms_crtc->base;
 	struct vkms_crtc_state *state;
 	u64 ret_overrun;
 	bool ret, fence_cookie;
 
 	fence_cookie = dma_fence_begin_signalling();
 
-	ret_overrun = hrtimer_forward_now(&output->vblank_hrtimer,
-					  output->period_ns);
+	ret_overrun = hrtimer_forward_now(&vkms_crtc->vblank_hrtimer,
+					  vkms_crtc->period_ns);
 	if (ret_overrun != 1)
 		pr_warn("%s: vblank timer overrun\n", __func__);
 
-	spin_lock(&output->lock);
+	spin_lock(&vkms_crtc->lock);
 	ret = drm_crtc_handle_vblank(crtc);
 	if (!ret)
 		DRM_ERROR("vkms failure on handling vblank");
 
-	state = output->composer_state;
-	spin_unlock(&output->lock);
+	state = vkms_crtc->composer_state;
+	spin_unlock(&vkms_crtc->lock);
 
-	if (state && output->composer_enabled) {
+	if (state && vkms_crtc->composer_enabled) {
 		u64 frame = drm_crtc_accurate_vblank_count(crtc);
 
 		/* update frame_start only if a queued vkms_composer_worker()
 		 * has read the data
 		 */
-		spin_lock(&output->composer_lock);
+		spin_lock(&vkms_crtc->composer_lock);
 		if (!state->crc_pending)
 			state->frame_start = frame;
 		else
@@ -47,9 +46,10 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
 					 state->frame_start, frame);
 		state->frame_end = frame;
 		state->crc_pending = true;
-		spin_unlock(&output->composer_lock);
+		spin_unlock(&vkms_crtc->composer_lock);
 
-		ret = queue_work(output->composer_workq, &state->composer_work);
+		ret = queue_work(vkms_crtc->composer_workq,
+				 &state->composer_work);
 		if (!ret)
 			DRM_DEBUG_DRIVER("Composer worker already queued\n");
 	}
@@ -62,25 +62,27 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
 static int vkms_enable_vblank(struct drm_crtc *crtc)
 {
 	struct drm_device *dev = crtc->dev;
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 	unsigned int pipe = drm_crtc_index(crtc);
 	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
-	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
 
 	drm_calc_timestamping_constants(crtc, &crtc->mode);
 
-	hrtimer_init(&out->vblank_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
-	out->vblank_hrtimer.function = &vkms_vblank_simulate;
-	out->period_ns = ktime_set(0, vblank->framedur_ns);
-	hrtimer_start(&out->vblank_hrtimer, out->period_ns, HRTIMER_MODE_REL);
+	hrtimer_init(&vkms_crtc->vblank_hrtimer, CLOCK_MONOTONIC,
+		     HRTIMER_MODE_REL);
+	vkms_crtc->vblank_hrtimer.function = &vkms_vblank_simulate;
+	vkms_crtc->period_ns = ktime_set(0, vblank->framedur_ns);
+	hrtimer_start(&vkms_crtc->vblank_hrtimer, vkms_crtc->period_ns,
+		      HRTIMER_MODE_REL);
 
 	return 0;
 }
 
 static void vkms_disable_vblank(struct drm_crtc *crtc)
 {
-	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 
-	hrtimer_cancel(&out->vblank_hrtimer);
+	hrtimer_cancel(&vkms_crtc->vblank_hrtimer);
 }
 
 static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
@@ -88,9 +90,8 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
 				      bool in_vblank_irq)
 {
 	struct drm_device *dev = crtc->dev;
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 	unsigned int pipe = crtc->index;
-	struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
-	struct vkms_output *output = &vkmsdev->output;
 	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
 
 	if (!READ_ONCE(vblank->enabled)) {
@@ -98,7 +99,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
 		return true;
 	}
 
-	*vblank_time = READ_ONCE(output->vblank_hrtimer.node.expires);
+	*vblank_time = READ_ONCE(vkms_crtc->vblank_hrtimer.node.expires);
 
 	if (WARN_ON(*vblank_time == vblank->time))
 		return true;
@@ -110,7 +111,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
 	 * the vblank core expects. Therefore we need to always correct the
 	 * timestampe by one frame.
 	 */
-	*vblank_time -= output->period_ns;
+	*vblank_time -= vkms_crtc->period_ns;
 
 	return true;
 }
@@ -161,7 +162,6 @@ static void vkms_atomic_crtc_reset(struct drm_crtc *crtc)
 
 static const struct drm_crtc_funcs vkms_crtc_funcs = {
 	.set_config             = drm_atomic_helper_set_config,
-	.destroy                = drm_crtc_cleanup,
 	.page_flip              = drm_atomic_helper_page_flip,
 	.reset                  = vkms_atomic_crtc_reset,
 	.atomic_duplicate_state = vkms_atomic_crtc_duplicate_state,
@@ -237,18 +237,18 @@ static void vkms_crtc_atomic_disable(struct drm_crtc *crtc,
 static void vkms_crtc_atomic_begin(struct drm_crtc *crtc,
 				   struct drm_atomic_state *state)
 {
-	struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 
 	/* This lock is held across the atomic commit to block vblank timer
 	 * from scheduling vkms_composer_worker until the composer is updated
 	 */
-	spin_lock_irq(&vkms_output->lock);
+	spin_lock_irq(&vkms_crtc->lock);
 }
 
 static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
 				   struct drm_atomic_state *state)
 {
-	struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
+	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
 
 	if (crtc->state->event) {
 		spin_lock(&crtc->dev->event_lock);
@@ -263,9 +263,9 @@ static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
 		crtc->state->event = NULL;
 	}
 
-	vkms_output->composer_state = to_vkms_crtc_state(crtc->state);
+	vkms_crtc->composer_state = to_vkms_crtc_state(crtc->state);
 
-	spin_unlock_irq(&vkms_output->lock);
+	spin_unlock_irq(&vkms_crtc->lock);
 }
 
 static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
@@ -276,27 +276,41 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
 	.atomic_disable	= vkms_crtc_atomic_disable,
 };
 
-int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
-		   struct drm_plane *primary, struct drm_plane *cursor)
+struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
+				 struct drm_plane *primary,
+				 struct drm_plane *cursor, const char *name)
 {
-	struct vkms_output *vkms_out = drm_crtc_to_vkms_output(crtc);
+	struct drm_device *dev = &vkmsdev->drm;
+	struct vkms_crtc *vkms_crtc;
 	int ret;
 
-	ret = drm_crtc_init_with_planes(dev, crtc, primary, cursor,
-					&vkms_crtc_funcs, NULL);
+	if (vkmsdev->output.num_crtcs >= VKMS_MAX_OUTPUT_OBJECTS) {
+		return ERR_PTR(-ENOMEM);
+	}
+	vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
+
+	ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, primary, cursor,
+					 &vkms_crtc_funcs, name);
 	if (ret) {
 		DRM_ERROR("Failed to init CRTC\n");
-		return ret;
+		goto out_error;
 	}
 
-	drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs);
+	drm_crtc_helper_add(&vkms_crtc->base, &vkms_crtc_helper_funcs);
 
-	spin_lock_init(&vkms_out->lock);
-	spin_lock_init(&vkms_out->composer_lock);
+	spin_lock_init(&vkms_crtc->lock);
+	spin_lock_init(&vkms_crtc->composer_lock);
 
-	vkms_out->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
-	if (!vkms_out->composer_workq)
-		return -ENOMEM;
+	vkms_crtc->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
+	if (!vkms_crtc->composer_workq) {
+		ret = -ENOMEM;
+		goto out_error;
+	}
+
+	return vkms_crtc;
 
-	return ret;
+out_error:
+	memset(vkms_crtc, 0, sizeof(*vkms_crtc));
+	vkmsdev->output.num_crtcs -= 1;
+	return ERR_PTR(ret);
 }
diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
index 69346906ec81..7e1ec7d89dcb 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -9,10 +9,15 @@
  * the GPU in DRM API tests.
  */
 
+#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>
 
+#include <drm/drm_device.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
@@ -37,19 +42,26 @@
 #define DRIVER_MAJOR	1
 #define DRIVER_MINOR	0
 
-static struct vkms_config *default_config;
+static bool enable_default_device = true;
+module_param_named(enable_default_device, enable_default_device, bool, 0444);
+MODULE_PARM_DESC(enable_default_device,
+		 "Enable/Disable creating the default device");
 
 static bool enable_cursor = true;
 module_param_named(enable_cursor, enable_cursor, bool, 0444);
-MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
+MODULE_PARM_DESC(enable_cursor,
+		 "Enable/Disable cursor support for the default device");
 
 static bool enable_writeback = true;
 module_param_named(enable_writeback, enable_writeback, bool, 0444);
-MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback connector support");
+MODULE_PARM_DESC(
+	enable_writeback,
+	"Enable/Disable writeback connector support for the default device");
 
 static bool enable_overlay;
 module_param_named(enable_overlay, enable_overlay, bool, 0444);
-MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
+MODULE_PARM_DESC(enable_overlay,
+		 "Enable/Disable overlay support for the default device");
 
 DEFINE_DRM_GEM_FOPS(vkms_driver_fops);
 
@@ -57,8 +69,8 @@ static void vkms_release(struct drm_device *dev)
 {
 	struct vkms_device *vkms = drm_device_to_vkms_device(dev);
 
-	if (vkms->output.composer_workq)
-		destroy_workqueue(vkms->output.composer_workq);
+	for (int i = 0; i < vkms->output.num_crtcs; i++)
+		destroy_workqueue(vkms->output.crtcs[i].composer_workq);
 }
 
 static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
@@ -90,37 +102,12 @@ static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
 	drm_atomic_helper_cleanup_planes(dev, old_state);
 }
 
-static int vkms_config_show(struct seq_file *m, void *data)
-{
-	struct drm_info_node *node = (struct drm_info_node *)m->private;
-	struct drm_device *dev = node->minor->dev;
-	struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
-
-	seq_printf(m, "writeback=%d\n", vkmsdev->config->writeback);
-	seq_printf(m, "cursor=%d\n", vkmsdev->config->cursor);
-	seq_printf(m, "overlay=%d\n", vkmsdev->config->overlay);
-
-	return 0;
-}
-
-static const struct drm_info_list vkms_config_debugfs_list[] = {
-	{ "vkms_config", vkms_config_show, 0 },
-};
-
-static void vkms_config_debugfs_init(struct drm_minor *minor)
-{
-	drm_debugfs_create_files(vkms_config_debugfs_list, ARRAY_SIZE(vkms_config_debugfs_list),
-				 minor->debugfs_root, minor);
-}
-
 static const struct drm_driver vkms_driver = {
 	.driver_features	= DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM,
 	.release		= vkms_release,
 	.fops			= &vkms_driver_fops,
 	DRM_GEM_SHMEM_DRIVER_OPS,
 
-	.debugfs_init           = vkms_config_debugfs_init,
-
 	.name			= DRIVER_NAME,
 	.desc			= DRIVER_DESC,
 	.date			= DRIVER_DATE,
@@ -141,8 +128,12 @@ static const struct drm_mode_config_helper_funcs vkms_mode_config_helpers = {
 static int vkms_modeset_init(struct vkms_device *vkmsdev)
 {
 	struct drm_device *dev = &vkmsdev->drm;
+	int ret;
+
+	ret = drmm_mode_config_init(dev);
+	if (ret)
+		return ret;
 
-	drm_mode_config_init(dev);
 	dev->mode_config.funcs = &vkms_mode_funcs;
 	dev->mode_config.min_width = XRES_MIN;
 	dev->mode_config.min_height = YRES_MIN;
@@ -156,114 +147,176 @@ 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 vkms_output_init(vkmsdev, 0);
+	return vkmsdev->configfs ? vkms_output_init(vkmsdev) :
+				   vkms_output_init_default(vkmsdev);
 }
 
-static int vkms_create(struct vkms_config *config)
+static int vkms_platform_probe(struct platform_device *pdev)
 {
 	int ret;
-	struct platform_device *pdev;
+	struct vkms_device_setup *vkms_device_setup = pdev->dev.platform_data;
 	struct vkms_device *vkms_device;
+	void *grp;
 
-	pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
-	if (IS_ERR(pdev))
-		return PTR_ERR(pdev);
-
-	if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) {
-		ret = -ENOMEM;
-		goto out_unregister;
+	grp = devres_open_group(&pdev->dev, NULL, GFP_KERNEL);
+	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);
 	if (IS_ERR(vkms_device)) {
 		ret = PTR_ERR(vkms_device);
-		goto out_devres;
+		goto out_release_group;
 	}
+
 	vkms_device->platform = pdev;
-	vkms_device->config = config;
-	config->dev = vkms_device;
+	vkms_device->config.cursor = enable_cursor;
+	vkms_device->config.writeback = enable_writeback;
+	vkms_device->config.overlay = enable_overlay;
+	vkms_device->configfs = vkms_device_setup->configfs;
 
 	ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev,
 					   DMA_BIT_MASK(64));
-
 	if (ret) {
 		DRM_ERROR("Could not initialize DMA support\n");
-		goto out_devres;
+		goto out_release_group;
 	}
 
-	ret = drm_vblank_init(&vkms_device->drm, 1);
+	ret = vkms_modeset_init(vkms_device);
 	if (ret) {
-		DRM_ERROR("Failed to vblank\n");
-		goto out_devres;
+		DRM_ERROR("Unable to initialize modesetting\n");
+		goto out_release_group;
 	}
 
-	ret = vkms_modeset_init(vkms_device);
-	if (ret)
-		goto out_devres;
+	ret = drm_vblank_init(&vkms_device->drm, vkms_device->output.num_crtcs);
+	if (ret) {
+		DRM_ERROR("Failed to vblank\n");
+		goto out_release_group;
+	}
 
 	ret = drm_dev_register(&vkms_device->drm, 0);
-	if (ret)
-		goto out_devres;
+	if (ret) {
+		DRM_ERROR("Unable to register device with id %d\n", pdev->id);
+		goto out_release_group;
+	}
 
 	drm_fbdev_generic_setup(&vkms_device->drm, 0);
+	platform_set_drvdata(pdev, vkms_device);
+	devres_close_group(&pdev->dev, grp);
 
 	return 0;
 
-out_devres:
-	devres_release_group(&pdev->dev, NULL);
-out_unregister:
-	platform_device_unregister(pdev);
+out_release_group:
+	devres_release_group(&pdev->dev, grp);
 	return ret;
 }
 
-static int __init vkms_init(void)
+static int vkms_platform_remove(struct platform_device *pdev)
 {
-	int ret;
-	struct vkms_config *config;
+	struct vkms_device *vkms_device;
 
-	config = kmalloc(sizeof(*config), GFP_KERNEL);
-	if (!config)
-		return -ENOMEM;
+	vkms_device = platform_get_drvdata(pdev);
+	if (!vkms_device)
+		return 0;
 
-	default_config = config;
+	drm_dev_unregister(&vkms_device->drm);
+	drm_atomic_helper_shutdown(&vkms_device->drm);
+	return 0;
+}
 
-	config->cursor = enable_cursor;
-	config->writeback = enable_writeback;
-	config->overlay = enable_overlay;
+static struct platform_driver vkms_platform_driver = {
+	.probe = vkms_platform_probe,
+	.remove = vkms_platform_remove,
+	.driver.name = DRIVER_NAME,
+};
 
-	ret = vkms_create(config);
-	if (ret)
-		kfree(config);
+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);
+		put_device(dev);
+	}
 
-	return ret;
+	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);
 }
 
-static void vkms_destroy(struct vkms_config *config)
+void vkms_remove_device(struct vkms_device *vkms_device)
 {
-	struct platform_device *pdev;
+	platform_device_unregister(vkms_device->platform);
+}
+
+static int __init vkms_init(void)
+{
+	int ret;
+	struct platform_device *default_pdev = NULL;
 
-	if (!config->dev) {
-		DRM_INFO("vkms_device is NULL.\n");
-		return;
+	ret = platform_driver_register(&vkms_platform_driver);
+	if (ret) {
+		DRM_ERROR("Unable to register platform driver\n");
+		return ret;
 	}
 
-	pdev = config->dev->platform;
+	if (enable_default_device) {
+		struct vkms_device_setup vkms_device_setup = {
+			.configfs = NULL,
+		};
+
+		default_pdev = platform_device_register_data(
+			NULL, DRIVER_NAME, 0, &vkms_device_setup,
+			sizeof(vkms_device_setup));
+		if (IS_ERR(default_pdev)) {
+			DRM_ERROR("Unable to register default vkms device\n");
+			platform_driver_unregister(&vkms_platform_driver);
+			return PTR_ERR(default_pdev);
+		}
+	}
 
-	drm_dev_unregister(&config->dev->drm);
-	drm_atomic_helper_shutdown(&config->dev->drm);
-	devres_release_group(&pdev->dev, NULL);
-	platform_device_unregister(pdev);
+	ret = vkms_init_configfs();
+	if (ret) {
+		DRM_ERROR("Unable to initialize configfs\n");
+		if (default_pdev)
+			platform_device_unregister(default_pdev);
+
+		platform_driver_unregister(&vkms_platform_driver);
+	}
 
-	config->dev = NULL;
+	return 0;
 }
 
 static void __exit vkms_exit(void)
 {
-	if (default_config->dev)
-		vkms_destroy(default_config);
+	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
+		// it so we don't leak memory.
+		put_device(dev);
+		platform_device_unregister(to_platform_device(dev));
+	}
 
-	kfree(default_config);
+	platform_driver_unregister(&vkms_platform_driver);
 }
 
 module_init(vkms_init);
diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
index 0a67b8073f7e..7b9b189511c2 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.h
+++ b/drivers/gpu/drm/vkms/vkms_drv.h
@@ -3,6 +3,8 @@
 #ifndef _VKMS_DRV_H_
 #define _VKMS_DRV_H_
 
+#include "drm/drm_connector.h"
+#include <linux/configfs.h>
 #include <linux/hrtimer.h>
 
 #include <drm/drm.h>
@@ -10,10 +12,11 @@
 #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    20
-#define YRES_MIN    20
+#define XRES_MIN    10
+#define YRES_MIN    10
 
 #define XRES_DEF  1024
 #define YRES_DEF   768
@@ -23,10 +26,15 @@
 
 #define NUM_OVERLAY_PLANES 8
 
+#define VKMS_MAX_OUTPUT_OBJECTS 16
+#define VKMS_MAX_PLANES (3 * VKMS_MAX_OUTPUT_OBJECTS)
+
 struct vkms_frame_info {
 	struct drm_framebuffer *fb;
 	struct drm_rect src, dst;
+	struct drm_rect rotated;
 	struct iosys_map map[DRM_FORMAT_MAX_PLANES];
+	unsigned int rotation;
 	unsigned int offset;
 	unsigned int pitch;
 	unsigned int cpp;
@@ -44,8 +52,7 @@ struct line_buffer {
 struct vkms_writeback_job {
 	struct iosys_map data[DRM_FORMAT_MAX_PLANES];
 	struct vkms_frame_info wb_frame_info;
-	void (*wb_write)(struct vkms_frame_info *frame_info,
-			 const struct line_buffer *buffer, int y);
+	void (*pixel_write)(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel);
 };
 
 /**
@@ -56,14 +63,32 @@ struct vkms_writeback_job {
 struct vkms_plane_state {
 	struct drm_shadow_plane_state base;
 	struct vkms_frame_info *frame_info;
-	void (*plane_read)(struct line_buffer *buffer,
-			   const struct vkms_frame_info *frame_info, int y);
+	void (*pixel_read)(u8 *src_buffer, struct pixel_argb_u16 *out_pixel);
 };
 
 struct vkms_plane {
 	struct drm_plane base;
 };
 
+struct vkms_crtc {
+	struct drm_crtc base;
+
+	struct drm_writeback_connector wb_connector;
+	struct hrtimer vblank_hrtimer;
+	ktime_t period_ns;
+	struct drm_pending_vblank_event *event;
+	/* ordered wq for composer_work */
+	struct workqueue_struct *composer_workq;
+	/* protects concurrent access to composer */
+	spinlock_t lock;
+
+	/* protected by @lock */
+	bool composer_enabled;
+	struct vkms_crtc_state *composer_state;
+
+	spinlock_t composer_lock;
+};
+
 /**
  * vkms_crtc_state - Driver specific CRTC state
  * @base: base CRTC state
@@ -88,62 +113,145 @@ struct vkms_crtc_state {
 };
 
 struct vkms_output {
-	struct drm_crtc crtc;
-	struct drm_encoder encoder;
-	struct drm_connector connector;
-	struct drm_writeback_connector wb_connector;
-	struct hrtimer vblank_hrtimer;
-	ktime_t period_ns;
-	struct drm_pending_vblank_event *event;
-	/* ordered wq for composer_work */
-	struct workqueue_struct *composer_workq;
-	/* protects concurrent access to composer */
-	spinlock_t lock;
-
-	/* protected by @lock */
-	bool composer_enabled;
-	struct vkms_crtc_state *composer_state;
-
-	spinlock_t composer_lock;
+	int num_crtcs;
+	struct vkms_crtc crtcs[VKMS_MAX_OUTPUT_OBJECTS];
+	int num_encoders;
+	struct drm_encoder encoders[VKMS_MAX_OUTPUT_OBJECTS];
+	int num_connectors;
+	struct drm_connector connectors[VKMS_MAX_OUTPUT_OBJECTS];
+	int num_planes;
+	struct vkms_plane planes[VKMS_MAX_PLANES];
 };
 
-struct vkms_device;
-
 struct vkms_config {
 	bool writeback;
 	bool cursor;
 	bool overlay;
-	/* only set when instantiated */
-	struct vkms_device *dev;
+};
+
+struct vkms_config_links {
+	struct config_group group;
+	unsigned long linked_object_bitmap;
+};
+
+struct vkms_config_connector {
+	struct config_group config_group;
+	struct drm_connector *connector;
+	struct vkms_config_links possible_encoders;
+	bool connected;
+};
+
+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 {
+	// Is NULL in the case of the default card.
+	struct vkms_configfs *configfs;
 };
 
 struct vkms_device {
 	struct drm_device drm;
 	struct platform_device *platform;
+	// Is NULL in the case of the default card.
+	struct vkms_configfs *configfs;
 	struct vkms_output output;
-	const struct vkms_config *config;
+	struct vkms_config config;
 };
 
-#define drm_crtc_to_vkms_output(target) \
-	container_of(target, struct vkms_output, crtc)
+#define drm_crtc_to_vkms_crtc(crtc) container_of(crtc, struct vkms_crtc, base)
 
 #define drm_device_to_vkms_device(target) \
 	container_of(target, struct vkms_device, drm)
 
+#define timer_to_vkms_crtc(timer) \
+	container_of(timer, struct vkms_crtc, vblank_hrtimer)
+
 #define to_vkms_crtc_state(target)\
 	container_of(target, struct vkms_crtc_state, base)
 
 #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 connector_item_to_configfs(item)                                     \
+	container_of(to_config_group(item->ci_parent), struct vkms_configfs, \
+		     connectors_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 */
-int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
-		   struct drm_plane *primary, struct drm_plane *cursor);
+struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
+				 struct drm_plane *primary,
+				 struct drm_plane *cursor, const char *name);
 
-int vkms_output_init(struct vkms_device *vkmsdev, int index);
+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,
-				   enum drm_plane_type type, int index);
+				   enum drm_plane_type type);
 
 /* CRC Support */
 const char *const *vkms_get_crc_sources(struct drm_crtc *crtc,
@@ -154,9 +262,20 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
 
 /* Composer Support */
 void vkms_composer_worker(struct work_struct *work);
-void vkms_set_composer(struct vkms_output *out, bool enabled);
+void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled);
+void vkms_compose_row(struct line_buffer *stage_buffer, struct vkms_plane_state *plane, int y);
+void vkms_writeback_row(struct vkms_writeback_job *wb, const struct line_buffer *src_buffer, int y);
 
 /* Writeback */
-int vkms_enable_writeback_connector(struct vkms_device *vkmsdev);
+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);
+
+/* Connector hotplugging */
+enum drm_connector_status vkms_connector_detect(struct drm_connector *connector,
+						bool force);
 
 #endif /* _VKMS_DRV_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_formats.c b/drivers/gpu/drm/vkms/vkms_formats.c
index d4950688b3f1..36046b12f296 100644
--- a/drivers/gpu/drm/vkms/vkms_formats.c
+++ b/drivers/gpu/drm/vkms/vkms_formats.c
@@ -2,6 +2,8 @@
 
 #include <linux/kernel.h>
 #include <linux/minmax.h>
+
+#include <drm/drm_blend.h>
 #include <drm/drm_rect.h>
 #include <drm/drm_fixed.h>
 
@@ -37,104 +39,106 @@ static void *packed_pixels_addr(const struct vkms_frame_info *frame_info,
 static void *get_packed_src_addr(const struct vkms_frame_info *frame_info, int y)
 {
 	int x_src = frame_info->src.x1 >> 16;
-	int y_src = y - frame_info->dst.y1 + (frame_info->src.y1 >> 16);
+	int y_src = y - frame_info->rotated.y1 + (frame_info->src.y1 >> 16);
 
 	return packed_pixels_addr(frame_info, x_src, y_src);
 }
 
-static void ARGB8888_to_argb_u16(struct line_buffer *stage_buffer,
-				 const struct vkms_frame_info *frame_info, int y)
+static int get_x_position(const struct vkms_frame_info *frame_info, int limit, int x)
 {
-	struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
-	u8 *src_pixels = get_packed_src_addr(frame_info, y);
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    stage_buffer->n_pixels);
-
-	for (size_t x = 0; x < x_limit; x++, src_pixels += 4) {
-		/*
-		 * The 257 is the "conversion ratio". This number is obtained by the
-		 * (2^16 - 1) / (2^8 - 1) division. Which, in this case, tries to get
-		 * the best color value in a pixel format with more possibilities.
-		 * A similar idea applies to others RGB color conversions.
-		 */
-		out_pixels[x].a = (u16)src_pixels[3] * 257;
-		out_pixels[x].r = (u16)src_pixels[2] * 257;
-		out_pixels[x].g = (u16)src_pixels[1] * 257;
-		out_pixels[x].b = (u16)src_pixels[0] * 257;
-	}
+	if (frame_info->rotation & (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_270))
+		return limit - x - 1;
+	return x;
 }
 
-static void XRGB8888_to_argb_u16(struct line_buffer *stage_buffer,
-				 const struct vkms_frame_info *frame_info, int y)
+static void ARGB8888_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
 {
-	struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
-	u8 *src_pixels = get_packed_src_addr(frame_info, y);
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    stage_buffer->n_pixels);
-
-	for (size_t x = 0; x < x_limit; x++, src_pixels += 4) {
-		out_pixels[x].a = (u16)0xffff;
-		out_pixels[x].r = (u16)src_pixels[2] * 257;
-		out_pixels[x].g = (u16)src_pixels[1] * 257;
-		out_pixels[x].b = (u16)src_pixels[0] * 257;
-	}
+	/*
+	 * The 257 is the "conversion ratio". This number is obtained by the
+	 * (2^16 - 1) / (2^8 - 1) division. Which, in this case, tries to get
+	 * the best color value in a pixel format with more possibilities.
+	 * A similar idea applies to others RGB color conversions.
+	 */
+	out_pixel->a = (u16)src_pixels[3] * 257;
+	out_pixel->r = (u16)src_pixels[2] * 257;
+	out_pixel->g = (u16)src_pixels[1] * 257;
+	out_pixel->b = (u16)src_pixels[0] * 257;
 }
 
-static void ARGB16161616_to_argb_u16(struct line_buffer *stage_buffer,
-				     const struct vkms_frame_info *frame_info,
-				     int y)
+static void XRGB8888_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
 {
-	struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
-	u16 *src_pixels = get_packed_src_addr(frame_info, y);
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    stage_buffer->n_pixels);
-
-	for (size_t x = 0; x < x_limit; x++, src_pixels += 4) {
-		out_pixels[x].a = le16_to_cpu(src_pixels[3]);
-		out_pixels[x].r = le16_to_cpu(src_pixels[2]);
-		out_pixels[x].g = le16_to_cpu(src_pixels[1]);
-		out_pixels[x].b = le16_to_cpu(src_pixels[0]);
-	}
+	out_pixel->a = (u16)0xffff;
+	out_pixel->r = (u16)src_pixels[2] * 257;
+	out_pixel->g = (u16)src_pixels[1] * 257;
+	out_pixel->b = (u16)src_pixels[0] * 257;
 }
 
-static void XRGB16161616_to_argb_u16(struct line_buffer *stage_buffer,
-				     const struct vkms_frame_info *frame_info,
-				     int y)
+static void ARGB16161616_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
 {
-	struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
-	u16 *src_pixels = get_packed_src_addr(frame_info, y);
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    stage_buffer->n_pixels);
-
-	for (size_t x = 0; x < x_limit; x++, src_pixels += 4) {
-		out_pixels[x].a = (u16)0xffff;
-		out_pixels[x].r = le16_to_cpu(src_pixels[2]);
-		out_pixels[x].g = le16_to_cpu(src_pixels[1]);
-		out_pixels[x].b = le16_to_cpu(src_pixels[0]);
-	}
+	u16 *pixels = (u16 *)src_pixels;
+
+	out_pixel->a = le16_to_cpu(pixels[3]);
+	out_pixel->r = le16_to_cpu(pixels[2]);
+	out_pixel->g = le16_to_cpu(pixels[1]);
+	out_pixel->b = le16_to_cpu(pixels[0]);
 }
 
-static void RGB565_to_argb_u16(struct line_buffer *stage_buffer,
-			       const struct vkms_frame_info *frame_info, int y)
+static void XRGB16161616_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
 {
-	struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
-	u16 *src_pixels = get_packed_src_addr(frame_info, y);
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			       stage_buffer->n_pixels);
+	u16 *pixels = (u16 *)src_pixels;
+
+	out_pixel->a = (u16)0xffff;
+	out_pixel->r = le16_to_cpu(pixels[2]);
+	out_pixel->g = le16_to_cpu(pixels[1]);
+	out_pixel->b = le16_to_cpu(pixels[0]);
+}
+
+static void RGB565_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
+{
+	u16 *pixels = (u16 *)src_pixels;
 
 	s64 fp_rb_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(31));
 	s64 fp_g_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(63));
 
-	for (size_t x = 0; x < x_limit; x++, src_pixels++) {
-		u16 rgb_565 = le16_to_cpu(*src_pixels);
-		s64 fp_r = drm_int2fixp((rgb_565 >> 11) & 0x1f);
-		s64 fp_g = drm_int2fixp((rgb_565 >> 5) & 0x3f);
-		s64 fp_b = drm_int2fixp(rgb_565 & 0x1f);
+	u16 rgb_565 = le16_to_cpu(*pixels);
+	s64 fp_r = drm_int2fixp((rgb_565 >> 11) & 0x1f);
+	s64 fp_g = drm_int2fixp((rgb_565 >> 5) & 0x3f);
+	s64 fp_b = drm_int2fixp(rgb_565 & 0x1f);
+
+	out_pixel->a = (u16)0xffff;
+	out_pixel->r = drm_fixp2int_round(drm_fixp_mul(fp_r, fp_rb_ratio));
+	out_pixel->g = drm_fixp2int_round(drm_fixp_mul(fp_g, fp_g_ratio));
+	out_pixel->b = drm_fixp2int_round(drm_fixp_mul(fp_b, fp_rb_ratio));
+}
+
+/**
+ * vkms_compose_row - compose a single row of a plane
+ * @stage_buffer: output line with the composed pixels
+ * @plane: state of the plane that is being composed
+ * @y: y coordinate of the row
+ *
+ * This function composes a single row of a plane. It gets the source pixels
+ * through the y coordinate (see get_packed_src_addr()) and goes linearly
+ * through the source pixel, reading the pixels and converting it to
+ * ARGB16161616 (see the pixel_read() callback). For rotate-90 and rotate-270,
+ * the source pixels are not traversed linearly. The source pixels are queried
+ * on each iteration in order to traverse the pixels vertically.
+ */
+void vkms_compose_row(struct line_buffer *stage_buffer, struct vkms_plane_state *plane, int y)
+{
+	struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
+	struct vkms_frame_info *frame_info = plane->frame_info;
+	u8 *src_pixels = get_packed_src_addr(frame_info, y);
+	int limit = min_t(size_t, drm_rect_width(&frame_info->dst), stage_buffer->n_pixels);
+
+	for (size_t x = 0; x < limit; x++, src_pixels += frame_info->cpp) {
+		int x_pos = get_x_position(frame_info, limit, x);
+
+		if (drm_rotation_90_or_270(frame_info->rotation))
+			src_pixels = get_packed_src_addr(frame_info, x + frame_info->rotated.y1)
+				+ frame_info->cpp * y;
 
-		out_pixels[x].a = (u16)0xffff;
-		out_pixels[x].r = drm_fixp2int(drm_fixp_mul(fp_r, fp_rb_ratio));
-		out_pixels[x].g = drm_fixp2int(drm_fixp_mul(fp_g, fp_g_ratio));
-		out_pixels[x].b = drm_fixp2int(drm_fixp_mul(fp_b, fp_rb_ratio));
+		plane->pixel_read(src_pixels, &out_pixels[x_pos]);
 	}
 }
 
@@ -146,110 +150,84 @@ static void RGB565_to_argb_u16(struct line_buffer *stage_buffer,
  * They are used in the `compose_active_planes` to convert and store a line
  * from the src_buffer to the writeback buffer.
  */
-static void argb_u16_to_ARGB8888(struct vkms_frame_info *frame_info,
-				 const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_ARGB8888(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
 {
-	int x_dst = frame_info->dst.x1;
-	u8 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
-	struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    src_buffer->n_pixels);
-
-	for (size_t x = 0; x < x_limit; x++, dst_pixels += 4) {
-		/*
-		 * This sequence below is important because the format's byte order is
-		 * in little-endian. In the case of the ARGB8888 the memory is
-		 * organized this way:
-		 *
-		 * | Addr     | = blue channel
-		 * | Addr + 1 | = green channel
-		 * | Addr + 2 | = Red channel
-		 * | Addr + 3 | = Alpha channel
-		 */
-		dst_pixels[3] = DIV_ROUND_CLOSEST(in_pixels[x].a, 257);
-		dst_pixels[2] = DIV_ROUND_CLOSEST(in_pixels[x].r, 257);
-		dst_pixels[1] = DIV_ROUND_CLOSEST(in_pixels[x].g, 257);
-		dst_pixels[0] = DIV_ROUND_CLOSEST(in_pixels[x].b, 257);
-	}
+	/*
+	 * This sequence below is important because the format's byte order is
+	 * in little-endian. In the case of the ARGB8888 the memory is
+	 * organized this way:
+	 *
+	 * | Addr     | = blue channel
+	 * | Addr + 1 | = green channel
+	 * | Addr + 2 | = Red channel
+	 * | Addr + 3 | = Alpha channel
+	 */
+	dst_pixels[3] = DIV_ROUND_CLOSEST(in_pixel->a, 257);
+	dst_pixels[2] = DIV_ROUND_CLOSEST(in_pixel->r, 257);
+	dst_pixels[1] = DIV_ROUND_CLOSEST(in_pixel->g, 257);
+	dst_pixels[0] = DIV_ROUND_CLOSEST(in_pixel->b, 257);
 }
 
-static void argb_u16_to_XRGB8888(struct vkms_frame_info *frame_info,
-				 const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_XRGB8888(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
 {
-	int x_dst = frame_info->dst.x1;
-	u8 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
-	struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    src_buffer->n_pixels);
-
-	for (size_t x = 0; x < x_limit; x++, dst_pixels += 4) {
-		dst_pixels[3] = 0xff;
-		dst_pixels[2] = DIV_ROUND_CLOSEST(in_pixels[x].r, 257);
-		dst_pixels[1] = DIV_ROUND_CLOSEST(in_pixels[x].g, 257);
-		dst_pixels[0] = DIV_ROUND_CLOSEST(in_pixels[x].b, 257);
-	}
+	dst_pixels[3] = 0xff;
+	dst_pixels[2] = DIV_ROUND_CLOSEST(in_pixel->r, 257);
+	dst_pixels[1] = DIV_ROUND_CLOSEST(in_pixel->g, 257);
+	dst_pixels[0] = DIV_ROUND_CLOSEST(in_pixel->b, 257);
 }
 
-static void argb_u16_to_ARGB16161616(struct vkms_frame_info *frame_info,
-				     const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_ARGB16161616(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
 {
-	int x_dst = frame_info->dst.x1;
-	u16 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
-	struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    src_buffer->n_pixels);
-
-	for (size_t x = 0; x < x_limit; x++, dst_pixels += 4) {
-		dst_pixels[3] = cpu_to_le16(in_pixels[x].a);
-		dst_pixels[2] = cpu_to_le16(in_pixels[x].r);
-		dst_pixels[1] = cpu_to_le16(in_pixels[x].g);
-		dst_pixels[0] = cpu_to_le16(in_pixels[x].b);
-	}
+	u16 *pixels = (u16 *)dst_pixels;
+
+	pixels[3] = cpu_to_le16(in_pixel->a);
+	pixels[2] = cpu_to_le16(in_pixel->r);
+	pixels[1] = cpu_to_le16(in_pixel->g);
+	pixels[0] = cpu_to_le16(in_pixel->b);
 }
 
-static void argb_u16_to_XRGB16161616(struct vkms_frame_info *frame_info,
-				     const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_XRGB16161616(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
 {
-	int x_dst = frame_info->dst.x1;
-	u16 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
-	struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    src_buffer->n_pixels);
-
-	for (size_t x = 0; x < x_limit; x++, dst_pixels += 4) {
-		dst_pixels[3] = 0xffff;
-		dst_pixels[2] = cpu_to_le16(in_pixels[x].r);
-		dst_pixels[1] = cpu_to_le16(in_pixels[x].g);
-		dst_pixels[0] = cpu_to_le16(in_pixels[x].b);
-	}
+	u16 *pixels = (u16 *)dst_pixels;
+
+	pixels[3] = 0xffff;
+	pixels[2] = cpu_to_le16(in_pixel->r);
+	pixels[1] = cpu_to_le16(in_pixel->g);
+	pixels[0] = cpu_to_le16(in_pixel->b);
 }
 
-static void argb_u16_to_RGB565(struct vkms_frame_info *frame_info,
-			       const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_RGB565(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
 {
-	int x_dst = frame_info->dst.x1;
-	u16 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
-	struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
-	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
-			    src_buffer->n_pixels);
+	u16 *pixels = (u16 *)dst_pixels;
 
 	s64 fp_rb_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(31));
 	s64 fp_g_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(63));
 
-	for (size_t x = 0; x < x_limit; x++, dst_pixels++) {
-		s64 fp_r = drm_int2fixp(in_pixels[x].r);
-		s64 fp_g = drm_int2fixp(in_pixels[x].g);
-		s64 fp_b = drm_int2fixp(in_pixels[x].b);
+	s64 fp_r = drm_int2fixp(in_pixel->r);
+	s64 fp_g = drm_int2fixp(in_pixel->g);
+	s64 fp_b = drm_int2fixp(in_pixel->b);
 
-		u16 r = drm_fixp2int(drm_fixp_div(fp_r, fp_rb_ratio));
-		u16 g = drm_fixp2int(drm_fixp_div(fp_g, fp_g_ratio));
-		u16 b = drm_fixp2int(drm_fixp_div(fp_b, fp_rb_ratio));
+	u16 r = drm_fixp2int(drm_fixp_div(fp_r, fp_rb_ratio));
+	u16 g = drm_fixp2int(drm_fixp_div(fp_g, fp_g_ratio));
+	u16 b = drm_fixp2int(drm_fixp_div(fp_b, fp_rb_ratio));
 
-		*dst_pixels = cpu_to_le16(r << 11 | g << 5 | b);
-	}
+	*pixels = cpu_to_le16(r << 11 | g << 5 | b);
+}
+
+void vkms_writeback_row(struct vkms_writeback_job *wb,
+			const struct line_buffer *src_buffer, int y)
+{
+	struct vkms_frame_info *frame_info = &wb->wb_frame_info;
+	int x_dst = frame_info->dst.x1;
+	u8 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
+	struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
+	int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst), src_buffer->n_pixels);
+
+	for (size_t x = 0; x < x_limit; x++, dst_pixels += frame_info->cpp)
+		wb->pixel_write(dst_pixels, &in_pixels[x]);
 }
 
-void *get_frame_to_line_function(u32 format)
+void *get_pixel_conversion_function(u32 format)
 {
 	switch (format) {
 	case DRM_FORMAT_ARGB8888:
@@ -267,7 +245,7 @@ void *get_frame_to_line_function(u32 format)
 	}
 }
 
-void *get_line_to_frame_function(u32 format)
+void *get_pixel_write_function(u32 format)
 {
 	switch (format) {
 	case DRM_FORMAT_ARGB8888:
diff --git a/drivers/gpu/drm/vkms/vkms_formats.h b/drivers/gpu/drm/vkms/vkms_formats.h
index 43b7c1979018..cf59c2ed8e9a 100644
--- a/drivers/gpu/drm/vkms/vkms_formats.h
+++ b/drivers/gpu/drm/vkms/vkms_formats.h
@@ -5,8 +5,8 @@
 
 #include "vkms_drv.h"
 
-void *get_frame_to_line_function(u32 format);
+void *get_pixel_conversion_function(u32 format);
 
-void *get_line_to_frame_function(u32 format);
+void *get_pixel_write_function(u32 format);
 
 #endif /* _VKMS_FORMATS_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 991857125bb4..28a50d2149f5 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -1,24 +1,73 @@
 // SPDX-License-Identifier: GPL-2.0+
 
-#include "vkms_drv.h"
+#include <drm/drm_print.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_plane.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_simple_kms_helper.h>
+#include <linux/printk.h>
 
-static void vkms_connector_destroy(struct drm_connector *connector)
-{
-	drm_connector_cleanup(connector);
-}
+#include "vkms_drv.h"
 
 static const struct drm_connector_funcs vkms_connector_funcs = {
+	.detect = vkms_connector_detect,
 	.fill_modes = drm_helper_probe_single_connector_modes,
-	.destroy = vkms_connector_destroy,
+	.destroy = drm_connector_cleanup,
 	.reset = drm_atomic_helper_connector_reset,
 	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
+static const struct vkms_config_connector *
+find_config_for_connector(struct drm_connector *connector)
+{
+	struct vkms_device *vkms = drm_device_to_vkms_device(connector->dev);
+	struct vkms_configfs *configfs = vkms->configfs;
+	struct config_item *item;
+
+	if (!configfs) {
+		pr_info("Default connector has no configfs entry");
+		return NULL;
+	}
+
+	list_for_each_entry(item, &configfs->connectors_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_connector *config_connector =
+			item_to_config_connector(item);
+		if (config_connector->connector == connector)
+			return config_connector;
+	}
+
+	pr_warn("Could not find config to match connector %s, but configfs was initialized",
+		connector->name);
+
+	return NULL;
+}
+
+enum drm_connector_status vkms_connector_detect(struct drm_connector *connector,
+						bool force)
+{
+	enum drm_connector_status status = connector_status_connected;
+	const struct vkms_config_connector *config_connector =
+		find_config_for_connector(connector);
+
+	if (!config_connector)
+		return connector_status_connected;
+
+	if (!config_connector->connected)
+		status = connector_status_disconnected;
+
+	return status;
+}
+
+static const struct drm_encoder_funcs vkms_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static int vkms_conn_get_modes(struct drm_connector *connector)
 {
 	int count;
@@ -30,82 +79,120 @@ static int vkms_conn_get_modes(struct drm_connector *connector)
 }
 
 static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
-	.get_modes    = vkms_conn_get_modes,
+	.get_modes = vkms_conn_get_modes,
 };
 
-static int vkms_add_overlay_plane(struct vkms_device *vkmsdev, int index,
-				  struct drm_crtc *crtc)
+static struct drm_connector *
+vkms_connector_init(struct vkms_device *vkms_device)
 {
-	struct vkms_plane *overlay;
+	struct drm_connector *connector;
+	int ret;
+
+	if (vkms_device->output.num_connectors >= VKMS_MAX_OUTPUT_OBJECTS)
+		return ERR_PTR(-ENOMEM);
 
-	overlay = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_OVERLAY, index);
-	if (IS_ERR(overlay))
-		return PTR_ERR(overlay);
+	connector = &vkms_device->output
+			     .connectors[vkms_device->output.num_connectors++];
+	ret = drm_connector_init(&vkms_device->drm, connector,
+				 &vkms_connector_funcs,
+				 DRM_MODE_CONNECTOR_VIRTUAL);
+	if (ret) {
+		memset(connector, 0, sizeof(*connector));
+		vkms_device->output.num_connectors -= 1;
+		return ERR_PTR(ret);
+	}
 
-	if (!overlay->base.possible_crtcs)
-		overlay->base.possible_crtcs = drm_crtc_mask(crtc);
+	drm_connector_helper_add(connector, &vkms_conn_helper_funcs);
 
-	return 0;
+	return connector;
 }
 
-int vkms_output_init(struct vkms_device *vkmsdev, int index)
+static struct drm_encoder *vkms_encoder_init(struct vkms_device *vkms_device)
+{
+	struct drm_encoder *encoder;
+	int ret;
+
+	if (vkms_device->output.num_encoders >= VKMS_MAX_OUTPUT_OBJECTS)
+		return ERR_PTR(-ENOMEM);
+
+	encoder = &vkms_device->output
+			   .encoders[vkms_device->output.num_encoders++];
+	ret = drm_encoder_init(&vkms_device->drm, encoder, &vkms_encoder_funcs,
+			       DRM_MODE_ENCODER_VIRTUAL, NULL);
+	if (ret) {
+		memset(encoder, 0, sizeof(*encoder));
+		vkms_device->output.num_encoders -= 1;
+		return ERR_PTR(ret);
+	}
+	return encoder;
+}
+
+int vkms_output_init_default(struct vkms_device *vkmsdev)
 {
-	struct vkms_output *output = &vkmsdev->output;
 	struct drm_device *dev = &vkmsdev->drm;
-	struct drm_connector *connector = &output->connector;
-	struct drm_encoder *encoder = &output->encoder;
-	struct drm_crtc *crtc = &output->crtc;
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+	struct vkms_crtc *vkms_crtc;
 	struct vkms_plane *primary, *cursor = NULL;
 	int ret;
 	int writeback;
 	unsigned int n;
 
-	primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY, index);
+	primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY);
 	if (IS_ERR(primary))
 		return PTR_ERR(primary);
 
-	if (vkmsdev->config->overlay) {
+	if (vkmsdev->config.overlay) {
 		for (n = 0; n < NUM_OVERLAY_PLANES; n++) {
-			ret = vkms_add_overlay_plane(vkmsdev, index, crtc);
-			if (ret)
-				return ret;
+			struct vkms_plane *overlay = vkms_plane_init(
+				vkmsdev, DRM_PLANE_TYPE_OVERLAY);
+			if (IS_ERR(overlay)) {
+				return PTR_ERR(overlay);
+			}
 		}
 	}
 
-	if (vkmsdev->config->cursor) {
-		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR, index);
-		if (IS_ERR(cursor))
+	if (vkmsdev->config.cursor) {
+		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR);
+		if (IS_ERR(cursor)) {
 			return PTR_ERR(cursor);
+		}
 	}
 
-	ret = vkms_crtc_init(dev, crtc, &primary->base, &cursor->base);
-	if (ret)
-		return ret;
+	vkms_crtc = vkms_crtc_init(vkmsdev, &primary->base,
+				   cursor ? &cursor->base : NULL,
+				   "crtc-default");
+	if (IS_ERR(vkms_crtc)) {
+		DRM_ERROR("Failed to init crtc\n");
+		return PTR_ERR(vkms_crtc);
+	}
 
-	ret = drm_connector_init(dev, connector, &vkms_connector_funcs,
-				 DRM_MODE_CONNECTOR_VIRTUAL);
-	if (ret) {
-		DRM_ERROR("Failed to init connector\n");
-		goto err_connector;
+	for (int i = 0; i < vkmsdev->output.num_planes; i++) {
+		vkmsdev->output.planes[i].base.possible_crtcs |=
+			drm_crtc_mask(&vkms_crtc->base);
 	}
 
-	drm_connector_helper_add(connector, &vkms_conn_helper_funcs);
+	connector = vkms_connector_init(vkmsdev);
+	if (IS_ERR(connector)) {
+		DRM_ERROR("Failed to init connector\n");
+		return PTR_ERR(connector);
+	}
 
-	ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL);
-	if (ret) {
+	encoder = vkms_encoder_init(vkmsdev);
+	if (IS_ERR(encoder)) {
 		DRM_ERROR("Failed to init encoder\n");
-		goto err_encoder;
+		return PTR_ERR(encoder);
 	}
-	encoder->possible_crtcs = 1;
+	encoder->possible_crtcs |= drm_crtc_mask(&vkms_crtc->base);
 
 	ret = drm_connector_attach_encoder(connector, encoder);
 	if (ret) {
 		DRM_ERROR("Failed to attach connector to encoder\n");
-		goto err_attach;
+		return ret;
 	}
 
-	if (vkmsdev->config->writeback) {
-		writeback = vkms_enable_writeback_connector(vkmsdev);
+	if (vkmsdev->config.writeback) {
+		writeback = vkms_enable_writeback_connector(vkmsdev, vkms_crtc);
 		if (writeback)
 			DRM_ERROR("Failed to init writeback connector\n");
 	}
@@ -113,15 +200,175 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
 	drm_mode_config_reset(dev);
 
 	return 0;
+}
+
+static bool is_object_linked(struct vkms_config_links *links, unsigned long idx)
+{
+	return links->linked_object_bitmap & (1 << idx);
+}
+
+int vkms_output_init(struct vkms_device *vkmsdev)
+{
+	struct drm_device *dev = &vkmsdev->drm;
+	struct vkms_configfs *configfs = vkmsdev->configfs;
+	struct vkms_output *output = &vkmsdev->output;
+	struct plane_map {
+		struct vkms_config_plane *config_plane;
+		struct vkms_plane *plane;
+	} plane_map[VKMS_MAX_PLANES] = { 0 };
+	struct encoder_map {
+		struct vkms_config_encoder *config_encoder;
+		struct drm_encoder *encoder;
+	} encoder_map[VKMS_MAX_OUTPUT_OBJECTS] = { 0 };
+	struct config_item *item;
+	int map_idx = 0;
+
+	list_for_each_entry(item, &configfs->planes_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_plane *config_plane =
+			item_to_config_plane(item);
+		struct vkms_plane *plane =
+			vkms_plane_init(vkmsdev, config_plane->type);
+
+		if (IS_ERR(plane)) {
+			DRM_ERROR("Unable to init plane from config: %s",
+				  item->ci_name);
+			return PTR_ERR(plane);
+		}
+
+		plane_map[map_idx].config_plane = config_plane;
+		plane_map[map_idx].plane = plane;
+		map_idx += 1;
+	}
+
+	map_idx = 0;
+	list_for_each_entry(item, &configfs->encoders_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_encoder *config_encoder =
+			item_to_config_encoder(item);
+		struct drm_encoder *encoder = vkms_encoder_init(vkmsdev);
+
+		if (IS_ERR(encoder)) {
+			DRM_ERROR("Failed to init config encoder: %s",
+				  item->ci_name);
+			return PTR_ERR(encoder);
+		}
+		encoder_map[map_idx].config_encoder = config_encoder;
+		encoder_map[map_idx].encoder = encoder;
+		map_idx += 1;
+	}
+
+	list_for_each_entry(item, &configfs->connectors_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_connector *config_connector =
+			item_to_config_connector(item);
+		struct drm_connector *connector = vkms_connector_init(vkmsdev);
+		if (IS_ERR(connector)) {
+			DRM_ERROR("Failed to init connector from config: %s",
+				  item->ci_name);
+			return PTR_ERR(connector);
+		}
+		config_connector->connector = connector;
+
+		for (int j = 0; j < output->num_connectors; j++) {
+			struct encoder_map *encoder = &encoder_map[j];
 
-err_attach:
-	drm_encoder_cleanup(encoder);
+			if (is_object_linked(
+				    &config_connector->possible_encoders,
+				    encoder->config_encoder
+					    ->encoder_config_idx)) {
+				drm_connector_attach_encoder(connector,
+							     encoder->encoder);
+			}
+		}
+	}
+
+	list_for_each_entry(item, &configfs->crtcs_group.cg_children,
+			    ci_entry) {
+		struct vkms_config_crtc *config_crtc =
+			item_to_config_crtc(item);
+		struct vkms_crtc *vkms_crtc;
+		struct drm_plane *primary = NULL, *cursor = NULL;
+
+		for (int j = 0; j < output->num_planes; j++) {
+			struct plane_map *plane_entry = &plane_map[j];
+			struct drm_plane *plane = &plane_entry->plane->base;
+
+			if (!is_object_linked(
+				    &plane_entry->config_plane->possible_crtcs,
+				    config_crtc->crtc_config_idx)) {
+				continue;
+			}
+
+			if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
+				if (primary) {
+					DRM_WARN(
+						"Too many primary planes found for crtc %s.",
+						item->ci_name);
+					return EINVAL;
+				}
+				primary = plane;
+			} else if (plane->type == DRM_PLANE_TYPE_CURSOR) {
+				if (cursor) {
+					DRM_WARN(
+						"Too many cursor planes found for crtc %s.",
+						item->ci_name);
+					return EINVAL;
+				}
+				cursor = plane;
+			}
+		}
+
+		if (!primary) {
+			DRM_WARN("No primary plane configured for crtc %s",
+				 item->ci_name);
+			return EINVAL;
+		}
 
-err_encoder:
-	drm_connector_cleanup(connector);
+		vkms_crtc =
+			vkms_crtc_init(vkmsdev, primary, cursor, item->ci_name);
+		if (IS_ERR(vkms_crtc)) {
+			DRM_WARN("Unable to init crtc from config: %s",
+				 item->ci_name);
+			return PTR_ERR(vkms_crtc);
+		}
+
+		for (int j = 0; j < output->num_planes; j++) {
+			struct plane_map *plane_entry = &plane_map[j];
 
-err_connector:
-	drm_crtc_cleanup(crtc);
+			if (!plane_entry->plane)
+				break;
 
-	return ret;
+			if (is_object_linked(
+				    &plane_entry->config_plane->possible_crtcs,
+				    config_crtc->crtc_config_idx)) {
+				plane_entry->plane->base.possible_crtcs |=
+					drm_crtc_mask(&vkms_crtc->base);
+			}
+		}
+
+		for (int j = 0; j < output->num_encoders; j++) {
+			struct encoder_map *encoder_entry = &encoder_map[j];
+
+			if (is_object_linked(&encoder_entry->config_encoder
+						      ->possible_crtcs,
+					     config_crtc->crtc_config_idx)) {
+				encoder_entry->encoder->possible_crtcs |=
+					drm_crtc_mask(&vkms_crtc->base);
+			}
+		}
+
+		if (vkmsdev->config.writeback) {
+			int ret = vkms_enable_writeback_connector(vkmsdev,
+								  vkms_crtc);
+			if (ret)
+				DRM_WARN(
+					"Failed to init writeback connector for config crtc: %s. Error code %d",
+					item->ci_name, ret);
+		}
+	}
+
+	drm_mode_config_reset(dev);
+
+	return 0;
 }
diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c
index b3f8a115cc23..f69621822c1b 100644
--- a/drivers/gpu/drm/vkms/vkms_plane.c
+++ b/drivers/gpu/drm/vkms/vkms_plane.c
@@ -4,20 +4,17 @@
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_plane_helper.h>
 
 #include "vkms_drv.h"
 #include "vkms_formats.h"
 
 static const u32 vkms_formats[] = {
-	DRM_FORMAT_XRGB8888,
-	DRM_FORMAT_XRGB16161616,
-	DRM_FORMAT_RGB565
-};
-
-static const u32 vkms_plane_formats[] = {
 	DRM_FORMAT_ARGB8888,
 	DRM_FORMAT_XRGB8888,
 	DRM_FORMAT_XRGB16161616,
@@ -70,6 +67,20 @@ static void vkms_plane_destroy_state(struct drm_plane *plane,
 	kfree(vkms_state);
 }
 
+static void vkms_plane_destroy(struct drm_plane *plane)
+{
+	struct vkms_plane *vkms_plane =
+		container_of(plane, struct vkms_plane, base);
+
+	if (plane->state) {
+		vkms_plane_destroy_state(plane, plane->state);
+		plane->state = NULL;
+	}
+
+	drm_plane_cleanup(plane);
+	memset(vkms_plane, 0, sizeof(struct vkms_plane));
+}
+
 static void vkms_plane_reset(struct drm_plane *plane)
 {
 	struct vkms_plane_state *vkms_state;
@@ -89,11 +100,12 @@ static void vkms_plane_reset(struct drm_plane *plane)
 }
 
 static const struct drm_plane_funcs vkms_plane_funcs = {
-	.update_plane		= drm_atomic_helper_update_plane,
-	.disable_plane		= drm_atomic_helper_disable_plane,
-	.reset			= vkms_plane_reset,
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = vkms_plane_destroy,
+	.reset = vkms_plane_reset,
 	.atomic_duplicate_state = vkms_plane_duplicate_state,
-	.atomic_destroy_state	= vkms_plane_destroy_state,
+	.atomic_destroy_state = vkms_plane_destroy_state,
 };
 
 static void vkms_plane_atomic_update(struct drm_plane *plane,
@@ -117,13 +129,23 @@ static void vkms_plane_atomic_update(struct drm_plane *plane,
 	frame_info = vkms_plane_state->frame_info;
 	memcpy(&frame_info->src, &new_state->src, sizeof(struct drm_rect));
 	memcpy(&frame_info->dst, &new_state->dst, sizeof(struct drm_rect));
+	memcpy(&frame_info->rotated, &new_state->dst, sizeof(struct drm_rect));
 	frame_info->fb = fb;
 	memcpy(&frame_info->map, &shadow_plane_state->data, sizeof(frame_info->map));
 	drm_framebuffer_get(frame_info->fb);
+	frame_info->rotation = drm_rotation_simplify(new_state->rotation, DRM_MODE_ROTATE_0 |
+						     DRM_MODE_ROTATE_90 |
+						     DRM_MODE_ROTATE_270 |
+						     DRM_MODE_REFLECT_X |
+						     DRM_MODE_REFLECT_Y);
+
+	drm_rect_rotate(&frame_info->rotated, drm_rect_width(&frame_info->rotated),
+			drm_rect_height(&frame_info->rotated), frame_info->rotation);
+
 	frame_info->offset = fb->offsets[0];
 	frame_info->pitch = fb->pitches[0];
 	frame_info->cpp = fb->format->cpp[0];
-	vkms_plane_state->plane_read = get_frame_to_line_function(fmt);
+	vkms_plane_state->pixel_read = get_pixel_conversion_function(fmt);
 }
 
 static int vkms_plane_atomic_check(struct drm_plane *plane,
@@ -132,7 +154,6 @@ static int vkms_plane_atomic_check(struct drm_plane *plane,
 	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
 										 plane);
 	struct drm_crtc_state *crtc_state;
-	bool can_position = false;
 	int ret;
 
 	if (!new_plane_state->fb || WARN_ON(!new_plane_state->crtc))
@@ -143,20 +164,13 @@ static int vkms_plane_atomic_check(struct drm_plane *plane,
 	if (IS_ERR(crtc_state))
 		return PTR_ERR(crtc_state);
 
-	if (plane->type != DRM_PLANE_TYPE_PRIMARY)
-		can_position = true;
-
 	ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
 						  DRM_PLANE_NO_SCALING,
 						  DRM_PLANE_NO_SCALING,
-						  can_position, true);
+						  true, true);
 	if (ret != 0)
 		return ret;
 
-	/* for now primary plane must be visible and full screen */
-	if (!new_plane_state->visible && !can_position)
-		return -EINVAL;
-
 	return 0;
 }
 
@@ -193,7 +207,7 @@ static void vkms_cleanup_fb(struct drm_plane *plane,
 	drm_gem_fb_vunmap(fb, shadow_plane_state->map);
 }
 
-static const struct drm_plane_helper_funcs vkms_primary_helper_funcs = {
+static const struct drm_plane_helper_funcs vkms_plane_helper_funcs = {
 	.atomic_update		= vkms_plane_atomic_update,
 	.atomic_check		= vkms_plane_atomic_check,
 	.prepare_fb		= vkms_prepare_fb,
@@ -201,41 +215,27 @@ static const struct drm_plane_helper_funcs vkms_primary_helper_funcs = {
 };
 
 struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
-				   enum drm_plane_type type, int index)
+				   enum drm_plane_type type)
 {
 	struct drm_device *dev = &vkmsdev->drm;
-	const struct drm_plane_helper_funcs *funcs;
+	struct vkms_output *output = &vkmsdev->output;
 	struct vkms_plane *plane;
-	const u32 *formats;
-	int nformats;
-
-	switch (type) {
-	case DRM_PLANE_TYPE_PRIMARY:
-		formats = vkms_formats;
-		nformats = ARRAY_SIZE(vkms_formats);
-		funcs = &vkms_primary_helper_funcs;
-		break;
-	case DRM_PLANE_TYPE_CURSOR:
-	case DRM_PLANE_TYPE_OVERLAY:
-		formats = vkms_plane_formats;
-		nformats = ARRAY_SIZE(vkms_plane_formats);
-		funcs = &vkms_primary_helper_funcs;
-		break;
-	default:
-		formats = vkms_formats;
-		nformats = ARRAY_SIZE(vkms_formats);
-		funcs = &vkms_primary_helper_funcs;
-		break;
-	}
+	int ret;
+
+	if (output->num_planes >= VKMS_MAX_PLANES)
+		return ERR_PTR(-ENOMEM);
+
+	plane = &output->planes[output->num_planes++];
+	ret = drm_universal_plane_init(dev, &plane->base, 0, &vkms_plane_funcs,
+				       vkms_formats, ARRAY_SIZE(vkms_formats),
+				       NULL, type, NULL);
+	if (ret)
+		return ERR_PTR(ret);
 
-	plane = drmm_universal_plane_alloc(dev, struct vkms_plane, base, 1 << index,
-					   &vkms_plane_funcs,
-					   formats, nformats,
-					   NULL, type, NULL);
-	if (IS_ERR(plane))
-		return plane;
+	drm_plane_helper_add(&plane->base, &vkms_plane_helper_funcs);
 
-	drm_plane_helper_add(&plane->base, funcs);
+	drm_plane_create_rotation_property(&plane->base, DRM_MODE_ROTATE_0,
+					   DRM_MODE_ROTATE_MASK | DRM_MODE_REFLECT_MASK);
 
 	return plane;
 }
diff --git a/drivers/gpu/drm/vkms/vkms_writeback.c b/drivers/gpu/drm/vkms/vkms_writeback.c
index 84a51cd281b9..47449979c564 100644
--- a/drivers/gpu/drm/vkms/vkms_writeback.c
+++ b/drivers/gpu/drm/vkms/vkms_writeback.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0+
 
 #include <linux/iosys-map.h>
+#include <linux/kernel.h>
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_edid.h>
@@ -15,6 +16,7 @@
 #include "vkms_formats.h"
 
 static const u32 vkms_wb_formats[] = {
+	DRM_FORMAT_ARGB8888,
 	DRM_FORMAT_XRGB8888,
 	DRM_FORMAT_XRGB16161616,
 	DRM_FORMAT_ARGB16161616,
@@ -101,7 +103,8 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
 				struct drm_writeback_job *job)
 {
 	struct vkms_writeback_job *vkmsjob = job->priv;
-	struct vkms_device *vkmsdev;
+	struct vkms_crtc *vkms_crtc =
+		container_of(connector, struct vkms_crtc, wb_connector);
 
 	if (!job->fb)
 		return;
@@ -110,8 +113,7 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
 
 	drm_framebuffer_put(vkmsjob->wb_frame_info.fb);
 
-	vkmsdev = drm_device_to_vkms_device(job->fb->dev);
-	vkms_set_composer(&vkmsdev->output, false);
+	vkms_set_composer(vkms_crtc, false);
 	kfree(vkmsjob);
 }
 
@@ -120,11 +122,11 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
 {
 	struct drm_connector_state *connector_state = drm_atomic_get_new_connector_state(state,
 											 conn);
-	struct vkms_device *vkmsdev = drm_device_to_vkms_device(conn->dev);
-	struct vkms_output *output = &vkmsdev->output;
-	struct drm_writeback_connector *wb_conn = &output->wb_connector;
+	struct vkms_crtc *vkms_crtc =
+		drm_crtc_to_vkms_crtc(connector_state->crtc);
+	struct drm_writeback_connector *wb_conn = &vkms_crtc->wb_connector;
 	struct drm_connector_state *conn_state = wb_conn->base.state;
-	struct vkms_crtc_state *crtc_state = output->composer_state;
+	struct vkms_crtc_state *crtc_state = vkms_crtc->composer_state;
 	struct drm_framebuffer *fb = connector_state->writeback_job->fb;
 	u16 crtc_height = crtc_state->base.crtc->mode.vdisplay;
 	u16 crtc_width = crtc_state->base.crtc->mode.hdisplay;
@@ -135,20 +137,24 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
 	if (!conn_state)
 		return;
 
-	vkms_set_composer(&vkmsdev->output, true);
+	vkms_set_composer(vkms_crtc, true);
 
 	active_wb = conn_state->writeback_job->priv;
 	wb_frame_info = &active_wb->wb_frame_info;
 
-	spin_lock_irq(&output->composer_lock);
+	spin_lock_irq(&vkms_crtc->composer_lock);
 	crtc_state->active_writeback = active_wb;
+	crtc_state->wb_pending = true;
+	spin_unlock_irq(&vkms_crtc->composer_lock);
+
 	wb_frame_info->offset = fb->offsets[0];
 	wb_frame_info->pitch = fb->pitches[0];
 	wb_frame_info->cpp = fb->format->cpp[0];
+
 	crtc_state->wb_pending = true;
-	spin_unlock_irq(&output->composer_lock);
+	spin_unlock_irq(&vkms_crtc->composer_lock);
 	drm_writeback_queue_job(wb_conn, connector_state);
-	active_wb->wb_write = get_line_to_frame_function(wb_format);
+	active_wb->pixel_write = get_pixel_write_function(wb_format);
 	drm_rect_init(&wb_frame_info->src, 0, 0, crtc_width, crtc_height);
 	drm_rect_init(&wb_frame_info->dst, 0, 0, crtc_width, crtc_height);
 }
@@ -160,9 +166,10 @@ static const struct drm_connector_helper_funcs vkms_wb_conn_helper_funcs = {
 	.atomic_commit = vkms_wb_atomic_commit,
 };
 
-int vkms_enable_writeback_connector(struct vkms_device *vkmsdev)
+int vkms_enable_writeback_connector(struct vkms_device *vkmsdev,
+				    struct vkms_crtc *vkms_crtc)
 {
-	struct drm_writeback_connector *wb = &vkmsdev->output.wb_connector;
+	struct drm_writeback_connector *wb = &vkms_crtc->wb_connector;
 
 	drm_connector_helper_add(&wb->base, &vkms_wb_conn_helper_funcs);
 
diff --git a/include/drm/drm_fixed.h b/include/drm/drm_fixed.h
index 553210c02ee0..6ea339d5de08 100644
--- a/include/drm/drm_fixed.h
+++ b/include/drm/drm_fixed.h
@@ -25,6 +25,7 @@
 #ifndef DRM_FIXED_H
 #define DRM_FIXED_H
 
+#include <linux/kernel.h>
 #include <linux/math64.h>
 
 typedef union dfixed {
@@ -70,6 +71,7 @@ static inline u32 dfixed_div(fixed20_12 A, fixed20_12 B)
 }
 
 #define DRM_FIXED_POINT		32
+#define DRM_FIXED_POINT_HALF	16
 #define DRM_FIXED_ONE		(1ULL << DRM_FIXED_POINT)
 #define DRM_FIXED_DECIMAL_MASK	(DRM_FIXED_ONE - 1)
 #define DRM_FIXED_DIGITS_MASK	(~DRM_FIXED_DECIMAL_MASK)
@@ -86,6 +88,11 @@ static inline int drm_fixp2int(s64 a)
 	return ((s64)a) >> DRM_FIXED_POINT;
 }
 
+static inline int drm_fixp2int_round(s64 a)
+{
+	return drm_fixp2int(a + (1 << (DRM_FIXED_POINT_HALF - 1)));
+}
+
 static inline int drm_fixp2int_ceil(s64 a)
 {
 	if (a > 0)
-- 
2.41.0.640.ga95def55d0-goog


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device
  2023-06-23 22:23 ` [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device Jim Shargo
  2023-06-25 17:57   ` Maira Canal
@ 2023-08-15 11:29   ` Marius Vlad
  2023-08-18  3:36     ` Brandon Pollack
  1 sibling, 1 reply; 21+ messages in thread
From: Marius Vlad @ 2023-08-15 11:29 UTC (permalink / raw)
  To: Jim Shargo
  Cc: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann, linux-kernel, dri-devel,
	linux-doc

[-- Attachment #1: Type: text/plain, Size: 30623 bytes --]

Hi,

See below some comments about vkms_wb_atomic_commit().

On Fri, Jun 23, 2023 at 06:23:44PM -0400, Jim Shargo wrote:
> This change supports multiple CRTCs, encoders, connectors instead of one
> of each per device.
> 
> Since ConfigFS-based devices will support multiple crtcs, it's useful to
> move all of the writeback/composition data from being per-"output" to
> being per-CRTC.
> 
> Since there's still only ever one CRTC, this should be a no-op refactor.
> 
> Signed-off-by: Jim Shargo <jshargo@chromium.org>
> ---
>  drivers/gpu/drm/vkms/vkms_composer.c  |  28 +++---
>  drivers/gpu/drm/vkms/vkms_crtc.c      |  95 +++++++++++---------
>  drivers/gpu/drm/vkms/vkms_drv.c       |  14 +--
>  drivers/gpu/drm/vkms/vkms_drv.h       |  65 +++++++++-----
>  drivers/gpu/drm/vkms/vkms_output.c    | 123 ++++++++++++++++++--------
>  drivers/gpu/drm/vkms/vkms_plane.c     |  44 ++++++---
>  drivers/gpu/drm/vkms/vkms_writeback.c |  26 +++---
>  7 files changed, 252 insertions(+), 143 deletions(-)
> 
> diff --git a/drivers/gpu/drm/vkms/vkms_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c
> index 1636ce3a79f9..75fa42e0ec07 100644
> --- a/drivers/gpu/drm/vkms/vkms_composer.c
> +++ b/drivers/gpu/drm/vkms/vkms_composer.c
> @@ -230,13 +230,13 @@ void vkms_composer_worker(struct work_struct *work)
>  						composer_work);
>  	struct drm_crtc *crtc = crtc_state->base.crtc;
>  	struct vkms_writeback_job *active_wb = crtc_state->active_writeback;
> -	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>  	bool crc_pending, wb_pending;
>  	u64 frame_start, frame_end;
>  	u32 crc32 = 0;
>  	int ret;
>  
> -	spin_lock_irq(&out->composer_lock);
> +	spin_lock_irq(&vkms_crtc->composer_lock);
>  	frame_start = crtc_state->frame_start;
>  	frame_end = crtc_state->frame_end;
>  	crc_pending = crtc_state->crc_pending;
> @@ -244,7 +244,7 @@ void vkms_composer_worker(struct work_struct *work)
>  	crtc_state->frame_start = 0;
>  	crtc_state->frame_end = 0;
>  	crtc_state->crc_pending = false;
> -	spin_unlock_irq(&out->composer_lock);
> +	spin_unlock_irq(&vkms_crtc->composer_lock);
>  
>  	/*
>  	 * We raced with the vblank hrtimer and previous work already computed
> @@ -262,10 +262,10 @@ void vkms_composer_worker(struct work_struct *work)
>  		return;
>  
>  	if (wb_pending) {
> -		drm_writeback_signal_completion(&out->wb_connector, 0);
> -		spin_lock_irq(&out->composer_lock);
> +		drm_writeback_signal_completion(&vkms_crtc->wb_connector, 0);
> +		spin_lock_irq(&vkms_crtc->composer_lock);
>  		crtc_state->wb_pending = false;
> -		spin_unlock_irq(&out->composer_lock);
> +		spin_unlock_irq(&vkms_crtc->composer_lock);
>  	}
>  
>  	/*
> @@ -315,25 +315,25 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *src_name,
>  	return 0;
>  }
>  
> -void vkms_set_composer(struct vkms_output *out, bool enabled)
> +void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled)
>  {
>  	bool old_enabled;
>  
>  	if (enabled)
> -		drm_crtc_vblank_get(&out->crtc);
> +		drm_crtc_vblank_get(&vkms_crtc->base);
>  
> -	spin_lock_irq(&out->lock);
> -	old_enabled = out->composer_enabled;
> -	out->composer_enabled = enabled;
> -	spin_unlock_irq(&out->lock);
> +	spin_lock_irq(&vkms_crtc->lock);
> +	old_enabled = vkms_crtc->composer_enabled;
> +	vkms_crtc->composer_enabled = enabled;
> +	spin_unlock_irq(&vkms_crtc->lock);
>  
>  	if (old_enabled)
> -		drm_crtc_vblank_put(&out->crtc);
> +		drm_crtc_vblank_put(&vkms_crtc->base);
>  }
>  
>  int vkms_set_crc_source(struct drm_crtc *crtc, const char *src_name)
>  {
> -	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *out = drm_crtc_to_vkms_crtc(crtc);
>  	bool enabled = false;
>  	int ret = 0;
>  
> diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
> index 515f6772b866..d91e49c53adc 100644
> --- a/drivers/gpu/drm/vkms/vkms_crtc.c
> +++ b/drivers/gpu/drm/vkms/vkms_crtc.c
> @@ -11,35 +11,34 @@
>  
>  static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
>  {
> -	struct vkms_output *output = container_of(timer, struct vkms_output,
> -						  vblank_hrtimer);
> -	struct drm_crtc *crtc = &output->crtc;
> +	struct vkms_crtc *vkms_crtc = timer_to_vkms_crtc(timer);
> +	struct drm_crtc *crtc = &vkms_crtc->base;
>  	struct vkms_crtc_state *state;
>  	u64 ret_overrun;
>  	bool ret, fence_cookie;
>  
>  	fence_cookie = dma_fence_begin_signalling();
>  
> -	ret_overrun = hrtimer_forward_now(&output->vblank_hrtimer,
> -					  output->period_ns);
> +	ret_overrun = hrtimer_forward_now(&vkms_crtc->vblank_hrtimer,
> +					  vkms_crtc->period_ns);
>  	if (ret_overrun != 1)
>  		pr_warn("%s: vblank timer overrun\n", __func__);
>  
> -	spin_lock(&output->lock);
> +	spin_lock(&vkms_crtc->lock);
>  	ret = drm_crtc_handle_vblank(crtc);
>  	if (!ret)
>  		DRM_ERROR("vkms failure on handling vblank");
>  
> -	state = output->composer_state;
> -	spin_unlock(&output->lock);
> +	state = vkms_crtc->composer_state;
> +	spin_unlock(&vkms_crtc->lock);
>  
> -	if (state && output->composer_enabled) {
> +	if (state && vkms_crtc->composer_enabled) {
>  		u64 frame = drm_crtc_accurate_vblank_count(crtc);
>  
>  		/* update frame_start only if a queued vkms_composer_worker()
>  		 * has read the data
>  		 */
> -		spin_lock(&output->composer_lock);
> +		spin_lock(&vkms_crtc->composer_lock);
>  		if (!state->crc_pending)
>  			state->frame_start = frame;
>  		else
> @@ -47,9 +46,10 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
>  					 state->frame_start, frame);
>  		state->frame_end = frame;
>  		state->crc_pending = true;
> -		spin_unlock(&output->composer_lock);
> +		spin_unlock(&vkms_crtc->composer_lock);
>  
> -		ret = queue_work(output->composer_workq, &state->composer_work);
> +		ret = queue_work(vkms_crtc->composer_workq,
> +				 &state->composer_work);
>  		if (!ret)
>  			DRM_DEBUG_DRIVER("Composer worker already queued\n");
>  	}
> @@ -62,25 +62,27 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
>  static int vkms_enable_vblank(struct drm_crtc *crtc)
>  {
>  	struct drm_device *dev = crtc->dev;
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>  	unsigned int pipe = drm_crtc_index(crtc);
>  	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
> -	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
>  
>  	drm_calc_timestamping_constants(crtc, &crtc->mode);
>  
> -	hrtimer_init(&out->vblank_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> -	out->vblank_hrtimer.function = &vkms_vblank_simulate;
> -	out->period_ns = ktime_set(0, vblank->framedur_ns);
> -	hrtimer_start(&out->vblank_hrtimer, out->period_ns, HRTIMER_MODE_REL);
> +	hrtimer_init(&vkms_crtc->vblank_hrtimer, CLOCK_MONOTONIC,
> +		     HRTIMER_MODE_REL);
> +	vkms_crtc->vblank_hrtimer.function = &vkms_vblank_simulate;
> +	vkms_crtc->period_ns = ktime_set(0, vblank->framedur_ns);
> +	hrtimer_start(&vkms_crtc->vblank_hrtimer, vkms_crtc->period_ns,
> +		      HRTIMER_MODE_REL);
>  
>  	return 0;
>  }
>  
>  static void vkms_disable_vblank(struct drm_crtc *crtc)
>  {
> -	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>  
> -	hrtimer_cancel(&out->vblank_hrtimer);
> +	hrtimer_cancel(&vkms_crtc->vblank_hrtimer);
>  }
>  
>  static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
> @@ -88,9 +90,8 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
>  				      bool in_vblank_irq)
>  {
>  	struct drm_device *dev = crtc->dev;
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>  	unsigned int pipe = crtc->index;
> -	struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
> -	struct vkms_output *output = &vkmsdev->output;
>  	struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
>  
>  	if (!READ_ONCE(vblank->enabled)) {
> @@ -98,7 +99,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
>  		return true;
>  	}
>  
> -	*vblank_time = READ_ONCE(output->vblank_hrtimer.node.expires);
> +	*vblank_time = READ_ONCE(vkms_crtc->vblank_hrtimer.node.expires);
>  
>  	if (WARN_ON(*vblank_time == vblank->time))
>  		return true;
> @@ -110,7 +111,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
>  	 * the vblank core expects. Therefore we need to always correct the
>  	 * timestampe by one frame.
>  	 */
> -	*vblank_time -= output->period_ns;
> +	*vblank_time -= vkms_crtc->period_ns;
>  
>  	return true;
>  }
> @@ -236,18 +237,18 @@ static void vkms_crtc_atomic_disable(struct drm_crtc *crtc,
>  static void vkms_crtc_atomic_begin(struct drm_crtc *crtc,
>  				   struct drm_atomic_state *state)
>  {
> -	struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>  
>  	/* This lock is held across the atomic commit to block vblank timer
>  	 * from scheduling vkms_composer_worker until the composer is updated
>  	 */
> -	spin_lock_irq(&vkms_output->lock);
> +	spin_lock_irq(&vkms_crtc->lock);
>  }
>  
>  static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
>  				   struct drm_atomic_state *state)
>  {
> -	struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
> +	struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
>  
>  	if (crtc->state->event) {
>  		spin_lock(&crtc->dev->event_lock);
> @@ -262,9 +263,9 @@ static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
>  		crtc->state->event = NULL;
>  	}
>  
> -	vkms_output->composer_state = to_vkms_crtc_state(crtc->state);
> +	vkms_crtc->composer_state = to_vkms_crtc_state(crtc->state);
>  
> -	spin_unlock_irq(&vkms_output->lock);
> +	spin_unlock_irq(&vkms_crtc->lock);
>  }
>  
>  static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
> @@ -275,27 +276,41 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
>  	.atomic_disable	= vkms_crtc_atomic_disable,
>  };
>  
> -int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
> -		   struct drm_plane *primary, struct drm_plane *cursor)
> +struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
> +				 struct drm_plane *primary,
> +				 struct drm_plane *cursor)
>  {
> -	struct vkms_output *vkms_out = drm_crtc_to_vkms_output(crtc);
> +	struct drm_device *dev = &vkmsdev->drm;
> +	struct vkms_crtc *vkms_crtc;
>  	int ret;
>  
> -	ret = drmm_crtc_init_with_planes(dev, crtc, primary, cursor,
> +	if (vkmsdev->output.num_crtcs >= VKMS_MAX_OUTPUT_OBJECTS) {
> +		return ERR_PTR(-ENOMEM);
> +	}
> +	vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
> +
> +	ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, primary, cursor,
>  					 &vkms_crtc_funcs, NULL);
>  	if (ret) {
>  		DRM_ERROR("Failed to init CRTC\n");
> -		return ret;
> +		goto out_error;
>  	}
>  
> -	drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs);
> +	drm_crtc_helper_add(&vkms_crtc->base, &vkms_crtc_helper_funcs);
>  
> -	spin_lock_init(&vkms_out->lock);
> -	spin_lock_init(&vkms_out->composer_lock);
> +	spin_lock_init(&vkms_crtc->lock);
> +	spin_lock_init(&vkms_crtc->composer_lock);
>  
> -	vkms_out->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
> -	if (!vkms_out->composer_workq)
> -		return -ENOMEM;
> +	vkms_crtc->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
> +	if (!vkms_crtc->composer_workq) {
> +		ret = -ENOMEM;
> +		goto out_error;
> +	}
> +
> +	return vkms_crtc;
>  
> -	return ret;
> +out_error:
> +	memset(vkms_crtc, 0, sizeof(*vkms_crtc));
> +	vkmsdev->output.num_crtcs -= 1;
> +	return ERR_PTR(ret);
>  }
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
> index f07454fff046..aa2bee82b757 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.c
> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
> @@ -57,8 +57,8 @@ static void vkms_release(struct drm_device *dev)
>  {
>  	struct vkms_device *vkms = drm_device_to_vkms_device(dev);
>  
> -	if (vkms->output.composer_workq)
> -		destroy_workqueue(vkms->output.composer_workq);
> +	for (int i = 0; i < vkms->output.num_crtcs; i++)
> +		destroy_workqueue(vkms->output.crtcs[i].composer_workq);
>  }
>  
>  static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
> @@ -185,15 +185,15 @@ static int vkms_platform_probe(struct platform_device *pdev)
>  		goto out_release_group;
>  	}
>  
> -	ret = drm_vblank_init(&vkms_device->drm, 1);
> +	ret = vkms_modeset_init(vkms_device);
>  	if (ret) {
> -		DRM_ERROR("Failed to vblank\n");
> +		DRM_ERROR("Unable to initialize modesetting\n");
>  		goto out_release_group;
>  	}
>  
> -	ret = vkms_modeset_init(vkms_device);
> +	ret = drm_vblank_init(&vkms_device->drm, vkms_device->output.num_crtcs);
>  	if (ret) {
> -		DRM_ERROR("Unable to initialize modesetting\n");
> +		DRM_ERROR("Failed to vblank\n");
>  		goto out_release_group;
>  	}
>  
> @@ -202,7 +202,7 @@ static int vkms_platform_probe(struct platform_device *pdev)
>  
>  	ret = drm_dev_register(&vkms_device->drm, 0);
>  	if (ret) {
> -		DRM_ERROR("Unable to register device\n");
> +		DRM_ERROR("Unable to register device with id %d\n", pdev->id);
>  		return ret;
>  	}
>  
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
> index e87c8aea6fb3..4c3274838ec7 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.h
> +++ b/drivers/gpu/drm/vkms/vkms_drv.h
> @@ -23,6 +23,9 @@
>  
>  #define NUM_OVERLAY_PLANES 8
>  
> +#define VKMS_MAX_OUTPUT_OBJECTS 16
> +#define VKMS_MAX_PLANES (3 * VKMS_MAX_OUTPUT_OBJECTS)
> +
>  struct vkms_frame_info {
>  	struct drm_framebuffer *fb;
>  	struct drm_rect src, dst;
> @@ -65,6 +68,25 @@ struct vkms_plane {
>  	struct drm_plane base;
>  };
>  
> +struct vkms_crtc {
> +	struct drm_crtc base;
> +
> +	struct drm_writeback_connector wb_connector;
> +	struct hrtimer vblank_hrtimer;
> +	ktime_t period_ns;
> +	struct drm_pending_vblank_event *event;
> +	/* ordered wq for composer_work */
> +	struct workqueue_struct *composer_workq;
> +	/* protects concurrent access to composer */
> +	spinlock_t lock;
> +
> +	/* protected by @lock */
> +	bool composer_enabled;
> +	struct vkms_crtc_state *composer_state;
> +
> +	spinlock_t composer_lock;
> +};
> +
>  /**
>   * vkms_crtc_state - Driver specific CRTC state
>   * @base: base CRTC state
> @@ -89,23 +111,14 @@ struct vkms_crtc_state {
>  };
>  
>  struct vkms_output {
> -	struct drm_crtc crtc;
> -	struct drm_encoder encoder;
> -	struct drm_connector connector;
> -	struct drm_writeback_connector wb_connector;
> -	struct hrtimer vblank_hrtimer;
> -	ktime_t period_ns;
> -	struct drm_pending_vblank_event *event;
> -	/* ordered wq for composer_work */
> -	struct workqueue_struct *composer_workq;
> -	/* protects concurrent access to composer */
> -	spinlock_t lock;
> -
> -	/* protected by @lock */
> -	bool composer_enabled;
> -	struct vkms_crtc_state *composer_state;
> -
> -	spinlock_t composer_lock;
> +	int num_crtcs;
> +	struct vkms_crtc crtcs[VKMS_MAX_OUTPUT_OBJECTS];
> +	int num_encoders;
> +	struct drm_encoder encoders[VKMS_MAX_OUTPUT_OBJECTS];
> +	int num_connectors;
> +	struct drm_connector connectors[VKMS_MAX_OUTPUT_OBJECTS];
> +	int num_planes;
> +	struct vkms_plane planes[VKMS_MAX_PLANES];
>  };
>  
>  struct vkms_device;
> @@ -123,12 +136,14 @@ struct vkms_device {
>  	struct vkms_config config;
>  };
>  
> -#define drm_crtc_to_vkms_output(target) \
> -	container_of(target, struct vkms_output, crtc)
> +#define drm_crtc_to_vkms_crtc(crtc) container_of(crtc, struct vkms_crtc, base)
>  
>  #define drm_device_to_vkms_device(target) \
>  	container_of(target, struct vkms_device, drm)
>  
> +#define timer_to_vkms_crtc(timer) \
> +	container_of(timer, struct vkms_crtc, vblank_hrtimer)
> +
>  #define to_vkms_crtc_state(target)\
>  	container_of(target, struct vkms_crtc_state, base)
>  
> @@ -136,13 +151,14 @@ struct vkms_device {
>  	container_of(target, struct vkms_plane_state, base.base)
>  
>  /* CRTC */
> -int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
> -		   struct drm_plane *primary, struct drm_plane *cursor);
> +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 index);
>  
>  struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
> -				   enum drm_plane_type type, int index);
> +				   enum drm_plane_type type);
>  
>  /* CRC Support */
>  const char *const *vkms_get_crc_sources(struct drm_crtc *crtc,
> @@ -153,10 +169,11 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
>  
>  /* Composer Support */
>  void vkms_composer_worker(struct work_struct *work);
> -void vkms_set_composer(struct vkms_output *out, bool enabled);
> +void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled);
>  void vkms_compose_row(struct line_buffer *stage_buffer, struct vkms_plane_state *plane, int y);
>  
>  /* Writeback */
> -int vkms_enable_writeback_connector(struct vkms_device *vkmsdev);
> +int vkms_enable_writeback_connector(struct vkms_device *vkmsdev,
> +				    struct vkms_crtc *vkms_crtc);
>  
>  #endif /* _VKMS_DRV_H_ */
> diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
> index 963a64cf068b..6b4a77780d3c 100644
> --- a/drivers/gpu/drm/vkms/vkms_output.c
> +++ b/drivers/gpu/drm/vkms/vkms_output.c
> @@ -1,9 +1,13 @@
>  // SPDX-License-Identifier: GPL-2.0+
>  
> -#include "vkms_drv.h"
>  #include <drm/drm_atomic_helper.h>
> +#include <drm/drm_connector.h>
>  #include <drm/drm_edid.h>
> +#include <drm/drm_encoder.h>
>  #include <drm/drm_probe_helper.h>
> +#include <drm/drm_simple_kms_helper.h>
> +
> +#include "vkms_drv.h"
>  
>  static const struct drm_connector_funcs vkms_connector_funcs = {
>  	.fill_modes = drm_helper_probe_single_connector_modes,
> @@ -28,74 +32,116 @@ static int vkms_conn_get_modes(struct drm_connector *connector)
>  }
>  
>  static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
> -	.get_modes    = vkms_conn_get_modes,
> +	.get_modes = vkms_conn_get_modes,
>  };
>  
> -static int vkms_add_overlay_plane(struct vkms_device *vkmsdev, int index,
> -				  struct drm_crtc *crtc)
> +static struct drm_connector *
> +vkms_connector_init(struct vkms_device *vkms_device)
>  {
> -	struct vkms_plane *overlay;
> +	struct drm_connector *connector;
> +	int ret;
>  
> -	overlay = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_OVERLAY, index);
> -	if (IS_ERR(overlay))
> -		return PTR_ERR(overlay);
> +	if (vkms_device->output.num_connectors >= VKMS_MAX_OUTPUT_OBJECTS)
> +		return ERR_PTR(-ENOMEM);
>  
> -	if (!overlay->base.possible_crtcs)
> -		overlay->base.possible_crtcs = drm_crtc_mask(crtc);
> +	connector = &vkms_device->output
> +			     .connectors[vkms_device->output.num_connectors++];
> +	ret = drm_connector_init(&vkms_device->drm, connector,
> +				 &vkms_connector_funcs,
> +				 DRM_MODE_CONNECTOR_VIRTUAL);
> +	if (ret) {
> +		memset(connector, 0, sizeof(*connector));
> +		vkms_device->output.num_connectors -= 1;
> +		return ERR_PTR(ret);
> +	}
>  
> -	return 0;
> +	drm_connector_helper_add(connector, &vkms_conn_helper_funcs);
> +
> +	return connector;
> +}
> +
> +static struct drm_encoder *vkms_encoder_init(struct vkms_device *vkms_device)
> +{
> +	struct drm_encoder *encoder;
> +	int ret;
> +
> +	if (vkms_device->output.num_encoders >= VKMS_MAX_OUTPUT_OBJECTS)
> +		return ERR_PTR(-ENOMEM);
> +
> +	encoder = &vkms_device->output
> +			   .encoders[vkms_device->output.num_encoders++];
> +	ret = drm_encoder_init(&vkms_device->drm, encoder, &vkms_encoder_funcs,
> +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> +	if (ret) {
> +		memset(encoder, 0, sizeof(*encoder));
> +		vkms_device->output.num_encoders -= 1;
> +		return ERR_PTR(ret);
> +	}
> +	return encoder;
>  }
>  
>  int vkms_output_init(struct vkms_device *vkmsdev, int index)
>  {
>  	struct vkms_output *output = &vkmsdev->output;
>  	struct drm_device *dev = &vkmsdev->drm;
> -	struct drm_connector *connector = &output->connector;
> -	struct drm_encoder *encoder = &output->encoder;
> -	struct drm_crtc *crtc = &output->crtc;
> +	struct drm_connector *connector;
> +	struct drm_encoder *encoder;
> +	struct vkms_crtc *vkms_crtc;
>  	struct vkms_plane *primary, *cursor = NULL;
>  	int ret;
>  	int writeback;
>  	unsigned int n;
>  
> -	primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY, index);
> +	primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY);
>  	if (IS_ERR(primary))
>  		return PTR_ERR(primary);
>  
>  	if (vkmsdev->config.overlay) {
>  		for (n = 0; n < NUM_OVERLAY_PLANES; n++) {
> -			ret = vkms_add_overlay_plane(vkmsdev, index, crtc);
> -			if (ret)
> -				return ret;
> +			struct vkms_plane *overlay = vkms_plane_init(
> +				vkmsdev, DRM_PLANE_TYPE_OVERLAY);
> +			if (IS_ERR(overlay)) {
> +				ret = PTR_ERR(overlay);
> +				goto err_planes;
> +			}
>  		}
>  	}
>  
>  	if (vkmsdev->config.cursor) {
> -		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR, index);
> -		if (IS_ERR(cursor))
> -			return PTR_ERR(cursor);
> +		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR);
> +		if (IS_ERR(cursor)) {
> +			ret = PTR_ERR(cursor);
> +			goto err_planes;
> +		}
>  	}
>  
> -	ret = vkms_crtc_init(dev, crtc, &primary->base, &cursor->base);
> -	if (ret)
> -		return ret;
> +	vkms_crtc = vkms_crtc_init(vkmsdev, &primary->base,
> +				   cursor ? &cursor->base : NULL);
> +	if (IS_ERR(vkms_crtc)) {
> +		DRM_ERROR("Failed to init crtc\n");
> +		ret = PTR_ERR(vkms_crtc);
> +		goto err_planes;
> +	}
>  
> -	ret = drm_connector_init(dev, connector, &vkms_connector_funcs,
> -				 DRM_MODE_CONNECTOR_VIRTUAL);
> -	if (ret) {
> +	for (int i = 0; i < vkmsdev->output.num_planes; i++) {
> +		vkmsdev->output.planes[i].base.possible_crtcs |=
> +			drm_crtc_mask(&vkms_crtc->base);
> +	}
> +
> +	connector = vkms_connector_init(vkmsdev);
> +	if (IS_ERR(connector)) {
>  		DRM_ERROR("Failed to init connector\n");
> +		ret = PTR_ERR(connector);
>  		goto err_connector;
>  	}
>  
> -	drm_connector_helper_add(connector, &vkms_conn_helper_funcs);
> -
> -	ret = drm_encoder_init(dev, encoder, &vkms_encoder_funcs,
> -			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> -	if (ret) {
> +	encoder = vkms_encoder_init(vkmsdev);
> +	if (IS_ERR(encoder)) {
>  		DRM_ERROR("Failed to init encoder\n");
> +		ret = PTR_ERR(encoder);
>  		goto err_encoder;
>  	}
> -	encoder->possible_crtcs = 1;
> +	encoder->possible_crtcs |= drm_crtc_mask(&vkms_crtc->base);
>  
>  	ret = drm_connector_attach_encoder(connector, encoder);
>  	if (ret) {
> @@ -104,7 +150,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
>  	}
>  
>  	if (vkmsdev->config.writeback) {
> -		writeback = vkms_enable_writeback_connector(vkmsdev);
> +		writeback = vkms_enable_writeback_connector(vkmsdev, vkms_crtc);
>  		if (writeback)
>  			DRM_ERROR("Failed to init writeback connector\n");
>  	}
> @@ -120,7 +166,14 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
>  	drm_connector_cleanup(connector);
>  
>  err_connector:
> -	drm_crtc_cleanup(crtc);
> +	drm_crtc_cleanup(&vkms_crtc->base);
> +
> +err_planes:
> +	for (int i = 0; i < output->num_planes; i++) {
> +		drm_plane_cleanup(&output->planes[i].base);
> +	}
> +
> +	memset(output, 0, sizeof(*output));
>  
>  	return ret;
>  }
> diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c
> index e5c625ab8e3e..f69621822c1b 100644
> --- a/drivers/gpu/drm/vkms/vkms_plane.c
> +++ b/drivers/gpu/drm/vkms/vkms_plane.c
> @@ -8,6 +8,8 @@
>  #include <drm/drm_fourcc.h>
>  #include <drm/drm_gem_atomic_helper.h>
>  #include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_plane_helper.h>
>  
>  #include "vkms_drv.h"
>  #include "vkms_formats.h"
> @@ -65,6 +67,20 @@ static void vkms_plane_destroy_state(struct drm_plane *plane,
>  	kfree(vkms_state);
>  }
>  
> +static void vkms_plane_destroy(struct drm_plane *plane)
> +{
> +	struct vkms_plane *vkms_plane =
> +		container_of(plane, struct vkms_plane, base);
> +
> +	if (plane->state) {
> +		vkms_plane_destroy_state(plane, plane->state);
> +		plane->state = NULL;
> +	}
> +
> +	drm_plane_cleanup(plane);
> +	memset(vkms_plane, 0, sizeof(struct vkms_plane));
> +}
> +
>  static void vkms_plane_reset(struct drm_plane *plane)
>  {
>  	struct vkms_plane_state *vkms_state;
> @@ -84,11 +100,12 @@ static void vkms_plane_reset(struct drm_plane *plane)
>  }
>  
>  static const struct drm_plane_funcs vkms_plane_funcs = {
> -	.update_plane		= drm_atomic_helper_update_plane,
> -	.disable_plane		= drm_atomic_helper_disable_plane,
> -	.reset			= vkms_plane_reset,
> +	.update_plane = drm_atomic_helper_update_plane,
> +	.disable_plane = drm_atomic_helper_disable_plane,
> +	.destroy = vkms_plane_destroy,
> +	.reset = vkms_plane_reset,
>  	.atomic_duplicate_state = vkms_plane_duplicate_state,
> -	.atomic_destroy_state	= vkms_plane_destroy_state,
> +	.atomic_destroy_state = vkms_plane_destroy_state,
>  };
>  
>  static void vkms_plane_atomic_update(struct drm_plane *plane,
> @@ -198,17 +215,22 @@ static const struct drm_plane_helper_funcs vkms_plane_helper_funcs = {
>  };
>  
>  struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
> -				   enum drm_plane_type type, int index)
> +				   enum drm_plane_type type)
>  {
>  	struct drm_device *dev = &vkmsdev->drm;
> +	struct vkms_output *output = &vkmsdev->output;
>  	struct vkms_plane *plane;
> +	int ret;
>  
> -	plane = drmm_universal_plane_alloc(dev, struct vkms_plane, base, 1 << index,
> -					   &vkms_plane_funcs,
> -					   vkms_formats, ARRAY_SIZE(vkms_formats),
> -					   NULL, type, NULL);
> -	if (IS_ERR(plane))
> -		return plane;
> +	if (output->num_planes >= VKMS_MAX_PLANES)
> +		return ERR_PTR(-ENOMEM);
> +
> +	plane = &output->planes[output->num_planes++];
> +	ret = drm_universal_plane_init(dev, &plane->base, 0, &vkms_plane_funcs,
> +				       vkms_formats, ARRAY_SIZE(vkms_formats),
> +				       NULL, type, NULL);
> +	if (ret)
> +		return ERR_PTR(ret);
>  
>  	drm_plane_helper_add(&plane->base, &vkms_plane_helper_funcs);
>  
> diff --git a/drivers/gpu/drm/vkms/vkms_writeback.c b/drivers/gpu/drm/vkms/vkms_writeback.c
> index 84a51cd281b9..b90bcbd0a0e0 100644
> --- a/drivers/gpu/drm/vkms/vkms_writeback.c
> +++ b/drivers/gpu/drm/vkms/vkms_writeback.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0+
>  
>  #include <linux/iosys-map.h>
> +#include <linux/kernel.h>
>  
>  #include <drm/drm_atomic.h>
>  #include <drm/drm_edid.h>
> @@ -101,7 +102,8 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
>  				struct drm_writeback_job *job)
>  {
>  	struct vkms_writeback_job *vkmsjob = job->priv;
> -	struct vkms_device *vkmsdev;
> +	struct vkms_crtc *vkms_crtc =
> +		container_of(connector, struct vkms_crtc, wb_connector);
>  
>  	if (!job->fb)
>  		return;
> @@ -110,8 +112,7 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
>  
>  	drm_framebuffer_put(vkmsjob->wb_frame_info.fb);
>  
> -	vkmsdev = drm_device_to_vkms_device(job->fb->dev);
> -	vkms_set_composer(&vkmsdev->output, false);
> +	vkms_set_composer(vkms_crtc, false);
>  	kfree(vkmsjob);
>  }
>  
> @@ -120,11 +121,11 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
>  {
>  	struct drm_connector_state *connector_state = drm_atomic_get_new_connector_state(state,
>  											 conn);
> -	struct vkms_device *vkmsdev = drm_device_to_vkms_device(conn->dev);
> -	struct vkms_output *output = &vkmsdev->output;
> -	struct drm_writeback_connector *wb_conn = &output->wb_connector;
> +	struct vkms_crtc *vkms_crtc =
> +		drm_crtc_to_vkms_crtc(connector_state->crtc);
> +	struct drm_writeback_connector *wb_conn = &vkms_crtc->wb_connector;
>  	struct drm_connector_state *conn_state = wb_conn->base.state;
No longer needed.
> -	struct vkms_crtc_state *crtc_state = output->composer_state;
> +	struct vkms_crtc_state *crtc_state = vkms_crtc->composer_state;
>  	struct drm_framebuffer *fb = connector_state->writeback_job->fb;
>  	u16 crtc_height = crtc_state->base.crtc->mode.vdisplay;
>  	u16 crtc_width = crtc_state->base.crtc->mode.hdisplay;
> @@ -135,18 +136,18 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
This should be connector_state.
>  	if (!conn_state)
>  		return;
>  
> -	vkms_set_composer(&vkmsdev->output, true);
> +	vkms_set_composer(vkms_crtc, true);
>  
>  	active_wb = conn_state->writeback_job->priv;
Same here, use connector_state, rather than conn_state.
>  	wb_frame_info = &active_wb->wb_frame_info;
>  
> -	spin_lock_irq(&output->composer_lock);
> +	spin_lock_irq(&vkms_crtc->composer_lock);
>  	crtc_state->active_writeback = active_wb;
>  	wb_frame_info->offset = fb->offsets[0];
>  	wb_frame_info->pitch = fb->pitches[0];
>  	wb_frame_info->cpp = fb->format->cpp[0];
>  	crtc_state->wb_pending = true;
> -	spin_unlock_irq(&output->composer_lock);
> +	spin_unlock_irq(&vkms_crtc->composer_lock);
>  	drm_writeback_queue_job(wb_conn, connector_state);
>  	active_wb->wb_write = get_line_to_frame_function(wb_format);
>  	drm_rect_init(&wb_frame_info->src, 0, 0, crtc_width, crtc_height);
> @@ -160,9 +161,10 @@ static const struct drm_connector_helper_funcs vkms_wb_conn_helper_funcs = {
>  	.atomic_commit = vkms_wb_atomic_commit,
>  };
>  
> -int vkms_enable_writeback_connector(struct vkms_device *vkmsdev)
> +int vkms_enable_writeback_connector(struct vkms_device *vkmsdev,
> +				    struct vkms_crtc *vkms_crtc)
>  {
> -	struct drm_writeback_connector *wb = &vkmsdev->output.wb_connector;
> +	struct drm_writeback_connector *wb = &vkms_crtc->wb_connector;
>  
>  	drm_connector_helper_add(&wb->base, &vkms_wb_conn_helper_funcs);
>  
> -- 
> 2.41.0.162.gfafddb0af9-goog
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 5/6] drm/vkms: Support enabling ConfigFS devices
  2023-06-23 22:23 ` [PATCH v2 5/6] drm/vkms: Support enabling ConfigFS devices Jim Shargo
@ 2023-08-15 14:01   ` Marius Vlad
  2023-08-18  5:24     ` Brandon Ross Pollack
  0 siblings, 1 reply; 21+ messages in thread
From: Marius Vlad @ 2023-08-15 14:01 UTC (permalink / raw)
  To: Jim Shargo
  Cc: mairacanal, Daniel Vetter, David Airlie, Haneen Mohammed,
	Jonathan Corbet, Maarten Lankhorst, Maxime Ripard, Melissa Wen,
	Rodrigo Siqueira, Thomas Zimmermann, linux-kernel, dri-devel,
	linux-doc

[-- Attachment #1: Type: text/plain, Size: 14078 bytes --]

Hi,

See below some minor comments:

On Fri, Jun 23, 2023 at 06:23:47PM -0400, Jim Shargo wrote:
> VKMS now supports creating and using virtual devices!
> 
> In addition to the enabling logic, this commit also prevents users from
> adding new objects once a card is registered.
> 
> Signed-off-by: Jim Shargo <jshargo@chromium.org>
> ---
>  drivers/gpu/drm/vkms/vkms_configfs.c |  37 +++--
>  drivers/gpu/drm/vkms/vkms_crtc.c     |   4 +-
>  drivers/gpu/drm/vkms/vkms_drv.c      |   3 +-
>  drivers/gpu/drm/vkms/vkms_drv.h      |   2 +-
>  drivers/gpu/drm/vkms/vkms_output.c   | 201 +++++++++++++++++++++++----
>  5 files changed, 202 insertions(+), 45 deletions(-)
> 
> diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
> index 544024735d19..f5eed6d23dcd 100644
> --- a/drivers/gpu/drm/vkms/vkms_configfs.c
> +++ b/drivers/gpu/drm/vkms/vkms_configfs.c
> @@ -504,29 +504,40 @@ static ssize_t device_enabled_store(struct config_item *item, const char *buf,
>  {
>  	struct vkms_configfs *configfs = item_to_configfs(item);
>  	struct vkms_device *device;
> -	int value, ret;
> +	int enabled, ret;
>  
> -	ret = kstrtoint(buf, 0, &value);
> +	ret = kstrtoint(buf, 0, &enabled);
>  	if (ret)
>  		return ret;
>  
> -	if (value != 1)
> -		return -EINVAL;
> -
> -	mutex_lock(&configfs->lock);
> -
> -	if (configfs->vkms_device) {
> +	if (enabled == 0) {
> +		mutex_lock(&configfs->lock);
> +		if (configfs->vkms_device) {
> +			vkms_remove_device(configfs->vkms_device);
> +			configfs->vkms_device = NULL;
> +		}
>  		mutex_unlock(&configfs->lock);
> +
>  		return len;
>  	}
>  
> -	device = vkms_add_device(configfs);
> -	mutex_unlock(&configfs->lock);
> +	if (enabled == 1) {
> +		mutex_lock(&configfs->lock);
> +		if (!configfs->vkms_device) {
> +			device = vkms_add_device(configfs);
> +			if (IS_ERR(device)) {
> +				mutex_unlock(&configfs->lock);
> +				return -PTR_ERR(device);
> +			}
> +
> +			configfs->vkms_device = device;
> +		}
> +		mutex_unlock(&configfs->lock);
>  
> -	if (IS_ERR(device))
> -		return -PTR_ERR(device);
> +		return len;
> +	}
>  
> -	return len;
> +	return -EINVAL;
>  }
>  
>  CONFIGFS_ATTR(device_, enabled);
> diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
> index d91e49c53adc..5ebb5264f6ef 100644
> --- a/drivers/gpu/drm/vkms/vkms_crtc.c
> +++ b/drivers/gpu/drm/vkms/vkms_crtc.c
> @@ -278,7 +278,7 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
>  
>  struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
>  				 struct drm_plane *primary,
> -				 struct drm_plane *cursor)
> +				 struct drm_plane *cursor, const char *name)
>  {
>  	struct drm_device *dev = &vkmsdev->drm;
>  	struct vkms_crtc *vkms_crtc;
> @@ -290,7 +290,7 @@ struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
>  	vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
>  
>  	ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, primary, cursor,
> -					 &vkms_crtc_funcs, NULL);
> +					 &vkms_crtc_funcs, name);
>  	if (ret) {
>  		DRM_ERROR("Failed to init CRTC\n");
>  		goto out_error;
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
> index 1b5b7143792f..314a04659c5f 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.c
> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
> @@ -210,7 +210,7 @@ static int vkms_platform_probe(struct platform_device *pdev)
>  	ret = drm_dev_register(&vkms_device->drm, 0);
>  	if (ret) {
>  		DRM_ERROR("Unable to register device with id %d\n", pdev->id);
> -		return ret;
> +		goto out_release_group;
>  	}
>  
>  	drm_fbdev_generic_setup(&vkms_device->drm, 0);
> @@ -256,6 +256,7 @@ struct vkms_device *vkms_add_device(struct vkms_configfs *configfs)
>  			dev, &vkms_platform_driver.driver))) {
>  		pdev = to_platform_device(dev);
>  		max_id = max(max_id, pdev->id);
> +		put_device(dev);
>  	}
>  
>  	pdev = platform_device_register_data(NULL, DRIVER_NAME, max_id + 1,
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
> index 3634eeeb4548..3d592d085f49 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.h
> +++ b/drivers/gpu/drm/vkms/vkms_drv.h
> @@ -239,7 +239,7 @@ 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);
> +				 struct drm_plane *cursor, const char *name);
>  
>  int vkms_output_init(struct vkms_device *vkmsdev);
>  int vkms_output_init_default(struct vkms_device *vkmsdev);
> diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
> index c9e6c653de19..806ff01954ad 100644
> --- a/drivers/gpu/drm/vkms/vkms_output.c
> +++ b/drivers/gpu/drm/vkms/vkms_output.c
> @@ -2,8 +2,10 @@
>  
>  #include <drm/drm_atomic_helper.h>
>  #include <drm/drm_connector.h>
> +#include <drm/drm_crtc.h>
>  #include <drm/drm_edid.h>
>  #include <drm/drm_encoder.h>
> +#include <drm/drm_plane.h>
>  #include <drm/drm_probe_helper.h>
>  #include <drm/drm_simple_kms_helper.h>
>  
> @@ -82,7 +84,6 @@ static struct drm_encoder *vkms_encoder_init(struct vkms_device *vkms_device)
>  
>  int vkms_output_init_default(struct vkms_device *vkmsdev)
>  {
> -	struct vkms_output *output = &vkmsdev->output;
>  	struct drm_device *dev = &vkmsdev->drm;
>  	struct drm_connector *connector;
>  	struct drm_encoder *encoder;
> @@ -101,8 +102,7 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>  			struct vkms_plane *overlay = vkms_plane_init(
>  				vkmsdev, DRM_PLANE_TYPE_OVERLAY);
>  			if (IS_ERR(overlay)) {
> -				ret = PTR_ERR(overlay);
> -				goto err_planes;
> +				return PTR_ERR(overlay);
>  			}
>  		}
>  	}
> @@ -110,17 +110,16 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>  	if (vkmsdev->config.cursor) {
>  		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR);
>  		if (IS_ERR(cursor)) {
> -			ret = PTR_ERR(cursor);
> -			goto err_planes;
> +			return PTR_ERR(cursor);
>  		}
>  	}
>  
>  	vkms_crtc = vkms_crtc_init(vkmsdev, &primary->base,
> -				   cursor ? &cursor->base : NULL);
> +				   cursor ? &cursor->base : NULL,
> +				   "crtc-default");
>  	if (IS_ERR(vkms_crtc)) {
>  		DRM_ERROR("Failed to init crtc\n");
> -		ret = PTR_ERR(vkms_crtc);
> -		goto err_planes;
> +		return PTR_ERR(vkms_crtc);
>  	}
>  
>  	for (int i = 0; i < vkmsdev->output.num_planes; i++) {
> @@ -131,22 +130,20 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>  	connector = vkms_connector_init(vkmsdev);
>  	if (IS_ERR(connector)) {
>  		DRM_ERROR("Failed to init connector\n");
> -		ret = PTR_ERR(connector);
> -		goto err_connector;
> +		return PTR_ERR(connector);
>  	}
>  
>  	encoder = vkms_encoder_init(vkmsdev);
>  	if (IS_ERR(encoder)) {
>  		DRM_ERROR("Failed to init encoder\n");
> -		ret = PTR_ERR(encoder);
> -		goto err_encoder;
> +		return PTR_ERR(encoder);
>  	}
>  	encoder->possible_crtcs |= drm_crtc_mask(&vkms_crtc->base);
>  
>  	ret = drm_connector_attach_encoder(connector, encoder);
>  	if (ret) {
>  		DRM_ERROR("Failed to attach connector to encoder\n");
> -		goto err_attach;
> +		return ret;
>  	}
>  
>  	if (vkmsdev->config.writeback) {
> @@ -158,27 +155,175 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>  	drm_mode_config_reset(dev);
>  
>  	return 0;
> +}
>  
> -err_attach:
> -	drm_encoder_cleanup(encoder);
> +static bool is_object_linked(struct vkms_config_links *links, unsigned long idx)
> +{
> +	return links->linked_object_bitmap & (1 << idx);
> +}
>  
> -err_encoder:
> -	drm_connector_cleanup(connector);
> +int vkms_output_init(struct vkms_device *vkmsdev)
> +{
> +	struct drm_device *dev = &vkmsdev->drm;
> +	struct vkms_configfs *configfs = vkmsdev->configfs;
> +	struct vkms_output *output = &vkmsdev->output;
> +	struct plane_map {
> +		struct vkms_config_plane *config_plane;
> +		struct vkms_plane *plane;
> +	} plane_map[VKMS_MAX_PLANES] = { 0 };
> +	struct encoder_map {
> +		struct vkms_config_encoder *config_encoder;
> +		struct drm_encoder *encoder;
> +	} encoder_map[VKMS_MAX_OUTPUT_OBJECTS] = { 0 };
> +	struct config_item *item;
> +	int map_idx = 0;
> +
> +	list_for_each_entry(item, &configfs->planes_group.cg_children,
> +			    ci_entry) {
> +		struct vkms_config_plane *config_plane =
> +			item_to_config_plane(item);
> +		struct vkms_plane *plane =
> +			vkms_plane_init(vkmsdev, config_plane->type);
> +
> +		if (IS_ERR(plane)) {
> +			DRM_ERROR("Unable to init plane from config: %s",
> +				  item->ci_name);
> +			return PTR_ERR(plane);
> +		}
>  
> -err_connector:
> -	drm_crtc_cleanup(&vkms_crtc->base);
> +		plane_map[map_idx].config_plane = config_plane;
> +		plane_map[map_idx].plane = plane;
> +		map_idx += 1;
> +	}
>  
> -err_planes:
> -	for (int i = 0; i < output->num_planes; i++) {
> -		drm_plane_cleanup(&output->planes[i].base);
> +	map_idx = 0;
> +	list_for_each_entry(item, &configfs->encoders_group.cg_children,
> +			    ci_entry) {
> +		struct vkms_config_encoder *config_encoder =
> +			item_to_config_encoder(item);
> +		struct drm_encoder *encoder = vkms_encoder_init(vkmsdev);
> +
> +		if (IS_ERR(encoder)) {
> +			DRM_ERROR("Failed to init config encoder: %s",
> +				  item->ci_name);
> +			return PTR_ERR(encoder);
> +		}
> +		encoder_map[map_idx].config_encoder = config_encoder;
A two connector configuration without an encoder would have the
potential of blowing up if we don't create a second_encoder.
> +		encoder_map[map_idx].encoder = encoder;
> +		map_idx += 1;
>  	}
>  
> -	memset(output, 0, sizeof(*output));
> +	list_for_each_entry(item, &configfs->connectors_group.cg_children,
> +			    ci_entry) {
> +		struct vkms_config_connector *config_connector =
> +			item_to_config_connector(item);
> +		struct drm_connector *connector = vkms_connector_init(vkmsdev);
>  
> -	return ret;
> -}
> +		if (IS_ERR(connector)) {
> +			DRM_ERROR("Failed to init connector from config: %s",
> +				  item->ci_name);
> +			return PTR_ERR(connector);
> +		}
>  
> -int vkms_output_init(struct vkms_device *vkmsdev)
> -{
> -	return -ENOTSUPP;
> +		for (int j = 0; j < output->num_connectors; j++) {
> +			struct encoder_map *encoder = &encoder_map[j];
> +
> +			if (is_object_linked(
> +				    &config_connector->possible_encoders,
> +				    encoder->config_encoder
> +					    ->encoder_config_idx)) {
Here encoder->config_encoder for two connectors but a single encoder
will give back a empty encoder.
> +				drm_connector_attach_encoder(connector,
> +							     encoder->encoder);
> +			}
> +		}
> +	}
> +
> +	list_for_each_entry(item, &configfs->crtcs_group.cg_children,
> +			    ci_entry) {
> +		struct vkms_config_crtc *config_crtc =
> +			item_to_config_crtc(item);
> +		struct vkms_crtc *vkms_crtc;
> +		struct drm_plane *primary = NULL, *cursor = NULL;
> +
> +		for (int j = 0; j < output->num_planes; j++) {
> +			struct plane_map *plane_entry = &plane_map[j];
> +			struct drm_plane *plane = &plane_entry->plane->base;
> +
> +			if (!is_object_linked(
> +				    &plane_entry->config_plane->possible_crtcs,
> +				    config_crtc->crtc_config_idx)) {
> +				continue;
> +			}
> +
> +			if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
> +				if (primary) {
> +					DRM_WARN(
> +						"Too many primary planes found for crtc %s.",
> +						item->ci_name);
> +					return EINVAL;
> +				}
> +				primary = plane;
> +			} else if (plane->type == DRM_PLANE_TYPE_CURSOR) {
> +				if (cursor) {
> +					DRM_WARN(
> +						"Too many cursor planes found for crtc %s.",
> +						item->ci_name);
> +					return EINVAL;
> +				}
> +				cursor = plane;
> +			}
> +		}
> +
> +		if (!primary) {
> +			DRM_WARN("No primary plane configured for crtc %s",
> +				 item->ci_name);
> +			return EINVAL;
> +		}
> +
> +		vkms_crtc =
> +			vkms_crtc_init(vkmsdev, primary, cursor, item->ci_name);
> +		if (IS_ERR(vkms_crtc)) {
> +			DRM_WARN("Unable to init crtc from config: %s",
> +				 item->ci_name);
> +			return PTR_ERR(vkms_crtc);
> +		}
> +
> +		for (int j = 0; j < output->num_planes; j++) {
> +			struct plane_map *plane_entry = &plane_map[j];
> +
> +			if (!plane_entry->plane)
> +				break;
> +
> +			if (is_object_linked(
> +				    &plane_entry->config_plane->possible_crtcs,
> +				    config_crtc->crtc_config_idx)) {
> +				plane_entry->plane->base.possible_crtcs |=
> +					drm_crtc_mask(&vkms_crtc->base);
> +			}
> +		}
> +
> +		for (int j = 0; j < output->num_encoders; j++) {
> +			struct encoder_map *encoder_entry = &encoder_map[j];
> +
> +			if (is_object_linked(&encoder_entry->config_encoder
> +						      ->possible_crtcs,
> +					     config_crtc->crtc_config_idx)) {
> +				encoder_entry->encoder->possible_crtcs |=
> +					drm_crtc_mask(&vkms_crtc->base);
> +			}
> +		}
> +
> +		if (vkmsdev->config.writeback) {
> +			int ret = vkms_enable_writeback_connector(vkmsdev,
> +								  vkms_crtc);
> +			if (ret)
> +				DRM_WARN(
> +					"Failed to init writeback connector for config crtc: %s. Error code %d",
> +					item->ci_name, ret);
> +		}
> +	}
I think we might be needing here a test for missing symlinks - invalid
configurations, like unused crtcs, encoders, connectors. I
suppose anything that's not matched with is_object_linked(),
such we avoid invalid configuration found by drm_mode_config_validate() 
and inform users about missing them.

An example here would be to create a second encoder, connector, crtc and
not symlink them to their possible_encoders,possible_crtcs mask. An
i-g-t test for this very thing would be needed to stress different kind
of combinations.

> +
> +	drm_mode_config_reset(dev);
> +
> +	return 0;
>  }
> -- 
> 2.41.0.162.gfafddb0af9-goog
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device
  2023-08-15 11:29   ` Marius Vlad
@ 2023-08-18  3:36     ` Brandon Pollack
  2023-08-18  5:26       ` Brandon Ross Pollack
  0 siblings, 1 reply; 21+ messages in thread
From: Brandon Pollack @ 2023-08-18  3:36 UTC (permalink / raw)
  To: marius.vlad
  Cc: corbet, dri-devel, hamohammed.sa, jshargo, linux-doc,
	linux-kernel, mairacanal, melissa.srw, mripard,
	rodrigosiqueiramelo, tzimmermann

Thanks for taking the time, everyone! Sorry it took so long, we had some
internal shuffling etc going on and I was building out what we needed these
chagnes for in the first place, this will be the first of a few replies
followed by a new version of the series to be sent out.

First up is a respons to Maria, Marius to follow.

---

Maria,

> -	if (vkms->output.composer_workq)
> -		destroy_workqueue(vkms->output.composer_workq);
> +	for (int i = 0; i < vkms->output.num_crtcs; i++)
> +		destroy_workqueue(vkms->output.crtcs[i].composer_workq);

I don't believe there is any need for a null check.  If you look in the
crtc_init, it is zero'd before returning any errors and that is the only place
it is set.  I don't believe that release can be called by an interrupt/async
(and if it did it would need a mutex/lock anyway).

>
>   static const struct drm_plane_funcs vkms_plane_funcs = {
> -	.update_plane		= drm_atomic_helper_update_plane,
> -	.disable_plane		= drm_atomic_helper_disable_plane,
> -	.reset			= vkms_plane_reset,

Yeah these do seem weirdly formatted on devices that don't treat tabs well.
The default formatter on my editor has a few suggestions for this file, but
they are all optional.  I'll send an extra patch that formats stuff and see
what people think, but ill make it seperate after all this is done. 
For now I reverted this.

>> -	if (IS_ERR(plane))
>> -		return plane;
>> +	if (output->num_planes >= VKMS_MAX_PLANES)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	plane = &output->planes[output->num_planes++];
>> +	ret = drm_universal_plane_init(dev, &plane->base, 0, &vkms_plane_funcs,
>> +				       vkms_formats, ARRAY_SIZE(vkms_formats),
>> +				       NULL, type, NULL);
>
>Wouldn't be possible to use drmm_universal_plane_alloc?

Maybe, but the *_init pattern allows these things to be inline in the struct as
they are now, and consistent with the other drm kernel objects in the
vkms_output struct.  There are also a few other places we could use drmm,
surely, but to limit the scope/risk why don't we do that as a followup?

---

Marius,

Yeah those values could safely be completely removed.  Good catch :)

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 5/6] drm/vkms: Support enabling ConfigFS devices
  2023-08-15 14:01   ` Marius Vlad
@ 2023-08-18  5:24     ` Brandon Ross Pollack
  2023-08-18  6:54       ` Marius Vlad
  0 siblings, 1 reply; 21+ messages in thread
From: Brandon Ross Pollack @ 2023-08-18  5:24 UTC (permalink / raw)
  To: Marius Vlad, Jim Shargo
  Cc: Haneen Mohammed, Thomas Zimmermann, Rodrigo Siqueira,
	Jonathan Corbet, linux-doc, linux-kernel, Maxime Ripard,
	Melissa Wen, mairacanal, dri-devel


On 8/15/23 23:01, Marius Vlad wrote:
> Hi,
>
> See below some minor comments:
>
> On Fri, Jun 23, 2023 at 06:23:47PM -0400, Jim Shargo wrote:
>> VKMS now supports creating and using virtual devices!
>>
>> In addition to the enabling logic, this commit also prevents users from
>> adding new objects once a card is registered.
>>
>> Signed-off-by: Jim Shargo <jshargo@chromium.org>
>> ---
>>   drivers/gpu/drm/vkms/vkms_configfs.c |  37 +++--
>>   drivers/gpu/drm/vkms/vkms_crtc.c     |   4 +-
>>   drivers/gpu/drm/vkms/vkms_drv.c      |   3 +-
>>   drivers/gpu/drm/vkms/vkms_drv.h      |   2 +-
>>   drivers/gpu/drm/vkms/vkms_output.c   | 201 +++++++++++++++++++++++----
>>   5 files changed, 202 insertions(+), 45 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
>> index 544024735d19..f5eed6d23dcd 100644
>> --- a/drivers/gpu/drm/vkms/vkms_configfs.c
>> +++ b/drivers/gpu/drm/vkms/vkms_configfs.c
>> @@ -504,29 +504,40 @@ static ssize_t device_enabled_store(struct config_item *item, const char *buf,
>>   {
>>   	struct vkms_configfs *configfs = item_to_configfs(item);
>>   	struct vkms_device *device;
>> -	int value, ret;
>> +	int enabled, ret;
>>   
>> -	ret = kstrtoint(buf, 0, &value);
>> +	ret = kstrtoint(buf, 0, &enabled);
>>   	if (ret)
>>   		return ret;
>>   
>> -	if (value != 1)
>> -		return -EINVAL;
>> -
>> -	mutex_lock(&configfs->lock);
>> -
>> -	if (configfs->vkms_device) {
>> +	if (enabled == 0) {
>> +		mutex_lock(&configfs->lock);
>> +		if (configfs->vkms_device) {
>> +			vkms_remove_device(configfs->vkms_device);
>> +			configfs->vkms_device = NULL;
>> +		}
>>   		mutex_unlock(&configfs->lock);
>> +
>>   		return len;
>>   	}
>>   
>> -	device = vkms_add_device(configfs);
>> -	mutex_unlock(&configfs->lock);
>> +	if (enabled == 1) {
>> +		mutex_lock(&configfs->lock);
>> +		if (!configfs->vkms_device) {
>> +			device = vkms_add_device(configfs);
>> +			if (IS_ERR(device)) {
>> +				mutex_unlock(&configfs->lock);
>> +				return -PTR_ERR(device);
>> +			}
>> +
>> +			configfs->vkms_device = device;
>> +		}
>> +		mutex_unlock(&configfs->lock);
>>   
>> -	if (IS_ERR(device))
>> -		return -PTR_ERR(device);
>> +		return len;
>> +	}
>>   
>> -	return len;
>> +	return -EINVAL;
>>   }
>>   
>>   CONFIGFS_ATTR(device_, enabled);
>> diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
>> index d91e49c53adc..5ebb5264f6ef 100644
>> --- a/drivers/gpu/drm/vkms/vkms_crtc.c
>> +++ b/drivers/gpu/drm/vkms/vkms_crtc.c
>> @@ -278,7 +278,7 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
>>   
>>   struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
>>   				 struct drm_plane *primary,
>> -				 struct drm_plane *cursor)
>> +				 struct drm_plane *cursor, const char *name)
>>   {
>>   	struct drm_device *dev = &vkmsdev->drm;
>>   	struct vkms_crtc *vkms_crtc;
>> @@ -290,7 +290,7 @@ struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
>>   	vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
>>   
>>   	ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, primary, cursor,
>> -					 &vkms_crtc_funcs, NULL);
>> +					 &vkms_crtc_funcs, name);
>>   	if (ret) {
>>   		DRM_ERROR("Failed to init CRTC\n");
>>   		goto out_error;
>> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
>> index 1b5b7143792f..314a04659c5f 100644
>> --- a/drivers/gpu/drm/vkms/vkms_drv.c
>> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
>> @@ -210,7 +210,7 @@ static int vkms_platform_probe(struct platform_device *pdev)
>>   	ret = drm_dev_register(&vkms_device->drm, 0);
>>   	if (ret) {
>>   		DRM_ERROR("Unable to register device with id %d\n", pdev->id);
>> -		return ret;
>> +		goto out_release_group;
>>   	}
>>   
>>   	drm_fbdev_generic_setup(&vkms_device->drm, 0);
>> @@ -256,6 +256,7 @@ struct vkms_device *vkms_add_device(struct vkms_configfs *configfs)
>>   			dev, &vkms_platform_driver.driver))) {
>>   		pdev = to_platform_device(dev);
>>   		max_id = max(max_id, pdev->id);
>> +		put_device(dev);
>>   	}
>>   
>>   	pdev = platform_device_register_data(NULL, DRIVER_NAME, max_id + 1,
>> diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
>> index 3634eeeb4548..3d592d085f49 100644
>> --- a/drivers/gpu/drm/vkms/vkms_drv.h
>> +++ b/drivers/gpu/drm/vkms/vkms_drv.h
>> @@ -239,7 +239,7 @@ 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);
>> +				 struct drm_plane *cursor, const char *name);
>>   
>>   int vkms_output_init(struct vkms_device *vkmsdev);
>>   int vkms_output_init_default(struct vkms_device *vkmsdev);
>> diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
>> index c9e6c653de19..806ff01954ad 100644
>> --- a/drivers/gpu/drm/vkms/vkms_output.c
>> +++ b/drivers/gpu/drm/vkms/vkms_output.c
>> @@ -2,8 +2,10 @@
>>   
>>   #include <drm/drm_atomic_helper.h>
>>   #include <drm/drm_connector.h>
>> +#include <drm/drm_crtc.h>
>>   #include <drm/drm_edid.h>
>>   #include <drm/drm_encoder.h>
>> +#include <drm/drm_plane.h>
>>   #include <drm/drm_probe_helper.h>
>>   #include <drm/drm_simple_kms_helper.h>
>>   
>> @@ -82,7 +84,6 @@ static struct drm_encoder *vkms_encoder_init(struct vkms_device *vkms_device)
>>   
>>   int vkms_output_init_default(struct vkms_device *vkmsdev)
>>   {
>> -	struct vkms_output *output = &vkmsdev->output;
>>   	struct drm_device *dev = &vkmsdev->drm;
>>   	struct drm_connector *connector;
>>   	struct drm_encoder *encoder;
>> @@ -101,8 +102,7 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>>   			struct vkms_plane *overlay = vkms_plane_init(
>>   				vkmsdev, DRM_PLANE_TYPE_OVERLAY);
>>   			if (IS_ERR(overlay)) {
>> -				ret = PTR_ERR(overlay);
>> -				goto err_planes;
>> +				return PTR_ERR(overlay);
>>   			}
>>   		}
>>   	}
>> @@ -110,17 +110,16 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>>   	if (vkmsdev->config.cursor) {
>>   		cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR);
>>   		if (IS_ERR(cursor)) {
>> -			ret = PTR_ERR(cursor);
>> -			goto err_planes;
>> +			return PTR_ERR(cursor);
>>   		}
>>   	}
>>   
>>   	vkms_crtc = vkms_crtc_init(vkmsdev, &primary->base,
>> -				   cursor ? &cursor->base : NULL);
>> +				   cursor ? &cursor->base : NULL,
>> +				   "crtc-default");
>>   	if (IS_ERR(vkms_crtc)) {
>>   		DRM_ERROR("Failed to init crtc\n");
>> -		ret = PTR_ERR(vkms_crtc);
>> -		goto err_planes;
>> +		return PTR_ERR(vkms_crtc);
>>   	}
>>   
>>   	for (int i = 0; i < vkmsdev->output.num_planes; i++) {
>> @@ -131,22 +130,20 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>>   	connector = vkms_connector_init(vkmsdev);
>>   	if (IS_ERR(connector)) {
>>   		DRM_ERROR("Failed to init connector\n");
>> -		ret = PTR_ERR(connector);
>> -		goto err_connector;
>> +		return PTR_ERR(connector);
>>   	}
>>   
>>   	encoder = vkms_encoder_init(vkmsdev);
>>   	if (IS_ERR(encoder)) {
>>   		DRM_ERROR("Failed to init encoder\n");
>> -		ret = PTR_ERR(encoder);
>> -		goto err_encoder;
>> +		return PTR_ERR(encoder);
>>   	}
>>   	encoder->possible_crtcs |= drm_crtc_mask(&vkms_crtc->base);
>>   
>>   	ret = drm_connector_attach_encoder(connector, encoder);
>>   	if (ret) {
>>   		DRM_ERROR("Failed to attach connector to encoder\n");
>> -		goto err_attach;
>> +		return ret;
>>   	}
>>   
>>   	if (vkmsdev->config.writeback) {
>> @@ -158,27 +155,175 @@ int vkms_output_init_default(struct vkms_device *vkmsdev)
>>   	drm_mode_config_reset(dev);
>>   
>>   	return 0;
>> +}
>>   
>> -err_attach:
>> -	drm_encoder_cleanup(encoder);
>> +static bool is_object_linked(struct vkms_config_links *links, unsigned long idx)
>> +{
>> +	return links->linked_object_bitmap & (1 << idx);
>> +}
>>   
>> -err_encoder:
>> -	drm_connector_cleanup(connector);
>> +int vkms_output_init(struct vkms_device *vkmsdev)
>> +{
>> +	struct drm_device *dev = &vkmsdev->drm;
>> +	struct vkms_configfs *configfs = vkmsdev->configfs;
>> +	struct vkms_output *output = &vkmsdev->output;
>> +	struct plane_map {
>> +		struct vkms_config_plane *config_plane;
>> +		struct vkms_plane *plane;
>> +	} plane_map[VKMS_MAX_PLANES] = { 0 };
>> +	struct encoder_map {
>> +		struct vkms_config_encoder *config_encoder;
>> +		struct drm_encoder *encoder;
>> +	} encoder_map[VKMS_MAX_OUTPUT_OBJECTS] = { 0 };
>> +	struct config_item *item;
>> +	int map_idx = 0;
>> +
>> +	list_for_each_entry(item, &configfs->planes_group.cg_children,
>> +			    ci_entry) {
>> +		struct vkms_config_plane *config_plane =
>> +			item_to_config_plane(item);
>> +		struct vkms_plane *plane =
>> +			vkms_plane_init(vkmsdev, config_plane->type);
>> +
>> +		if (IS_ERR(plane)) {
>> +			DRM_ERROR("Unable to init plane from config: %s",
>> +				  item->ci_name);
>> +			return PTR_ERR(plane);
>> +		}
>>   
>> -err_connector:
>> -	drm_crtc_cleanup(&vkms_crtc->base);
>> +		plane_map[map_idx].config_plane = config_plane;
>> +		plane_map[map_idx].plane = plane;
>> +		map_idx += 1;
>> +	}
>>   
>> -err_planes:
>> -	for (int i = 0; i < output->num_planes; i++) {
>> -		drm_plane_cleanup(&output->planes[i].base);
>> +	map_idx = 0;
>> +	list_for_each_entry(item, &configfs->encoders_group.cg_children,
>> +			    ci_entry) {
>> +		struct vkms_config_encoder *config_encoder =
>> +			item_to_config_encoder(item);
>> +		struct drm_encoder *encoder = vkms_encoder_init(vkmsdev);
>> +
>> +		if (IS_ERR(encoder)) {
>> +			DRM_ERROR("Failed to init config encoder: %s",
>> +				  item->ci_name);
>> +			return PTR_ERR(encoder);
>> +		}
>> +		encoder_map[map_idx].config_encoder = config_encoder;
> A two connector configuration without an encoder would have the
> potential of blowing up if we don't create a second_encoder.

What a catch!!! I tested this and sure enough BOOM!

Switched to limiting based on output->num_encoders as it should be.

>> +		encoder_map[map_idx].encoder = encoder;
>> +		map_idx += 1;
>>   	}
>>   
>> -	memset(output, 0, sizeof(*output));
>> +	list_for_each_entry(item, &configfs->connectors_group.cg_children,
>> +			    ci_entry) {
>> +		struct vkms_config_connector *config_connector =
>> +			item_to_config_connector(item);
>> +		struct drm_connector *connector = vkms_connector_init(vkmsdev);
>>   
>> -	return ret;
>> -}
>> +		if (IS_ERR(connector)) {
>> +			DRM_ERROR("Failed to init connector from config: %s",
>> +				  item->ci_name);
>> +			return PTR_ERR(connector);
>> +		}
>>   
>> -int vkms_output_init(struct vkms_device *vkmsdev)
>> -{
>> -	return -ENOTSUPP;
>> +		for (int j = 0; j < output->num_connectors; j++) {
>> +			struct encoder_map *encoder = &encoder_map[j];
>> +
>> +			if (is_object_linked(
>> +				    &config_connector->possible_encoders,
>> +				    encoder->config_encoder
>> +					    ->encoder_config_idx)) {
> Here encoder->config_encoder for two connectors but a single encoder
> will give back a empty encoder.
>> +				drm_connector_attach_encoder(connector,
>> +							     encoder->encoder);
>> +			}
>> +		}
>> +	}
>> +
>> +	list_for_each_entry(item, &configfs->crtcs_group.cg_children,
>> +			    ci_entry) {
>> +		struct vkms_config_crtc *config_crtc =
>> +			item_to_config_crtc(item);
>> +		struct vkms_crtc *vkms_crtc;
>> +		struct drm_plane *primary = NULL, *cursor = NULL;
>> +
>> +		for (int j = 0; j < output->num_planes; j++) {
>> +			struct plane_map *plane_entry = &plane_map[j];
>> +			struct drm_plane *plane = &plane_entry->plane->base;
>> +
>> +			if (!is_object_linked(
>> +				    &plane_entry->config_plane->possible_crtcs,
>> +				    config_crtc->crtc_config_idx)) {
>> +				continue;
>> +			}
>> +
>> +			if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
>> +				if (primary) {
>> +					DRM_WARN(
>> +						"Too many primary planes found for crtc %s.",
>> +						item->ci_name);
>> +					return EINVAL;
>> +				}
>> +				primary = plane;
>> +			} else if (plane->type == DRM_PLANE_TYPE_CURSOR) {
>> +				if (cursor) {
>> +					DRM_WARN(
>> +						"Too many cursor planes found for crtc %s.",
>> +						item->ci_name);
>> +					return EINVAL;
>> +				}
>> +				cursor = plane;
>> +			}
>> +		}
>> +
>> +		if (!primary) {
>> +			DRM_WARN("No primary plane configured for crtc %s",
>> +				 item->ci_name);
>> +			return EINVAL;
>> +		}
>> +
>> +		vkms_crtc =
>> +			vkms_crtc_init(vkmsdev, primary, cursor, item->ci_name);
>> +		if (IS_ERR(vkms_crtc)) {
>> +			DRM_WARN("Unable to init crtc from config: %s",
>> +				 item->ci_name);
>> +			return PTR_ERR(vkms_crtc);
>> +		}
>> +
>> +		for (int j = 0; j < output->num_planes; j++) {
>> +			struct plane_map *plane_entry = &plane_map[j];
>> +
>> +			if (!plane_entry->plane)
>> +				break;
>> +
>> +			if (is_object_linked(
>> +				    &plane_entry->config_plane->possible_crtcs,
>> +				    config_crtc->crtc_config_idx)) {
>> +				plane_entry->plane->base.possible_crtcs |=
>> +					drm_crtc_mask(&vkms_crtc->base);
>> +			}
>> +		}
>> +
>> +		for (int j = 0; j < output->num_encoders; j++) {
>> +			struct encoder_map *encoder_entry = &encoder_map[j];
>> +
>> +			if (is_object_linked(&encoder_entry->config_encoder
>> +						      ->possible_crtcs,
>> +					     config_crtc->crtc_config_idx)) {
>> +				encoder_entry->encoder->possible_crtcs |=
>> +					drm_crtc_mask(&vkms_crtc->base);
>> +			}
>> +		}
>> +
>> +		if (vkmsdev->config.writeback) {
>> +			int ret = vkms_enable_writeback_connector(vkmsdev,
>> +								  vkms_crtc);
>> +			if (ret)
>> +				DRM_WARN(
>> +					"Failed to init writeback connector for config crtc: %s. Error code %d",
>> +					item->ci_name, ret);
>> +		}
>> +	}
> I think we might be needing here a test for missing symlinks - invalid
> configurations, like unused crtcs, encoders, connectors. I
> suppose anything that's not matched with is_object_linked(),
> such we avoid invalid configuration found by drm_mode_config_validate()
> and inform users about missing them.
>
> An example here would be to create a second encoder, connector, crtc and
> not symlink them to their possible_encoders,possible_crtcs mask. An
> i-g-t test for this very thing would be needed to stress different kind
> of combinations.

I wonder about this.  Shouldn't a user be free to create dangling files 
to simulate some scenario?

I could see a further development to publish a warning in the log, but 
disallowing it seems overly restrictive.

Either way we could accomplish this pretty easily by adding a flag to 
each vkms object in the form of `bool is_linked` and set it when we 
detect it's linked.  Then at the end iterate through all planes, 
encoders, connectors, crtcs that can be linked and if they are not just 
publish a warning saying

kwarn: "crtc/plane/encoder $NAME is created but unlinked"

IGT tests to test that that was done make sense to me.

>> +
>> +	drm_mode_config_reset(dev);
>> +
>> +	return 0;
>>   }
>> -- 
>> 2.41.0.162.gfafddb0af9-goog
>>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device
  2023-08-18  3:36     ` Brandon Pollack
@ 2023-08-18  5:26       ` Brandon Ross Pollack
  0 siblings, 0 replies; 21+ messages in thread
From: Brandon Ross Pollack @ 2023-08-18  5:26 UTC (permalink / raw)
  To: marius.vlad
  Cc: corbet, dri-devel, hamohammed.sa, jshargo, linux-doc,
	linux-kernel, mairacanal, melissa.srw, mripard,
	rodrigosiqueiramelo, tzimmermann

Silly addition but I need to apologize for mispelling somone's name in 
my reply.

Maira, please accept my sincere apology.

On 8/18/23 12:36, Brandon Pollack wrote:
> Thanks for taking the time, everyone! Sorry it took so long, we had some
> internal shuffling etc going on and I was building out what we needed these
> chagnes for in the first place, this will be the first of a few replies
> followed by a new version of the series to be sent out.
>
> First up is a respons to Maria, Marius to follow.
>
> ---
>
> Maria,
>
>> -	if (vkms->output.composer_workq)
>> -		destroy_workqueue(vkms->output.composer_workq);
>> +	for (int i = 0; i < vkms->output.num_crtcs; i++)
>> +		destroy_workqueue(vkms->output.crtcs[i].composer_workq);
> I don't believe there is any need for a null check.  If you look in the
> crtc_init, it is zero'd before returning any errors and that is the only place
> it is set.  I don't believe that release can be called by an interrupt/async
> (and if it did it would need a mutex/lock anyway).
>
>>    static const struct drm_plane_funcs vkms_plane_funcs = {
>> -	.update_plane		= drm_atomic_helper_update_plane,
>> -	.disable_plane		= drm_atomic_helper_disable_plane,
>> -	.reset			= vkms_plane_reset,
> Yeah these do seem weirdly formatted on devices that don't treat tabs well.
> The default formatter on my editor has a few suggestions for this file, but
> they are all optional.  I'll send an extra patch that formats stuff and see
> what people think, but ill make it seperate after all this is done.
> For now I reverted this.
>
>>> -	if (IS_ERR(plane))
>>> -		return plane;
>>> +	if (output->num_planes >= VKMS_MAX_PLANES)
>>> +		return ERR_PTR(-ENOMEM);
>>> +
>>> +	plane = &output->planes[output->num_planes++];
>>> +	ret = drm_universal_plane_init(dev, &plane->base, 0, &vkms_plane_funcs,
>>> +				       vkms_formats, ARRAY_SIZE(vkms_formats),
>>> +				       NULL, type, NULL);
>> Wouldn't be possible to use drmm_universal_plane_alloc?
> Maybe, but the *_init pattern allows these things to be inline in the struct as
> they are now, and consistent with the other drm kernel objects in the
> vkms_output struct.  There are also a few other places we could use drmm,
> surely, but to limit the scope/risk why don't we do that as a followup?
>
> ---
>
> Marius,
>
> Yeah those values could safely be completely removed.  Good catch :)

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 6/6] drm/vkms: Add a module param to enable/disable the default device
  2023-06-25 18:04   ` Maira Canal
@ 2023-08-18  5:39     ` Brandon Ross Pollack
  0 siblings, 0 replies; 21+ messages in thread
From: Brandon Ross Pollack @ 2023-08-18  5:39 UTC (permalink / raw)
  To: Maira Canal, Jim Shargo, Daniel Vetter, David Airlie,
	Haneen Mohammed, Jonathan Corbet, Maarten Lankhorst,
	Maxime Ripard, Melissa Wen, Rodrigo Siqueira, Thomas Zimmermann
  Cc: dri-devel, linux-doc, linux-kernel


On 6/26/23 03:04, Maira Canal wrote:
> Hi Jim,
>
> On 6/23/23 19:23, Jim Shargo wrote:
>> In many testing circumstances, we will want to just create a new device
>> and test against that. If we create a default device, it can be annoying
>> to have to manually select the new device instead of choosing the only
>> one that exists.
>>
>> The param, enable_default, is defaulted to true to maintain backwards
>> compatibility.
>>
>> Signed-off-by: Jim Shargo <jshargo@chromium.org>
>> ---
>>   drivers/gpu/drm/vkms/vkms_drv.c | 44 ++++++++++++++++++++++-----------
>>   1 file changed, 29 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c 
>> b/drivers/gpu/drm/vkms/vkms_drv.c
>> index 314a04659c5f..1cb56c090a65 100644
>> --- a/drivers/gpu/drm/vkms/vkms_drv.c
>> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
>> @@ -42,17 +42,26 @@
>>   #define DRIVER_MAJOR    1
>>   #define DRIVER_MINOR    0
>>   +static bool enable_default_device = true;
>> +module_param_named(enable_default_device, enable_default_device, 
>> bool, 0444);
>> +MODULE_PARM_DESC(enable_default_device,
>> +         "Enable/Disable creating the default device");
>
> Wouldn't be better to just call it "enable_default"?

At the risk of being annoyingly pedantic, I actually like the original 
name better because it makes it clear it is an entire device and setup,

including the planes/encoders/connectors/crtcs needed and connecting 
them as necessary etc.  I just feel "device" here makes it clearer what 
is the default thing.

>
> Also, could you add this parameter to vkms_config debugfs file?

But of course! This is the last comment before I send out the new series.


Thank you so much for your time, looking forward to the next round of 
comments.


>
> Best Regards,
> - Maíra
>
>> +
>>   static bool enable_cursor = true;
>>   module_param_named(enable_cursor, enable_cursor, bool, 0444);
>> -MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
>> +MODULE_PARM_DESC(enable_cursor,
>> +         "Enable/Disable cursor support for the default device");
>>     static bool enable_writeback = true;
>>   module_param_named(enable_writeback, enable_writeback, bool, 0444);
>> -MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback 
>> connector support");
>> +MODULE_PARM_DESC(
>> +    enable_writeback,
>> +    "Enable/Disable writeback connector support for the default 
>> device");
>>     static bool enable_overlay;
>>   module_param_named(enable_overlay, enable_overlay, bool, 0444);
>> -MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
>> +MODULE_PARM_DESC(enable_overlay,
>> +         "Enable/Disable overlay support for the default device");
>>     DEFINE_DRM_GEM_FOPS(vkms_driver_fops);
>>   @@ -278,10 +287,7 @@ void vkms_remove_device(struct vkms_device 
>> *vkms_device)
>>   static int __init vkms_init(void)
>>   {
>>       int ret;
>> -    struct platform_device *pdev;
>> -    struct vkms_device_setup vkms_device_setup = {
>> -        .configfs = NULL,
>> -    };
>> +    struct platform_device *default_pdev = NULL;
>>         ret = platform_driver_register(&vkms_platform_driver);
>>       if (ret) {
>> @@ -289,19 +295,27 @@ static int __init vkms_init(void)
>>           return ret;
>>       }
>>   -    pdev = platform_device_register_data(NULL, DRIVER_NAME, 0,
>> -                         &vkms_device_setup,
>> -                         sizeof(vkms_device_setup));
>> -    if (IS_ERR(pdev)) {
>> -        DRM_ERROR("Unable to register default vkms device\n");
>> -        platform_driver_unregister(&vkms_platform_driver);
>> -        return PTR_ERR(pdev);
>> +    if (enable_default_device) {
>> +        struct vkms_device_setup vkms_device_setup = {
>> +            .configfs = NULL,
>> +        };
>> +
>> +        default_pdev = platform_device_register_data(
>> +            NULL, DRIVER_NAME, 0, &vkms_device_setup,
>> +            sizeof(vkms_device_setup));
>> +        if (IS_ERR(default_pdev)) {
>> +            DRM_ERROR("Unable to register default vkms device\n");
>> + platform_driver_unregister(&vkms_platform_driver);
>> +            return PTR_ERR(default_pdev);
>> +        }
>>       }
>>         ret = vkms_init_configfs();
>>       if (ret) {
>>           DRM_ERROR("Unable to initialize configfs\n");
>> -        platform_device_unregister(pdev);
>> +        if (default_pdev)
>> +            platform_device_unregister(default_pdev);
>> +
>>           platform_driver_unregister(&vkms_platform_driver);
>>       }
>
> From mboxrd@z Thu Jan  1 00:00:00 1970
> Return-Path: <dri-devel-bounces@lists.freedesktop.org>
> X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on
>     aws-us-west-2-korg-lkml-1.web.codeaurora.org
> Received: from gabe.freedesktop.org (gabe.freedesktop.org 
> [131.252.210.177])
>     (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 
> bits))
>     (No client certificate requested)
>     by smtp.lore.kernel.org (Postfix) with ESMTPS id AB561C0015E
>     for <dri-devel@archiver.kernel.org>; Sun, 25 Jun 2023 18:04:15 
> +0000 (UTC)
> Received: from gabe.freedesktop.org (localhost [127.0.0.1])
>     by gabe.freedesktop.org (Postfix) with ESMTP id D153310E18C;
>     Sun, 25 Jun 2023 18:04:14 +0000 (UTC)
> Received: from mx1.riseup.net (mx1.riseup.net [198.252.153.129])
> by gabe.freedesktop.org (Postfix) with ESMTPS id C6BE910E187
> for <dri-devel@lists.freedesktop.org>; Sun, 25 Jun 2023 18:04:12 +0000 
> (UTC)
> Received: from fews01-sea.riseup.net (fews01-sea-pn.riseup.net 
> [10.0.1.109])
> (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
> key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest 
> SHA256)
> (No client certificate requested)
> by mx1.riseup.net (Postfix) with ESMTPS id 4QpzPl6CHTzDrNy;
> Sun, 25 Jun 2023 18:04:11 +0000 (UTC)
> DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=riseup.net; 
> s=squak;
> t=1687716252; bh=3cOd9Tulf+RLZ2R/lGuVfEdERy8xqZ4HHWjkWZv3FAs=;
> h=Date:Subject:To:Cc:References:From:In-Reply-To:From;
> b=lXentHTOo29YwgA/Q5WAgBBEUsl5DTqsmOJd4wsjxBgZocgZ/PG9hmyBm6a2IjvcQ
> jSPI26UWubdYOe7kngWhcU0/oO570ofVUyrxiNgiJQfcscdp5bpwrskBKK4yXdJc0q
> 2YM4+n0xeBBSr2pFLMDwbOsLDvaGUK77nSPcbPXQ=
> X-Riseup-User-ID: 
> 7EBE90DED2258EE1CA830B796CBA05FD63338D890F31F4C1BCCCB27A6DA77A56
> Received: from [127.0.0.1] (localhost [127.0.0.1])
> by fews01-sea.riseup.net (Postfix) with ESMTPSA id 4QpzPg4YpbzJntB;
> Sun, 25 Jun 2023 18:04:07 +0000 (UTC)
> Message-ID: <64c40359-d0ee-5070-2a52-033c7e655e0a@riseup.net>
> Date: Sun, 25 Jun 2023 15:04:05 -0300
> MIME-Version: 1.0
> Subject: Re: [PATCH v2 6/6] drm/vkms: Add a module param to 
> enable/disable the
> default device
> Content-Language: en-US
> To: Jim Shargo <jshargo@chromium.org>, Daniel Vetter <daniel@ffwll.ch>,
> David Airlie <airlied@gmail.com>, Haneen Mohammed 
> <hamohammed.sa@gmail.com>,
> Jonathan Corbet <corbet@lwn.net>,
> Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
> Maxime Ripard <mripard@kernel.org>, Melissa Wen <melissa.srw@gmail.com>,
> Rodrigo Siqueira <rodrigosiqueiramelo@gmail.com>,
> Thomas Zimmermann <tzimmermann@suse.de>
> References: <20230623222353.97283-1-jshargo@chromium.org>
> <20230623222353.97283-7-jshargo@chromium.org>
> From: Maira Canal <mairacanal@riseup.net>
> In-Reply-To: <20230623222353.97283-7-jshargo@chromium.org>
> Content-Type: text/plain; charset=UTF-8; format=flowed
> Content-Transfer-Encoding: 8bit
> X-BeenThere: dri-devel@lists.freedesktop.org
> X-Mailman-Version: 2.1.29
> Precedence: list
> List-Id: Direct Rendering Infrastructure - Development
> <dri-devel.lists.freedesktop.org>
> List-Unsubscribe: 
> <https://lists.freedesktop.org/mailman/options/dri-devel>,
> <mailto:dri-devel-request@lists.freedesktop.org?subject=unsubscribe>
> List-Archive: <https://lists.freedesktop.org/archives/dri-devel>
> List-Post: <mailto:dri-devel@lists.freedesktop.org>
> List-Help: <mailto:dri-devel-request@lists.freedesktop.org?subject=help>
> List-Subscribe: 
> <https://lists.freedesktop.org/mailman/listinfo/dri-devel>,
> <mailto:dri-devel-request@lists.freedesktop.org?subject=subscribe>
> Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org,
> linux-doc@vger.kernel.org
> Errors-To: dri-devel-bounces@lists.freedesktop.org
> Sender: "dri-devel" <dri-devel-bounces@lists.freedesktop.org>
>
> Hi Jim,
>
> On 6/23/23 19:23, Jim Shargo wrote:
>> In many testing circumstances, we will want to just create a new device
>> and test against that. If we create a default device, it can be annoying
>> to have to manually select the new device instead of choosing the only
>> one that exists.
>>
>> The param, enable_default, is defaulted to true to maintain backwards
>> compatibility.
>>
>> Signed-off-by: Jim Shargo <jshargo@chromium.org>
>> ---
>>   drivers/gpu/drm/vkms/vkms_drv.c | 44 ++++++++++++++++++++++-----------
>>   1 file changed, 29 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c 
>> b/drivers/gpu/drm/vkms/vkms_drv.c
>> index 314a04659c5f..1cb56c090a65 100644
>> --- a/drivers/gpu/drm/vkms/vkms_drv.c
>> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
>> @@ -42,17 +42,26 @@
>>   #define DRIVER_MAJOR    1
>>   #define DRIVER_MINOR    0
>>   +static bool enable_default_device = true;
>> +module_param_named(enable_default_device, enable_default_device, 
>> bool, 0444);
>> +MODULE_PARM_DESC(enable_default_device,
>> +         "Enable/Disable creating the default device");
>
> Wouldn't be better to just call it "enable_default"?
>
> Also, could you add this parameter to vkms_config debugfs file?
>
> Best Regards,
> - Maíra
>
>> +
>>   static bool enable_cursor = true;
>>   module_param_named(enable_cursor, enable_cursor, bool, 0444);
>> -MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
>> +MODULE_PARM_DESC(enable_cursor,
>> +         "Enable/Disable cursor support for the default device");
>>     static bool enable_writeback = true;
>>   module_param_named(enable_writeback, enable_writeback, bool, 0444);
>> -MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback 
>> connector support");
>> +MODULE_PARM_DESC(
>> +    enable_writeback,
>> +    "Enable/Disable writeback connector support for the default 
>> device");
>>     static bool enable_overlay;
>>   module_param_named(enable_overlay, enable_overlay, bool, 0444);
>> -MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
>> +MODULE_PARM_DESC(enable_overlay,
>> +         "Enable/Disable overlay support for the default device");
>>     DEFINE_DRM_GEM_FOPS(vkms_driver_fops);
>>   @@ -278,10 +287,7 @@ void vkms_remove_device(struct vkms_device 
>> *vkms_device)
>>   static int __init vkms_init(void)
>>   {
>>       int ret;
>> -    struct platform_device *pdev;
>> -    struct vkms_device_setup vkms_device_setup = {
>> -        .configfs = NULL,
>> -    };
>> +    struct platform_device *default_pdev = NULL;
>>         ret = platform_driver_register(&vkms_platform_driver);
>>       if (ret) {
>> @@ -289,19 +295,27 @@ static int __init vkms_init(void)
>>           return ret;
>>       }
>>   -    pdev = platform_device_register_data(NULL, DRIVER_NAME, 0,
>> -                         &vkms_device_setup,
>> -                         sizeof(vkms_device_setup));
>> -    if (IS_ERR(pdev)) {
>> -        DRM_ERROR("Unable to register default vkms device\n");
>> -        platform_driver_unregister(&vkms_platform_driver);
>> -        return PTR_ERR(pdev);
>> +    if (enable_default_device) {
>> +        struct vkms_device_setup vkms_device_setup = {
>> +            .configfs = NULL,
>> +        };
>> +
>> +        default_pdev = platform_device_register_data(
>> +            NULL, DRIVER_NAME, 0, &vkms_device_setup,
>> +            sizeof(vkms_device_setup));
>> +        if (IS_ERR(default_pdev)) {
>> +            DRM_ERROR("Unable to register default vkms device\n");
>> + platform_driver_unregister(&vkms_platform_driver);
>> +            return PTR_ERR(default_pdev);
>> +        }
>>       }
>>         ret = vkms_init_configfs();
>>       if (ret) {
>>           DRM_ERROR("Unable to initialize configfs\n");
>> -        platform_device_unregister(pdev);
>> +        if (default_pdev)
>> +            platform_device_unregister(default_pdev);
>> +
>>           platform_driver_unregister(&vkms_platform_driver);
>>       }
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 5/6] drm/vkms: Support enabling ConfigFS devices
  2023-08-18  5:24     ` Brandon Ross Pollack
@ 2023-08-18  6:54       ` Marius Vlad
  0 siblings, 0 replies; 21+ messages in thread
From: Marius Vlad @ 2023-08-18  6:54 UTC (permalink / raw)
  To: Brandon Ross Pollack, Jim Shargo
  Cc: Haneen Mohammed, Thomas Zimmermann, Rodrigo Siqueira,
	Jonathan Corbet, linux-doc, linux-kernel, Maxime Ripard,
	Melissa Wen, mairacanal, dri-devel

Hi,

Seem my comments below.

On 8/18/23 08:24, Brandon Ross Pollack wrote:
> 
> On 8/15/23 23:01, Marius Vlad wrote:
>> Hi,
>>
>> See below some minor comments:
>>
>> On Fri, Jun 23, 2023 at 06:23:47PM -0400, Jim Shargo wrote:
>>> VKMS now supports creating and using virtual devices!
>>>
>>> In addition to the enabling logic, this commit also prevents users from
>>> adding new objects once a card is registered.
>>>
>>> Signed-off-by: Jim Shargo <jshargo@chromium.org>
>>> ---
>>>   drivers/gpu/drm/vkms/vkms_configfs.c |  37 +++--
>>>   drivers/gpu/drm/vkms/vkms_crtc.c     |   4 +-
>>>   drivers/gpu/drm/vkms/vkms_drv.c      |   3 +-
>>>   drivers/gpu/drm/vkms/vkms_drv.h      |   2 +-
>>>   drivers/gpu/drm/vkms/vkms_output.c   | 201 +++++++++++++++++++++++----
>>>   5 files changed, 202 insertions(+), 45 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c 
>>> b/drivers/gpu/drm/vkms/vkms_configfs.c
>>> index 544024735d19..f5eed6d23dcd 100644
>>> --- a/drivers/gpu/drm/vkms/vkms_configfs.c
>>> +++ b/drivers/gpu/drm/vkms/vkms_configfs.c
>>> @@ -504,29 +504,40 @@ static ssize_t device_enabled_store(struct 
>>> config_item *item, const char *buf,
>>>   {
>>>       struct vkms_configfs *configfs = item_to_configfs(item);
>>>       struct vkms_device *device;
>>> -    int value, ret;
>>> +    int enabled, ret;
>>> -    ret = kstrtoint(buf, 0, &value);
>>> +    ret = kstrtoint(buf, 0, &enabled);
>>>       if (ret)
>>>           return ret;
>>> -    if (value != 1)
>>> -        return -EINVAL;
>>> -
>>> -    mutex_lock(&configfs->lock);
>>> -
>>> -    if (configfs->vkms_device) {
>>> +    if (enabled == 0) {
>>> +        mutex_lock(&configfs->lock);
>>> +        if (configfs->vkms_device) {
>>> +            vkms_remove_device(configfs->vkms_device);
>>> +            configfs->vkms_device = NULL;
>>> +        }
>>>           mutex_unlock(&configfs->lock);
>>> +
>>>           return len;
>>>       }
>>> -    device = vkms_add_device(configfs);
>>> -    mutex_unlock(&configfs->lock);
>>> +    if (enabled == 1) {
>>> +        mutex_lock(&configfs->lock);
>>> +        if (!configfs->vkms_device) {
>>> +            device = vkms_add_device(configfs);
>>> +            if (IS_ERR(device)) {
>>> +                mutex_unlock(&configfs->lock);
>>> +                return -PTR_ERR(device);
>>> +            }
>>> +
>>> +            configfs->vkms_device = device;
>>> +        }
>>> +        mutex_unlock(&configfs->lock);
>>> -    if (IS_ERR(device))
>>> -        return -PTR_ERR(device);
>>> +        return len;
>>> +    }
>>> -    return len;
>>> +    return -EINVAL;
>>>   }
>>>   CONFIGFS_ATTR(device_, enabled);
>>> diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c 
>>> b/drivers/gpu/drm/vkms/vkms_crtc.c
>>> index d91e49c53adc..5ebb5264f6ef 100644
>>> --- a/drivers/gpu/drm/vkms/vkms_crtc.c
>>> +++ b/drivers/gpu/drm/vkms/vkms_crtc.c
>>> @@ -278,7 +278,7 @@ static const struct drm_crtc_helper_funcs 
>>> vkms_crtc_helper_funcs = {
>>>   struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
>>>                    struct drm_plane *primary,
>>> -                 struct drm_plane *cursor)
>>> +                 struct drm_plane *cursor, const char *name)
>>>   {
>>>       struct drm_device *dev = &vkmsdev->drm;
>>>       struct vkms_crtc *vkms_crtc;
>>> @@ -290,7 +290,7 @@ struct vkms_crtc *vkms_crtc_init(struct 
>>> vkms_device *vkmsdev,
>>>       vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
>>>       ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, 
>>> primary, cursor,
>>> -                     &vkms_crtc_funcs, NULL);
>>> +                     &vkms_crtc_funcs, name);
>>>       if (ret) {
>>>           DRM_ERROR("Failed to init CRTC\n");
>>>           goto out_error;
>>> diff --git a/drivers/gpu/drm/vkms/vkms_drv.c 
>>> b/drivers/gpu/drm/vkms/vkms_drv.c
>>> index 1b5b7143792f..314a04659c5f 100644
>>> --- a/drivers/gpu/drm/vkms/vkms_drv.c
>>> +++ b/drivers/gpu/drm/vkms/vkms_drv.c
>>> @@ -210,7 +210,7 @@ static int vkms_platform_probe(struct 
>>> platform_device *pdev)
>>>       ret = drm_dev_register(&vkms_device->drm, 0);
>>>       if (ret) {
>>>           DRM_ERROR("Unable to register device with id %d\n", pdev->id);
>>> -        return ret;
>>> +        goto out_release_group;
>>>       }
>>>       drm_fbdev_generic_setup(&vkms_device->drm, 0);
>>> @@ -256,6 +256,7 @@ struct vkms_device *vkms_add_device(struct 
>>> vkms_configfs *configfs)
>>>               dev, &vkms_platform_driver.driver))) {
>>>           pdev = to_platform_device(dev);
>>>           max_id = max(max_id, pdev->id);
>>> +        put_device(dev);
>>>       }
>>>       pdev = platform_device_register_data(NULL, DRIVER_NAME, max_id 
>>> + 1,
>>> diff --git a/drivers/gpu/drm/vkms/vkms_drv.h 
>>> b/drivers/gpu/drm/vkms/vkms_drv.h
>>> index 3634eeeb4548..3d592d085f49 100644
>>> --- a/drivers/gpu/drm/vkms/vkms_drv.h
>>> +++ b/drivers/gpu/drm/vkms/vkms_drv.h
>>> @@ -239,7 +239,7 @@ 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);
>>> +                 struct drm_plane *cursor, const char *name);
>>>   int vkms_output_init(struct vkms_device *vkmsdev);
>>>   int vkms_output_init_default(struct vkms_device *vkmsdev);
>>> diff --git a/drivers/gpu/drm/vkms/vkms_output.c 
>>> b/drivers/gpu/drm/vkms/vkms_output.c
>>> index c9e6c653de19..806ff01954ad 100644
>>> --- a/drivers/gpu/drm/vkms/vkms_output.c
>>> +++ b/drivers/gpu/drm/vkms/vkms_output.c
>>> @@ -2,8 +2,10 @@
>>>   #include <drm/drm_atomic_helper.h>
>>>   #include <drm/drm_connector.h>
>>> +#include <drm/drm_crtc.h>
>>>   #include <drm/drm_edid.h>
>>>   #include <drm/drm_encoder.h>
>>> +#include <drm/drm_plane.h>
>>>   #include <drm/drm_probe_helper.h>
>>>   #include <drm/drm_simple_kms_helper.h>
>>> @@ -82,7 +84,6 @@ static struct drm_encoder *vkms_encoder_init(struct 
>>> vkms_device *vkms_device)
>>>   int vkms_output_init_default(struct vkms_device *vkmsdev)
>>>   {
>>> -    struct vkms_output *output = &vkmsdev->output;
>>>       struct drm_device *dev = &vkmsdev->drm;
>>>       struct drm_connector *connector;
>>>       struct drm_encoder *encoder;
>>> @@ -101,8 +102,7 @@ int vkms_output_init_default(struct vkms_device 
>>> *vkmsdev)
>>>               struct vkms_plane *overlay = vkms_plane_init(
>>>                   vkmsdev, DRM_PLANE_TYPE_OVERLAY);
>>>               if (IS_ERR(overlay)) {
>>> -                ret = PTR_ERR(overlay);
>>> -                goto err_planes;
>>> +                return PTR_ERR(overlay);
>>>               }
>>>           }
>>>       }
>>> @@ -110,17 +110,16 @@ int vkms_output_init_default(struct vkms_device 
>>> *vkmsdev)
>>>       if (vkmsdev->config.cursor) {
>>>           cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR);
>>>           if (IS_ERR(cursor)) {
>>> -            ret = PTR_ERR(cursor);
>>> -            goto err_planes;
>>> +            return PTR_ERR(cursor);
>>>           }
>>>       }
>>>       vkms_crtc = vkms_crtc_init(vkmsdev, &primary->base,
>>> -                   cursor ? &cursor->base : NULL);
>>> +                   cursor ? &cursor->base : NULL,
>>> +                   "crtc-default");
>>>       if (IS_ERR(vkms_crtc)) {
>>>           DRM_ERROR("Failed to init crtc\n");
>>> -        ret = PTR_ERR(vkms_crtc);
>>> -        goto err_planes;
>>> +        return PTR_ERR(vkms_crtc);
>>>       }
>>>       for (int i = 0; i < vkmsdev->output.num_planes; i++) {
>>> @@ -131,22 +130,20 @@ int vkms_output_init_default(struct vkms_device 
>>> *vkmsdev)
>>>       connector = vkms_connector_init(vkmsdev);
>>>       if (IS_ERR(connector)) {
>>>           DRM_ERROR("Failed to init connector\n");
>>> -        ret = PTR_ERR(connector);
>>> -        goto err_connector;
>>> +        return PTR_ERR(connector);
>>>       }
>>>       encoder = vkms_encoder_init(vkmsdev);
>>>       if (IS_ERR(encoder)) {
>>>           DRM_ERROR("Failed to init encoder\n");
>>> -        ret = PTR_ERR(encoder);
>>> -        goto err_encoder;
>>> +        return PTR_ERR(encoder);
>>>       }
>>>       encoder->possible_crtcs |= drm_crtc_mask(&vkms_crtc->base);
>>>       ret = drm_connector_attach_encoder(connector, encoder);
>>>       if (ret) {
>>>           DRM_ERROR("Failed to attach connector to encoder\n");
>>> -        goto err_attach;
>>> +        return ret;
>>>       }
>>>       if (vkmsdev->config.writeback) {
>>> @@ -158,27 +155,175 @@ int vkms_output_init_default(struct 
>>> vkms_device *vkmsdev)
>>>       drm_mode_config_reset(dev);
>>>       return 0;
>>> +}
>>> -err_attach:
>>> -    drm_encoder_cleanup(encoder);
>>> +static bool is_object_linked(struct vkms_config_links *links, 
>>> unsigned long idx)
>>> +{
>>> +    return links->linked_object_bitmap & (1 << idx);
>>> +}
>>> -err_encoder:
>>> -    drm_connector_cleanup(connector);
>>> +int vkms_output_init(struct vkms_device *vkmsdev)
>>> +{
>>> +    struct drm_device *dev = &vkmsdev->drm;
>>> +    struct vkms_configfs *configfs = vkmsdev->configfs;
>>> +    struct vkms_output *output = &vkmsdev->output;
>>> +    struct plane_map {
>>> +        struct vkms_config_plane *config_plane;
>>> +        struct vkms_plane *plane;
>>> +    } plane_map[VKMS_MAX_PLANES] = { 0 };
>>> +    struct encoder_map {
>>> +        struct vkms_config_encoder *config_encoder;
>>> +        struct drm_encoder *encoder;
>>> +    } encoder_map[VKMS_MAX_OUTPUT_OBJECTS] = { 0 };
>>> +    struct config_item *item;
>>> +    int map_idx = 0;
>>> +
>>> +    list_for_each_entry(item, &configfs->planes_group.cg_children,
>>> +                ci_entry) {
>>> +        struct vkms_config_plane *config_plane =
>>> +            item_to_config_plane(item);
>>> +        struct vkms_plane *plane =
>>> +            vkms_plane_init(vkmsdev, config_plane->type);
>>> +
>>> +        if (IS_ERR(plane)) {
>>> +            DRM_ERROR("Unable to init plane from config: %s",
>>> +                  item->ci_name);
>>> +            return PTR_ERR(plane);
>>> +        }
>>> -err_connector:
>>> -    drm_crtc_cleanup(&vkms_crtc->base);
>>> +        plane_map[map_idx].config_plane = config_plane;
>>> +        plane_map[map_idx].plane = plane;
>>> +        map_idx += 1;
>>> +    }
>>> -err_planes:
>>> -    for (int i = 0; i < output->num_planes; i++) {
>>> -        drm_plane_cleanup(&output->planes[i].base);
>>> +    map_idx = 0;
>>> +    list_for_each_entry(item, &configfs->encoders_group.cg_children,
>>> +                ci_entry) {
>>> +        struct vkms_config_encoder *config_encoder =
>>> +            item_to_config_encoder(item);
>>> +        struct drm_encoder *encoder = vkms_encoder_init(vkmsdev);
>>> +
>>> +        if (IS_ERR(encoder)) {
>>> +            DRM_ERROR("Failed to init config encoder: %s",
>>> +                  item->ci_name);
>>> +            return PTR_ERR(encoder);
>>> +        }
>>> +        encoder_map[map_idx].config_encoder = config_encoder;
>> A two connector configuration without an encoder would have the
>> potential of blowing up if we don't create a second_encoder.
> 
> What a catch!!! I tested this and sure enough BOOM!
> 
> Switched to limiting based on output->num_encoders as it should be.
> 
>>> +        encoder_map[map_idx].encoder = encoder;
>>> +        map_idx += 1;
>>>       }
>>> -    memset(output, 0, sizeof(*output));
>>> +    list_for_each_entry(item, &configfs->connectors_group.cg_children,
>>> +                ci_entry) {
>>> +        struct vkms_config_connector *config_connector =
>>> +            item_to_config_connector(item);
>>> +        struct drm_connector *connector = vkms_connector_init(vkmsdev);
>>> -    return ret;
>>> -}
>>> +        if (IS_ERR(connector)) {
>>> +            DRM_ERROR("Failed to init connector from config: %s",
>>> +                  item->ci_name);
>>> +            return PTR_ERR(connector);
>>> +        }
>>> -int vkms_output_init(struct vkms_device *vkmsdev)
>>> -{
>>> -    return -ENOTSUPP;
>>> +        for (int j = 0; j < output->num_connectors; j++) {
>>> +            struct encoder_map *encoder = &encoder_map[j];
>>> +
>>> +            if (is_object_linked(
>>> +                    &config_connector->possible_encoders,
>>> +                    encoder->config_encoder
>>> +                        ->encoder_config_idx)) {
>> Here encoder->config_encoder for two connectors but a single encoder
>> will give back a empty encoder.
>>> +                drm_connector_attach_encoder(connector,
>>> +                                 encoder->encoder);
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    list_for_each_entry(item, &configfs->crtcs_group.cg_children,
>>> +                ci_entry) {
>>> +        struct vkms_config_crtc *config_crtc =
>>> +            item_to_config_crtc(item);
>>> +        struct vkms_crtc *vkms_crtc;
>>> +        struct drm_plane *primary = NULL, *cursor = NULL;
>>> +
>>> +        for (int j = 0; j < output->num_planes; j++) {
>>> +            struct plane_map *plane_entry = &plane_map[j];
>>> +            struct drm_plane *plane = &plane_entry->plane->base;
>>> +
>>> +            if (!is_object_linked(
>>> +                    &plane_entry->config_plane->possible_crtcs,
>>> +                    config_crtc->crtc_config_idx)) {
>>> +                continue;
>>> +            }
>>> +
>>> +            if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
>>> +                if (primary) {
>>> +                    DRM_WARN(
>>> +                        "Too many primary planes found for crtc %s.",
>>> +                        item->ci_name);
>>> +                    return EINVAL;
>>> +                }
>>> +                primary = plane;
>>> +            } else if (plane->type == DRM_PLANE_TYPE_CURSOR) {
>>> +                if (cursor) {
>>> +                    DRM_WARN(
>>> +                        "Too many cursor planes found for crtc %s.",
>>> +                        item->ci_name);
>>> +                    return EINVAL;
>>> +                }
>>> +                cursor = plane;
>>> +            }
>>> +        }
>>> +
>>> +        if (!primary) {
>>> +            DRM_WARN("No primary plane configured for crtc %s",
>>> +                 item->ci_name);
>>> +            return EINVAL;
>>> +        }
>>> +
>>> +        vkms_crtc =
>>> +            vkms_crtc_init(vkmsdev, primary, cursor, item->ci_name);
>>> +        if (IS_ERR(vkms_crtc)) {
>>> +            DRM_WARN("Unable to init crtc from config: %s",
>>> +                 item->ci_name);
>>> +            return PTR_ERR(vkms_crtc);
>>> +        }
>>> +
>>> +        for (int j = 0; j < output->num_planes; j++) {
>>> +            struct plane_map *plane_entry = &plane_map[j];
>>> +
>>> +            if (!plane_entry->plane)
>>> +                break;
>>> +
>>> +            if (is_object_linked(
>>> +                    &plane_entry->config_plane->possible_crtcs,
>>> +                    config_crtc->crtc_config_idx)) {
>>> +                plane_entry->plane->base.possible_crtcs |=
>>> +                    drm_crtc_mask(&vkms_crtc->base);
>>> +            }
>>> +        }
>>> +
>>> +        for (int j = 0; j < output->num_encoders; j++) {
>>> +            struct encoder_map *encoder_entry = &encoder_map[j];
>>> +
>>> +            if (is_object_linked(&encoder_entry->config_encoder
>>> +                              ->possible_crtcs,
>>> +                         config_crtc->crtc_config_idx)) {
>>> +                encoder_entry->encoder->possible_crtcs |=
>>> +                    drm_crtc_mask(&vkms_crtc->base);
>>> +            }
>>> +        }
>>> +
>>> +        if (vkmsdev->config.writeback) {
>>> +            int ret = vkms_enable_writeback_connector(vkmsdev,
>>> +                                  vkms_crtc);
>>> +            if (ret)
>>> +                DRM_WARN(
>>> +                    "Failed to init writeback connector for config 
>>> crtc: %s. Error code %d",
>>> +                    item->ci_name, ret);
>>> +        }
>>> +    }
>> I think we might be needing here a test for missing symlinks - invalid
>> configurations, like unused crtcs, encoders, connectors. I
>> suppose anything that's not matched with is_object_linked(),
>> such we avoid invalid configuration found by drm_mode_config_validate()
>> and inform users about missing them.
>>
>> An example here would be to create a second encoder, connector, crtc and
>> not symlink them to their possible_encoders,possible_crtcs mask. An
>> i-g-t test for this very thing would be needed to stress different kind
>> of combinations.
> 
> I wonder about this.  Shouldn't a user be free to create dangling files 
> to simulate some scenario?

Problem is we end up with invalid pipelines which would be invalided by 
`drm_mode_config_validate()`, further more leading to kernel 
warns/errors. That seems to be case now. If the system is still usable 
--  while still having these missing bits, and possibly inform the user 
about an invalid pipeline would be ideal.

> 
> I could see a further development to publish a warning in the log, but 
> disallowing it seems overly restrictive.

Sure, I think ideally this should be the case, if we can do that in the 
first place, given that we operate on a transaction model, only when 
enable it we go over all items that were created/added by the user. 
Dangling or not, my impression is that these invalid bits need to be 
dropped entirely when submitted back to the DRM helpers.

If the user configures 3 pipelines, two correctly, and one not,  still 
allow those two pipelines to work and inform that it might have an 
invalid one, rather than invalidating the entire config, I think that's 
the point.
> 
> Either way we could accomplish this pretty easily by adding a flag to 
> each vkms object in the form of `bool is_linked` and set it when we 
> detect it's linked.  Then at the end iterate through all planes, 
> encoders, connectors, crtcs that can be linked and if they are not just 
> publish a warning saying
> 
> kwarn: "crtc/plane/encoder $NAME is created but unlinked"
Yeah, that would be really good.
> 
> IGT tests to test that that was done make sense to me.
> 
>>> +
>>> +    drm_mode_config_reset(dev);
>>> +
>>> +    return 0;
>>>   }
>>> -- 
>>> 2.41.0.162.gfafddb0af9-goog
>>>

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2023-08-18  6:54 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-23 22:23 [PATCH v2 0/6] Adds support for ConfigFS to VKMS! Jim Shargo
2023-06-23 22:23 ` [PATCH v2 1/6] drm/vkms: Back VKMS with DRM memory management instead of static objects Jim Shargo
2023-06-25 17:40   ` Maira Canal
2023-06-23 22:23 ` [PATCH v2 2/6] drm/vkms: Support multiple DRM objects (crtcs, etc.) per VKMS device Jim Shargo
2023-06-25 17:57   ` Maira Canal
2023-08-15 11:29   ` Marius Vlad
2023-08-18  3:36     ` Brandon Pollack
2023-08-18  5:26       ` Brandon Ross Pollack
2023-06-23 22:23 ` [PATCH v2 3/6] drm/vkms: Provide platform data when creating VKMS devices Jim Shargo
2023-06-23 22:23 ` [PATCH v2 4/6] drm/vkms: Add ConfigFS scaffolding to VKMS Jim Shargo
2023-06-25 18:19   ` Maira Canal
2023-06-28  2:00   ` kernel test robot
2023-06-23 22:23 ` [PATCH v2 5/6] drm/vkms: Support enabling ConfigFS devices Jim Shargo
2023-08-15 14:01   ` Marius Vlad
2023-08-18  5:24     ` Brandon Ross Pollack
2023-08-18  6:54       ` Marius Vlad
2023-06-23 22:23 ` [PATCH v2 6/6] drm/vkms: Add a module param to enable/disable the default device Jim Shargo
2023-06-25 18:04   ` Maira Canal
2023-08-18  5:39     ` Brandon Ross Pollack
2023-08-08  2:42 ` Brandon Pollack
2023-08-08  2:42 ` [PATCH] Initial backport of vkms changes from 6.4, including jshargo and brpols configs changes Brandon Pollack

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).