All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC v3 00/12] drm: Add generic fbdev emulation
@ 2018-02-22 20:06 Noralf Trønnes
  2018-02-22 20:06 ` [RFC v3 01/12] drm: provide management functions for drm_file Noralf Trønnes
                   ` (12 more replies)
  0 siblings, 13 replies; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

This patchset explores the possibility of having generic fbdev emulation
in DRM for drivers that supports dumb buffers which they can export.

The change this time is that I have tried to do an in-kernel client API.
The intention was to have callbacks on the drm_file, but I gave up on
that mainly because I have to take a ref on the driver module since
drm_driver->postclose is called in drm_file_free(). This blocks DRM
driver module unload. Another reason is that the callback signalling new
drm_devices would have to be out-of-band so I put all the callbacks
together.

The patchset includes 3 different clients that I have done to see how it
is to use the API:
- fbdev
- bootsplash
- VT console

There is still work left to be done, but it would be nice to get some
feedback about the direction I have taken.

For readers I suggest to first look at the bootsplash client to see the
API in use. The client is very simple so it's an easy read.

Noralf.

Changes since version 2:
- Don't set drm master for in-kernel clients. (Daniel Vetter)
- Add in-kernel client API

Changes since version 1:
- Don't add drm_fb_helper_fb_open() and drm_fb_helper_fb_release() to
  DRM_FB_HELPER_DEFAULT_OPS(). (Fi.CI.STATIC)
  The following uses that macro and sets fb_open/close: udlfb_ops,
  amdgpufb_ops, drm_fb_helper_generic_fbdev_ops, nouveau_fbcon_ops,
  nouveau_fbcon_sw_ops, radeonfb_ops.
  This results in: warning: Initializer entry defined twice
- Support CONFIG_DRM_KMS_HELPER=m (kbuild test robot)
  ERROR: <function> [drivers/gpu/drm/drm_kms_helper.ko] undefined!
- Drop buggy patch: (Chris Wilson)
  drm/prime: Clear drm_gem_object->dma_buf on release
- Defer buffer creation until fb_open.

David Herrmann (1):
  drm: provide management functions for drm_file

Noralf Trønnes (11):
  drm/file: Don't set master on in-kernel clients
  drm: Make ioctls available for in-kernel clients part 1
  drm: Make ioctls available for in-kernel clients part 2
  drm: Add _ioctl suffix to some functions
  drm: Add DRM device iterator
  drm/modes: Add drm_umode_equal()
  drm/framebuffer: Add drm_mode_can_dirtyfb()
  drm: Add API for in-kernel clients
  drm/client: Add fbdev emulation client
  drm/client: Add bootsplash client
  drm/client: Add VT console client

 drivers/gpu/drm/Kconfig                 |    2 +
 drivers/gpu/drm/Makefile                |    3 +-
 drivers/gpu/drm/client/Kconfig          |   30 +
 drivers/gpu/drm/client/Makefile         |    5 +
 drivers/gpu/drm/client/drm_bootsplash.c |  205 ++++
 drivers/gpu/drm/client/drm_client.c     | 1612 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/client/drm_fbdev.c      |  997 +++++++++++++++++++
 drivers/gpu/drm/client/drm_vtcon.c      |  760 +++++++++++++++
 drivers/gpu/drm/drm_connector.c         |   50 +-
 drivers/gpu/drm/drm_crtc.c              |   47 +-
 drivers/gpu/drm/drm_crtc_internal.h     |   80 +-
 drivers/gpu/drm/drm_drv.c               |   66 ++
 drivers/gpu/drm/drm_dumb_buffers.c      |   33 +-
 drivers/gpu/drm/drm_encoder.c           |   10 +-
 drivers/gpu/drm/drm_file.c              |  304 +++---
 drivers/gpu/drm/drm_framebuffer.c       |  149 ++-
 drivers/gpu/drm/drm_internal.h          |    7 +
 drivers/gpu/drm/drm_ioc32.c             |    2 +-
 drivers/gpu/drm/drm_ioctl.c             |   24 +-
 drivers/gpu/drm/drm_mode_config.c       |   81 +-
 drivers/gpu/drm/drm_mode_object.c       |   12 +-
 drivers/gpu/drm/drm_modes.c             |   50 +
 drivers/gpu/drm/drm_plane.c             |   24 +-
 drivers/gpu/drm/drm_prime.c             |   13 +-
 drivers/gpu/drm/drm_probe_helper.c      |    3 +
 drivers/gpu/drm/drm_vblank.c            |   11 +-
 include/drm/drm_client.h                |  192 ++++
 include/drm/drm_device.h                |    1 +
 include/drm/drm_drv.h                   |   34 +
 include/drm/drm_file.h                  |    7 +
 include/drm/drm_framebuffer.h           |    2 +
 include/drm/drm_modes.h                 |    2 +
 32 files changed, 4515 insertions(+), 303 deletions(-)
 create mode 100644 drivers/gpu/drm/client/Kconfig
 create mode 100644 drivers/gpu/drm/client/Makefile
 create mode 100644 drivers/gpu/drm/client/drm_bootsplash.c
 create mode 100644 drivers/gpu/drm/client/drm_client.c
 create mode 100644 drivers/gpu/drm/client/drm_fbdev.c
 create mode 100644 drivers/gpu/drm/client/drm_vtcon.c
 create mode 100644 include/drm/drm_client.h

-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 01/12] drm: provide management functions for drm_file
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-02-22 20:06 ` [RFC v3 02/12] drm/file: Don't set master on in-kernel clients Noralf Trønnes
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

From: David Herrmann <dh.herrmann@gmail.com>

Rather than doing drm_file allocation/destruction right in the fops, lets
provide separate helpers. This decouples drm_file management from the
still-mandatory drm-fops. It prepares for use of drm_file without the
fops, both by possible separate fops implementations and APIs (not that I
am aware of any such plans), and more importantly from in-kernel use where
no real file is available.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
---
 drivers/gpu/drm/drm_file.c     | 305 +++++++++++++++++++++++------------------
 drivers/gpu/drm/drm_internal.h |   2 +
 2 files changed, 175 insertions(+), 132 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index e394799979a6..d4588d33f91c 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -101,6 +101,175 @@ DEFINE_MUTEX(drm_global_mutex);
 
 static int drm_open_helper(struct file *filp, struct drm_minor *minor);
 
+/**
+ * drm_file_alloc - allocate file context
+ * @minor: minor to allocate on
+ *
+ * This allocates a new DRM file context. It is not linked into any context and
+ * can be used by the caller freely. Note that the context keeps a pointer to
+ * @minor, so it must be freed before @minor is.
+ *
+ * RETURNS:
+ * Pointer to newly allocated context, ERR_PTR on failure.
+ */
+struct drm_file *drm_file_alloc(struct drm_minor *minor)
+{
+	struct drm_device *dev = minor->dev;
+	struct drm_file *file;
+	int ret;
+
+	file = kzalloc(sizeof(*file), GFP_KERNEL);
+	if (!file)
+		return ERR_PTR(-ENOMEM);
+
+	file->pid = get_pid(task_pid(current));
+	file->minor = minor;
+
+	/* for compatibility root is always authenticated */
+	file->authenticated = capable(CAP_SYS_ADMIN);
+	file->lock_count = 0;
+
+	INIT_LIST_HEAD(&file->lhead);
+	INIT_LIST_HEAD(&file->fbs);
+	mutex_init(&file->fbs_lock);
+	INIT_LIST_HEAD(&file->blobs);
+	INIT_LIST_HEAD(&file->pending_event_list);
+	INIT_LIST_HEAD(&file->event_list);
+	init_waitqueue_head(&file->event_wait);
+	file->event_space = 4096; /* set aside 4k for event buffer */
+
+	mutex_init(&file->event_read_lock);
+
+	if (drm_core_check_feature(dev, DRIVER_GEM))
+		drm_gem_open(dev, file);
+
+	if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
+		drm_syncobj_open(file);
+
+	if (drm_core_check_feature(dev, DRIVER_PRIME))
+		drm_prime_init_file_private(&file->prime);
+
+	if (dev->driver->open) {
+		ret = dev->driver->open(dev, file);
+		if (ret < 0)
+			goto out_prime_destroy;
+	}
+
+	if (drm_is_primary_client(file)) {
+		ret = drm_master_open(file);
+		if (ret)
+			goto out_close;
+	}
+
+	return file;
+
+out_close:
+	if (dev->driver->postclose)
+		dev->driver->postclose(dev, file);
+out_prime_destroy:
+	if (drm_core_check_feature(dev, DRIVER_PRIME))
+		drm_prime_destroy_file_private(&file->prime);
+	if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
+		drm_syncobj_release(file);
+	if (drm_core_check_feature(dev, DRIVER_GEM))
+		drm_gem_release(dev, file);
+	put_pid(file->pid);
+	kfree(file);
+
+	return ERR_PTR(ret);
+}
+
+static void drm_events_release(struct drm_file *file_priv)
+{
+	struct drm_device *dev = file_priv->minor->dev;
+	struct drm_pending_event *e, *et;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+
+	/* Unlink pending events */
+	list_for_each_entry_safe(e, et, &file_priv->pending_event_list,
+				 pending_link) {
+		list_del(&e->pending_link);
+		e->file_priv = NULL;
+	}
+
+	/* Remove unconsumed events */
+	list_for_each_entry_safe(e, et, &file_priv->event_list, link) {
+		list_del(&e->link);
+		kfree(e);
+	}
+
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+/**
+ * drm_file_free - free file context
+ * @file: context to free, or NULL
+ *
+ * This destroys and deallocates a DRM file context previously allocated via
+ * drm_file_alloc(). The caller must make sure to unlink it from any contexts
+ * before calling this.
+ *
+ * If NULL is passed, this is a no-op.
+ *
+ * RETURNS:
+ * 0 on success, or error code on failure.
+ */
+void drm_file_free(struct drm_file *file)
+{
+	struct drm_device *dev;
+
+	if (!file)
+		return;
+
+	dev = file->minor->dev;
+
+	DRM_DEBUG("pid = %d, device = 0x%lx, open_count = %d\n",
+		  task_pid_nr(current),
+		  (long)old_encode_dev(file->minor->kdev->devt),
+		  dev->open_count);
+
+	if (drm_core_check_feature(dev, DRIVER_LEGACY) &&
+	    dev->driver->preclose)
+		dev->driver->preclose(dev, file);
+
+	if (drm_core_check_feature(dev, DRIVER_LEGACY))
+		drm_legacy_lock_release(dev, file->filp);
+
+	if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+		drm_legacy_reclaim_buffers(dev, file);
+
+	drm_events_release(file);
+
+	if (drm_core_check_feature(dev, DRIVER_MODESET)) {
+		drm_fb_release(file);
+		drm_property_destroy_user_blobs(dev, file);
+	}
+
+	if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
+		drm_syncobj_release(file);
+
+	if (drm_core_check_feature(dev, DRIVER_GEM))
+		drm_gem_release(dev, file);
+
+	drm_legacy_ctxbitmap_flush(dev, file);
+
+	if (drm_is_primary_client(file))
+		drm_master_release(file);
+
+	if (dev->driver->postclose)
+		dev->driver->postclose(dev, file);
+
+	if (drm_core_check_feature(dev, DRIVER_PRIME))
+		drm_prime_destroy_file_private(&file->prime);
+
+	WARN_ON(!list_empty(&file->event_list));
+
+	put_pid(file->pid);
+	kfree(file);
+}
+
 static int drm_setup(struct drm_device * dev)
 {
 	int ret;
@@ -196,7 +365,6 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
 {
 	struct drm_device *dev = minor->dev;
 	struct drm_file *priv;
-	int ret;
 
 	if (filp->f_flags & O_EXCL)
 		return -EBUSY;	/* No exclusive opens */
@@ -207,50 +375,12 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
 
 	DRM_DEBUG("pid = %d, minor = %d\n", task_pid_nr(current), minor->index);
 
-	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
-	if (!priv)
-		return -ENOMEM;
+	priv = drm_file_alloc(minor);
+	if (IS_ERR(priv))
+		return PTR_ERR(priv);
 
 	filp->private_data = priv;
 	priv->filp = filp;
-	priv->pid = get_pid(task_pid(current));
-	priv->minor = minor;
-
-	/* for compatibility root is always authenticated */
-	priv->authenticated = capable(CAP_SYS_ADMIN);
-	priv->lock_count = 0;
-
-	INIT_LIST_HEAD(&priv->lhead);
-	INIT_LIST_HEAD(&priv->fbs);
-	mutex_init(&priv->fbs_lock);
-	INIT_LIST_HEAD(&priv->blobs);
-	INIT_LIST_HEAD(&priv->pending_event_list);
-	INIT_LIST_HEAD(&priv->event_list);
-	init_waitqueue_head(&priv->event_wait);
-	priv->event_space = 4096; /* set aside 4k for event buffer */
-
-	mutex_init(&priv->event_read_lock);
-
-	if (drm_core_check_feature(dev, DRIVER_GEM))
-		drm_gem_open(dev, priv);
-
-	if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
-		drm_syncobj_open(priv);
-
-	if (drm_core_check_feature(dev, DRIVER_PRIME))
-		drm_prime_init_file_private(&priv->prime);
-
-	if (dev->driver->open) {
-		ret = dev->driver->open(dev, priv);
-		if (ret < 0)
-			goto out_prime_destroy;
-	}
-
-	if (drm_is_primary_client(priv)) {
-		ret = drm_master_open(priv);
-		if (ret)
-			goto out_close;
-	}
 
 	mutex_lock(&dev->filelist_mutex);
 	list_add(&priv->lhead, &dev->filelist);
@@ -277,45 +407,6 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
 #endif
 
 	return 0;
-
-out_close:
-	if (dev->driver->postclose)
-		dev->driver->postclose(dev, priv);
-out_prime_destroy:
-	if (drm_core_check_feature(dev, DRIVER_PRIME))
-		drm_prime_destroy_file_private(&priv->prime);
-	if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
-		drm_syncobj_release(priv);
-	if (drm_core_check_feature(dev, DRIVER_GEM))
-		drm_gem_release(dev, priv);
-	put_pid(priv->pid);
-	kfree(priv);
-	filp->private_data = NULL;
-	return ret;
-}
-
-static void drm_events_release(struct drm_file *file_priv)
-{
-	struct drm_device *dev = file_priv->minor->dev;
-	struct drm_pending_event *e, *et;
-	unsigned long flags;
-
-	spin_lock_irqsave(&dev->event_lock, flags);
-
-	/* Unlink pending events */
-	list_for_each_entry_safe(e, et, &file_priv->pending_event_list,
-				 pending_link) {
-		list_del(&e->pending_link);
-		e->file_priv = NULL;
-	}
-
-	/* Remove unconsumed events */
-	list_for_each_entry_safe(e, et, &file_priv->event_list, link) {
-		list_del(&e->link);
-		kfree(e);
-	}
-
-	spin_unlock_irqrestore(&dev->event_lock, flags);
 }
 
 static void drm_legacy_dev_reinit(struct drm_device *dev)
@@ -382,57 +473,7 @@ int drm_release(struct inode *inode, struct file *filp)
 	list_del(&file_priv->lhead);
 	mutex_unlock(&dev->filelist_mutex);
 
-	if (drm_core_check_feature(dev, DRIVER_LEGACY) &&
-	    dev->driver->preclose)
-		dev->driver->preclose(dev, file_priv);
-
-	/* ========================================================
-	 * Begin inline drm_release
-	 */
-
-	DRM_DEBUG("pid = %d, device = 0x%lx, open_count = %d\n",
-		  task_pid_nr(current),
-		  (long)old_encode_dev(file_priv->minor->kdev->devt),
-		  dev->open_count);
-
-	if (drm_core_check_feature(dev, DRIVER_LEGACY))
-		drm_legacy_lock_release(dev, filp);
-
-	if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
-		drm_legacy_reclaim_buffers(dev, file_priv);
-
-	drm_events_release(file_priv);
-
-	if (drm_core_check_feature(dev, DRIVER_MODESET)) {
-		drm_fb_release(file_priv);
-		drm_property_destroy_user_blobs(dev, file_priv);
-	}
-
-	if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
-		drm_syncobj_release(file_priv);
-
-	if (drm_core_check_feature(dev, DRIVER_GEM))
-		drm_gem_release(dev, file_priv);
-
-	drm_legacy_ctxbitmap_flush(dev, file_priv);
-
-	if (drm_is_primary_client(file_priv))
-		drm_master_release(file_priv);
-
-	if (dev->driver->postclose)
-		dev->driver->postclose(dev, file_priv);
-
-	if (drm_core_check_feature(dev, DRIVER_PRIME))
-		drm_prime_destroy_file_private(&file_priv->prime);
-
-	WARN_ON(!list_empty(&file_priv->event_list));
-
-	put_pid(file_priv->pid);
-	kfree(file_priv);
-
-	/* ========================================================
-	 * End inline drm_release
-	 */
+	drm_file_free(file_priv);
 
 	if (!--dev->open_count) {
 		drm_lastclose(dev);
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
index b72242e93ea4..40179c5fc6b8 100644
--- a/drivers/gpu/drm/drm_internal.h
+++ b/drivers/gpu/drm/drm_internal.h
@@ -26,6 +26,8 @@
 
 /* drm_file.c */
 extern struct mutex drm_global_mutex;
+struct drm_file *drm_file_alloc(struct drm_minor *minor);
+void drm_file_free(struct drm_file *file);
 void drm_lastclose(struct drm_device *dev);
 
 /* drm_pci.c */
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 02/12] drm/file: Don't set master on in-kernel clients
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
  2018-02-22 20:06 ` [RFC v3 01/12] drm: provide management functions for drm_file Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-03-06  8:28   ` Daniel Vetter
  2018-02-22 20:06 ` [RFC v3 03/12] drm: Make ioctls available for in-kernel clients part 1 Noralf Trønnes
                   ` (10 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

It only makes sense for userspace clients.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_file.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index d4588d33f91c..55505378df47 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -155,17 +155,8 @@ struct drm_file *drm_file_alloc(struct drm_minor *minor)
 			goto out_prime_destroy;
 	}
 
-	if (drm_is_primary_client(file)) {
-		ret = drm_master_open(file);
-		if (ret)
-			goto out_close;
-	}
-
 	return file;
 
-out_close:
-	if (dev->driver->postclose)
-		dev->driver->postclose(dev, file);
 out_prime_destroy:
 	if (drm_core_check_feature(dev, DRIVER_PRIME))
 		drm_prime_destroy_file_private(&file->prime);
@@ -365,6 +356,7 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
 {
 	struct drm_device *dev = minor->dev;
 	struct drm_file *priv;
+	int ret;
 
 	if (filp->f_flags & O_EXCL)
 		return -EBUSY;	/* No exclusive opens */
@@ -379,6 +371,14 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
 	if (IS_ERR(priv))
 		return PTR_ERR(priv);
 
+	if (drm_is_primary_client(priv)) {
+		ret = drm_master_open(priv);
+		if (ret) {
+			drm_file_free(priv);
+			return ret;
+		}
+	}
+
 	filp->private_data = priv;
 	priv->filp = filp;
 
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 03/12] drm: Make ioctls available for in-kernel clients part 1
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
  2018-02-22 20:06 ` [RFC v3 01/12] drm: provide management functions for drm_file Noralf Trønnes
  2018-02-22 20:06 ` [RFC v3 02/12] drm/file: Don't set master on in-kernel clients Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-02-22 20:06 ` [RFC v3 04/12] drm: Make ioctls available for in-kernel clients part 2 Noralf Trønnes
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

This is part 1 of making ioctls useable for in-kernel clients.
Make an ioctl wrapper function that calls a function that can be used by
in-kernel clients.

It adjusts the following functions to handle kernel buffers:
- drm_mode_getresources()
- drm_mode_setcrtc()
- drm_mode_getconnector()
- drm_mode_dirtyfb_ioctl()

There is no functional change from the userspace side.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_connector.c     | 50 +++++++++++++++--------
 drivers/gpu/drm/drm_crtc.c          | 32 ++++++++++-----
 drivers/gpu/drm/drm_crtc_internal.h | 23 ++++++++---
 drivers/gpu/drm/drm_framebuffer.c   | 56 +++++++++++++++----------
 drivers/gpu/drm/drm_ioctl.c         |  6 +--
 drivers/gpu/drm/drm_mode_config.c   | 81 +++++++++++++++++++++++++------------
 6 files changed, 164 insertions(+), 84 deletions(-)

diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index b3cde897cd80..57c9c27ceb05 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -1544,10 +1544,10 @@ static bool drm_mode_expose_to_userspace(const struct drm_display_mode *mode,
 	return true;
 }
 
-int drm_mode_getconnector(struct drm_device *dev, void *data,
-			  struct drm_file *file_priv)
+int drm_mode_getconnector(struct drm_device *dev,
+			  struct drm_mode_get_connector *out_resp,
+			  struct drm_file *file_priv, bool user)
 {
-	struct drm_mode_get_connector *out_resp = data;
 	struct drm_connector *connector;
 	struct drm_encoder *encoder;
 	struct drm_display_mode *mode;
@@ -1556,9 +1556,10 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
 	int ret = 0;
 	int copied = 0;
 	int i;
-	struct drm_mode_modeinfo u_mode;
-	struct drm_mode_modeinfo __user *mode_ptr;
-	uint32_t __user *encoder_ptr;
+	struct drm_mode_modeinfo __user *mode_ptr_user;
+	struct drm_mode_modeinfo u_mode, *mode_ptr;
+	uint32_t __user *encoder_ptr_user;
+	u32 *encoder_ptr;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
 		return -EINVAL;
@@ -1575,13 +1576,18 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
 
 	if ((out_resp->count_encoders >= encoders_count) && encoders_count) {
 		copied = 0;
-		encoder_ptr = (uint32_t __user *)(unsigned long)(out_resp->encoders_ptr);
+		encoder_ptr_user = (uint32_t __user *)(unsigned long)(out_resp->encoders_ptr);
+		encoder_ptr = (u32 *)(unsigned long)(out_resp->encoders_ptr);
 		for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
 			if (connector->encoder_ids[i] != 0) {
-				if (put_user(connector->encoder_ids[i],
-					     encoder_ptr + copied)) {
-					ret = -EFAULT;
-					goto out;
+				if (user) {
+					if (put_user(connector->encoder_ids[i],
+						     encoder_ptr_user + copied)) {
+						ret = -EFAULT;
+						goto out;
+					}
+				} else {
+					encoder_ptr[copied] = connector->encoder_ids[i];
 				}
 				copied++;
 			}
@@ -1616,18 +1622,23 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
 	 */
 	if ((out_resp->count_modes >= mode_count) && mode_count) {
 		copied = 0;
-		mode_ptr = (struct drm_mode_modeinfo __user *)(unsigned long)out_resp->modes_ptr;
+		mode_ptr_user = (struct drm_mode_modeinfo __user *)(unsigned long)out_resp->modes_ptr;
+		mode_ptr = (struct drm_mode_modeinfo *)(unsigned long)out_resp->modes_ptr;
 		list_for_each_entry(mode, &connector->modes, head) {
 			if (!drm_mode_expose_to_userspace(mode, file_priv))
 				continue;
 
 			drm_mode_convert_to_umode(&u_mode, mode);
-			if (copy_to_user(mode_ptr + copied,
-					 &u_mode, sizeof(u_mode))) {
-				ret = -EFAULT;
-				mutex_unlock(&dev->mode_config.mutex);
+			if (user) {
+				if (copy_to_user(mode_ptr_user + copied,
+						 &u_mode, sizeof(u_mode))) {
+					ret = -EFAULT;
+					mutex_unlock(&dev->mode_config.mutex);
 
-				goto out;
+					goto out;
+				}
+			} else {
+				mode_ptr[copied] = u_mode;
 			}
 			copied++;
 		}
@@ -1656,6 +1667,11 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
 	return ret;
 }
 
+int drm_mode_getconnector_ioctl(struct drm_device *dev, void *data,
+				struct drm_file *file_priv)
+{
+	return drm_mode_getconnector(dev, data, file_priv, true);
+}
 
 /**
  * DOC: Tile group
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 353e24fcde9e..c9ab1cc6b412 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -538,21 +538,21 @@ EXPORT_SYMBOL(drm_crtc_check_viewport);
 /**
  * drm_mode_setcrtc - set CRTC configuration
  * @dev: drm device for the ioctl
- * @data: data pointer for the ioctl
- * @file_priv: drm file for the ioctl call
+ * @crtc_req: pointer to request structure
+ * @file_priv: drm file
+ * @user: True if called form userspace, false from in-kernel client
  *
- * Build a new CRTC configuration based on user request.
+ * Build a new CRTC configuration based on request.
  *
- * Called by the user via ioctl.
+ * Called by the user via ioctl, or by an in-kernel client.
  *
  * Returns:
  * Zero on success, negative errno on failure.
  */
-int drm_mode_setcrtc(struct drm_device *dev, void *data,
-		     struct drm_file *file_priv)
+int drm_mode_setcrtc(struct drm_device *dev, struct drm_mode_crtc *crtc_req,
+		     struct drm_file *file_priv, bool user)
 {
 	struct drm_mode_config *config = &dev->mode_config;
-	struct drm_mode_crtc *crtc_req = data;
 	struct drm_crtc *crtc;
 	struct drm_connector **connector_set = NULL, *connector;
 	struct drm_framebuffer *fb = NULL;
@@ -678,10 +678,14 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
 
 		for (i = 0; i < crtc_req->count_connectors; i++) {
 			connector_set[i] = NULL;
-			set_connectors_ptr = (uint32_t __user *)(unsigned long)crtc_req->set_connectors_ptr;
-			if (get_user(out_id, &set_connectors_ptr[i])) {
-				ret = -EFAULT;
-				goto out;
+			if (user) {
+				set_connectors_ptr = (uint32_t __user *)(unsigned long)crtc_req->set_connectors_ptr;
+				if (get_user(out_id, &set_connectors_ptr[i])) {
+					ret = -EFAULT;
+					goto out;
+				}
+			} else {
+				out_id = ((u32 *)(unsigned long)crtc_req->set_connectors_ptr)[i];
 			}
 
 			connector = drm_connector_lookup(dev, file_priv, out_id);
@@ -732,6 +736,12 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
 	return ret;
 }
 
+int drm_mode_setcrtc_ioctl(struct drm_device *dev, void *data,
+			   struct drm_file *file_priv)
+{
+	return drm_mode_setcrtc(dev, data, file_priv, true);
+}
+
 int drm_mode_crtc_set_obj_prop(struct drm_mode_object *obj,
 			       struct drm_property *property,
 			       uint64_t value)
diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h
index af00f42ba269..29c59ce7e56e 100644
--- a/drivers/gpu/drm/drm_crtc_internal.h
+++ b/drivers/gpu/drm/drm_crtc_internal.h
@@ -45,20 +45,26 @@ void drm_crtc_unregister_all(struct drm_device *dev);
 
 struct dma_fence *drm_crtc_create_fence(struct drm_crtc *crtc);
 
+int drm_mode_setcrtc(struct drm_device *dev, struct drm_mode_crtc *crtc_req,
+		     struct drm_file *file_priv, bool user);
+
 /* IOCTLs */
 int drm_mode_getcrtc(struct drm_device *dev,
 		     void *data, struct drm_file *file_priv);
-int drm_mode_setcrtc(struct drm_device *dev,
-		     void *data, struct drm_file *file_priv);
+int drm_mode_setcrtc_ioctl(struct drm_device *dev,
+			   void *data, struct drm_file *file_priv);
 
 
 /* drm_mode_config.c */
 int drm_modeset_register_all(struct drm_device *dev);
 void drm_modeset_unregister_all(struct drm_device *dev);
+int drm_mode_getresources(struct drm_device *dev,
+			  struct drm_mode_card_res *card_res,
+			  struct drm_file *file_priv, bool user);
 
 /* IOCTLs */
-int drm_mode_getresources(struct drm_device *dev,
-			  void *data, struct drm_file *file_priv);
+int drm_mode_getresources_ioctl(struct drm_device *dev, void *data,
+				struct drm_file *file_priv);
 
 
 /* drm_dumb_buffers.c */
@@ -143,12 +149,15 @@ int drm_mode_connector_set_obj_prop(struct drm_mode_object *obj,
 int drm_connector_create_standard_properties(struct drm_device *dev);
 const char *drm_get_connector_force_name(enum drm_connector_force force);
 void drm_connector_free_work_fn(struct work_struct *work);
+int drm_mode_getconnector(struct drm_device *dev,
+			  struct drm_mode_get_connector *out_resp,
+			  struct drm_file *file_priv, bool user);
 
 /* IOCTL */
 int drm_mode_connector_property_set_ioctl(struct drm_device *dev,
 					  void *data, struct drm_file *file_priv);
-int drm_mode_getconnector(struct drm_device *dev,
-			  void *data, struct drm_file *file_priv);
+int drm_mode_getconnector_ioctl(struct drm_device *dev,
+				void *data, struct drm_file *file_priv);
 
 /* drm_framebuffer.c */
 struct drm_framebuffer *
@@ -161,6 +170,8 @@ int drm_framebuffer_check_src_coords(uint32_t src_x, uint32_t src_y,
 				     const struct drm_framebuffer *fb);
 void drm_fb_release(struct drm_file *file_priv);
 
+int drm_mode_dirtyfb(struct drm_device *dev, struct drm_mode_fb_dirty_cmd *req,
+		     struct drm_file *file_priv, bool user);
 
 /* IOCTL */
 int drm_mode_addfb(struct drm_device *dev,
diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c
index 5a13ff29f4f0..e918c7124dcd 100644
--- a/drivers/gpu/drm/drm_framebuffer.c
+++ b/drivers/gpu/drm/drm_framebuffer.c
@@ -486,10 +486,11 @@ int drm_mode_getfb(struct drm_device *dev,
 }
 
 /**
- * drm_mode_dirtyfb_ioctl - flush frontbuffer rendering on an FB
- * @dev: drm device for the ioctl
- * @data: data pointer for the ioctl
- * @file_priv: drm file for the ioctl call
+ * drm_mode_dirtyfb - flush frontbuffer rendering on an FB
+ * @dev: drm device
+ * @r: pointer to request structure
+ * @file_priv: drm file
+ * @user: true if called form userspace, false if called by in-kernel client
  *
  * Lookup the FB and flush out the damaged area supplied by userspace as a clip
  * rectangle list. Generic userspace which does frontbuffer rendering must call
@@ -499,17 +500,16 @@ int drm_mode_getfb(struct drm_device *dev,
  * Modesetting drivers which always update the frontbuffer do not need to
  * implement the corresponding &drm_framebuffer_funcs.dirty callback.
  *
- * Called by the user via ioctl.
+ * Called by the user via ioctl, or by an in-kernel client.
  *
  * Returns:
  * Zero on success, negative errno on failure.
  */
-int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
-			   void *data, struct drm_file *file_priv)
+int drm_mode_dirtyfb(struct drm_device *dev, struct drm_mode_fb_dirty_cmd *r,
+		     struct drm_file *file_priv, bool user)
 {
 	struct drm_clip_rect __user *clips_ptr;
 	struct drm_clip_rect *clips = NULL;
-	struct drm_mode_fb_dirty_cmd *r = data;
 	struct drm_framebuffer *fb;
 	unsigned flags;
 	int num_clips;
@@ -523,9 +523,8 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 		return -ENOENT;
 
 	num_clips = r->num_clips;
-	clips_ptr = (struct drm_clip_rect __user *)(unsigned long)r->clips_ptr;
 
-	if (!num_clips != !clips_ptr) {
+	if (!num_clips != !r->clips_ptr) {
 		ret = -EINVAL;
 		goto out_err1;
 	}
@@ -538,22 +537,28 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 		goto out_err1;
 	}
 
-	if (num_clips && clips_ptr) {
+	if (num_clips && r->clips_ptr) {
 		if (num_clips < 0 || num_clips > DRM_MODE_FB_DIRTY_MAX_CLIPS) {
 			ret = -EINVAL;
 			goto out_err1;
 		}
-		clips = kcalloc(num_clips, sizeof(*clips), GFP_KERNEL);
-		if (!clips) {
-			ret = -ENOMEM;
-			goto out_err1;
-		}
 
-		ret = copy_from_user(clips, clips_ptr,
-				     num_clips * sizeof(*clips));
-		if (ret) {
-			ret = -EFAULT;
-			goto out_err2;
+		if (user) {
+			clips_ptr = (struct drm_clip_rect __user *)(unsigned long)r->clips_ptr;
+			clips = kcalloc(num_clips, sizeof(*clips), GFP_KERNEL);
+			if (!clips) {
+				ret = -ENOMEM;
+				goto out_err1;
+			}
+
+			ret = copy_from_user(clips, clips_ptr,
+					     num_clips * sizeof(*clips));
+			if (ret) {
+				ret = -EFAULT;
+				goto out_err2;
+			}
+		} else {
+			clips = (struct drm_clip_rect *)(unsigned long)r->clips_ptr;
 		}
 	}
 
@@ -565,13 +570,20 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 	}
 
 out_err2:
-	kfree(clips);
+	if (user)
+		kfree(clips);
 out_err1:
 	drm_framebuffer_put(fb);
 
 	return ret;
 }
 
+int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
+			   void *data, struct drm_file *file_priv)
+{
+	return drm_mode_dirtyfb(dev, data, file_priv, true);
+}
+
 /**
  * drm_fb_release - remove and free the FBs on this file
  * @priv: drm file for the ioctl
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index af782911c505..346b8060df7c 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -613,21 +613,21 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
 	DRM_IOCTL_DEF(DRM_IOCTL_GEM_FLINK, drm_gem_flink_ioctl, DRM_AUTH|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_GEM_OPEN, drm_gem_open_ioctl, DRM_AUTH|DRM_UNLOCKED),
 
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 
 	DRM_IOCTL_DEF(DRM_IOCTL_PRIME_HANDLE_TO_FD, drm_prime_handle_to_fd_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW),
 	DRM_IOCTL_DEF(DRM_IOCTL_PRIME_FD_TO_HANDLE, drm_prime_fd_to_handle_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW),
 
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANERESOURCES, drm_mode_getplane_res, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANE, drm_mode_getplane, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPLANE, drm_mode_setplane, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETGAMMA, drm_mode_gamma_get_ioctl, DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETGAMMA, drm_mode_gamma_set_ioctl, DRM_MASTER|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETENCODER, drm_mode_getencoder, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATTACHMODE, drm_noop, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_DETACHMODE, drm_noop, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPERTY, drm_mode_getproperty_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index e5c653357024..a5c092e4a099 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -81,19 +81,20 @@ void drm_modeset_unregister_all(struct drm_device *dev)
  * Returns:
  * Zero on success, negative errno on failure.
  */
-int drm_mode_getresources(struct drm_device *dev, void *data,
-			  struct drm_file *file_priv)
+int drm_mode_getresources(struct drm_device *dev,
+			  struct drm_mode_card_res *card_res,
+			  struct drm_file *file_priv, bool user)
 {
-	struct drm_mode_card_res *card_res = data;
 	struct drm_framebuffer *fb;
 	struct drm_connector *connector;
 	struct drm_crtc *crtc;
 	struct drm_encoder *encoder;
 	int count, ret = 0;
-	uint32_t __user *fb_id;
-	uint32_t __user *crtc_id;
-	uint32_t __user *connector_id;
-	uint32_t __user *encoder_id;
+	uint32_t __user *fb_id_user;
+	uint32_t __user *crtc_id_user;
+	uint32_t __user *connector_id_user;
+	uint32_t __user *encoder_id_user;
+	u32 *fb_id, *crtc_id, *connector_id, *encoder_id;
 	struct drm_connector_list_iter conn_iter;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
@@ -102,12 +103,18 @@ int drm_mode_getresources(struct drm_device *dev, void *data,
 
 	mutex_lock(&file_priv->fbs_lock);
 	count = 0;
-	fb_id = u64_to_user_ptr(card_res->fb_id_ptr);
+	fb_id_user = u64_to_user_ptr(card_res->fb_id_ptr);
+	fb_id = (u32 *)(unsigned long)(card_res->fb_id_ptr);
 	list_for_each_entry(fb, &file_priv->fbs, filp_head) {
-		if (count < card_res->count_fbs &&
-		    put_user(fb->base.id, fb_id + count)) {
-			mutex_unlock(&file_priv->fbs_lock);
-			return -EFAULT;
+		if (count < card_res->count_fbs) {
+			if (user) {
+				if (put_user(fb->base.id, fb_id_user + count)) {
+					mutex_unlock(&file_priv->fbs_lock);
+					return -EFAULT;
+				}
+			} else {
+				fb_id[count] = fb->base.id;
+			}
 		}
 		count++;
 	}
@@ -120,36 +127,54 @@ int drm_mode_getresources(struct drm_device *dev, void *data,
 	card_res->min_width = dev->mode_config.min_width;
 
 	count = 0;
-	crtc_id = u64_to_user_ptr(card_res->crtc_id_ptr);
+	crtc_id_user = u64_to_user_ptr(card_res->crtc_id_ptr);
+	crtc_id = (u32 *)(unsigned long)(card_res->crtc_id_ptr);
 	drm_for_each_crtc(crtc, dev) {
 		if (drm_lease_held(file_priv, crtc->base.id)) {
-			if (count < card_res->count_crtcs &&
-			    put_user(crtc->base.id, crtc_id + count))
-				return -EFAULT;
+			if (count < card_res->count_crtcs) {
+				if (user) {
+					if (put_user(crtc->base.id, crtc_id_user + count))
+						return -EFAULT;
+				} else {
+					crtc_id[count] = crtc->base.id;
+				}
+			}
 			count++;
 		}
 	}
 	card_res->count_crtcs = count;
 
 	count = 0;
-	encoder_id = u64_to_user_ptr(card_res->encoder_id_ptr);
+	encoder_id_user = u64_to_user_ptr(card_res->encoder_id_ptr);
+	encoder_id = (u32 *)(unsigned long)(card_res->encoder_id_ptr);
 	drm_for_each_encoder(encoder, dev) {
-		if (count < card_res->count_encoders &&
-		    put_user(encoder->base.id, encoder_id + count))
-			return -EFAULT;
+		if (count < card_res->count_encoders) {
+			if (user) {
+				if (put_user(encoder->base.id, encoder_id_user + count))
+					return -EFAULT;
+			} else {
+				encoder_id[count] = encoder->base.id;
+			}
+		}
 		count++;
 	}
 	card_res->count_encoders = count;
 
 	drm_connector_list_iter_begin(dev, &conn_iter);
 	count = 0;
-	connector_id = u64_to_user_ptr(card_res->connector_id_ptr);
+	connector_id_user = u64_to_user_ptr(card_res->connector_id_ptr);
+	connector_id = (u32 *)(unsigned long)(card_res->connector_id_ptr);
 	drm_for_each_connector_iter(connector, &conn_iter) {
 		if (drm_lease_held(file_priv, connector->base.id)) {
-			if (count < card_res->count_connectors &&
-			    put_user(connector->base.id, connector_id + count)) {
-				drm_connector_list_iter_end(&conn_iter);
-				return -EFAULT;
+			if (count < card_res->count_connectors) {
+				if (user) {
+					if (put_user(connector->base.id, connector_id_user + count)) {
+						drm_connector_list_iter_end(&conn_iter);
+						return -EFAULT;
+					}
+				} else {
+					connector_id[count] = connector->base.id;
+				}
 			}
 			count++;
 		}
@@ -160,6 +185,12 @@ int drm_mode_getresources(struct drm_device *dev, void *data,
 	return ret;
 }
 
+int drm_mode_getresources_ioctl(struct drm_device *dev, void *data,
+				struct drm_file *file_priv)
+{
+	return drm_mode_getresources(dev, data, file_priv, true);
+}
+
 /**
  * drm_mode_config_reset - call ->reset callbacks
  * @dev: drm device
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 04/12] drm: Make ioctls available for in-kernel clients part 2
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (2 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 03/12] drm: Make ioctls available for in-kernel clients part 1 Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-03-06  8:41   ` Daniel Vetter
  2018-02-22 20:06 ` [RFC v3 05/12] drm: Add _ioctl suffix to some functions Noralf Trønnes
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

This is part 2 of making ioctls useable for in-kernel clients.
Make an ioctl wrapper function that calls a function that can be used by
in-kernel clients.

It adjusts the signature of the following functions:
- drm_mode_getcrtc()
- drm_mode_create_dumb_ioctl()
- drm_mode_destroy_dumb_ioctl()
- drm_mode_getencoder()
- drm_mode_addfb2()
- drm_mode_rmfb()
- drm_mode_obj_set_property_ioctl()
- drm_mode_page_flip_ioctl()
- drm_prime_handle_to_fd_ioctl()
- drm_wait_vblank_ioctl()

drm_mode_addfb2() also gets the ability to override the debug name.

There is no functional change from the userspace side.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_crtc.c          | 15 +++++++----
 drivers/gpu/drm/drm_crtc_internal.h | 37 +++++++++++++++++++++------
 drivers/gpu/drm/drm_dumb_buffers.c  | 33 ++++++++++++++++--------
 drivers/gpu/drm/drm_encoder.c       | 10 ++++++--
 drivers/gpu/drm/drm_framebuffer.c   | 50 ++++++++++++++++++++++++-------------
 drivers/gpu/drm/drm_internal.h      |  5 ++++
 drivers/gpu/drm/drm_ioc32.c         |  2 +-
 drivers/gpu/drm/drm_ioctl.c         |  8 +++---
 drivers/gpu/drm/drm_mode_object.c   | 12 ++++++---
 drivers/gpu/drm/drm_plane.c         | 12 ++++++---
 drivers/gpu/drm/drm_prime.c         | 13 +++++++---
 drivers/gpu/drm/drm_vblank.c        | 11 +++++---
 12 files changed, 147 insertions(+), 61 deletions(-)

diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index c9ab1cc6b412..61a6a90fae7e 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -387,8 +387,8 @@ EXPORT_SYMBOL(drm_crtc_cleanup);
 /**
  * drm_mode_getcrtc - get CRTC configuration
  * @dev: drm device for the ioctl
- * @data: data pointer for the ioctl
- * @file_priv: drm file for the ioctl call
+ * @crtc_resp: pointer to crtc request structure
+ * @file_priv: drm file
  *
  * Construct a CRTC configuration structure to return to the user.
  *
@@ -397,10 +397,9 @@ EXPORT_SYMBOL(drm_crtc_cleanup);
  * Returns:
  * Zero on success, negative errno on failure.
  */
-int drm_mode_getcrtc(struct drm_device *dev,
-		     void *data, struct drm_file *file_priv)
+int drm_mode_getcrtc(struct drm_device *dev, struct drm_mode_crtc *crtc_resp,
+		     struct drm_file *file_priv)
 {
-	struct drm_mode_crtc *crtc_resp = data;
 	struct drm_crtc *crtc;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
@@ -451,6 +450,12 @@ int drm_mode_getcrtc(struct drm_device *dev,
 	return 0;
 }
 
+int drm_mode_getcrtc_ioctl(struct drm_device *dev,
+			   void *data, struct drm_file *file_priv)
+{
+	return drm_mode_getcrtc(dev, data, file_priv);
+}
+
 static int __drm_mode_set_config_internal(struct drm_mode_set *set,
 					  struct drm_modeset_acquire_ctx *ctx)
 {
diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h
index 29c59ce7e56e..45713af5a015 100644
--- a/drivers/gpu/drm/drm_crtc_internal.h
+++ b/drivers/gpu/drm/drm_crtc_internal.h
@@ -45,12 +45,14 @@ void drm_crtc_unregister_all(struct drm_device *dev);
 
 struct dma_fence *drm_crtc_create_fence(struct drm_crtc *crtc);
 
+int drm_mode_getcrtc(struct drm_device *dev, struct drm_mode_crtc *crtc_resp,
+		     struct drm_file *file_priv);
 int drm_mode_setcrtc(struct drm_device *dev, struct drm_mode_crtc *crtc_req,
 		     struct drm_file *file_priv, bool user);
 
 /* IOCTLs */
-int drm_mode_getcrtc(struct drm_device *dev,
-		     void *data, struct drm_file *file_priv);
+int drm_mode_getcrtc_ioctl(struct drm_device *dev,
+			   void *data, struct drm_file *file_priv);
 int drm_mode_setcrtc_ioctl(struct drm_device *dev,
 			   void *data, struct drm_file *file_priv);
 
@@ -68,6 +70,12 @@ int drm_mode_getresources_ioctl(struct drm_device *dev, void *data,
 
 
 /* drm_dumb_buffers.c */
+int drm_mode_create_dumb(struct drm_device *dev,
+			 struct drm_mode_create_dumb *args,
+			 struct drm_file *file_priv);
+int drm_mode_destroy_dumb(struct drm_device *dev, u32 handle,
+			  struct drm_file *file_priv);
+
 /* IOCTLs */
 int drm_mode_create_dumb_ioctl(struct drm_device *dev,
 			       void *data, struct drm_file *file_priv);
@@ -122,6 +130,9 @@ int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
 				   uint32_t *arg_count_props);
 struct drm_property *drm_mode_obj_find_prop_id(struct drm_mode_object *obj,
 					       uint32_t prop_id);
+int drm_mode_obj_set_property(struct drm_device *dev,
+			      struct drm_mode_obj_set_property *arg,
+			      struct drm_file *file_priv);
 
 /* IOCTL */
 
@@ -133,10 +144,13 @@ int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
 /* drm_encoder.c */
 int drm_encoder_register_all(struct drm_device *dev);
 void drm_encoder_unregister_all(struct drm_device *dev);
+int drm_mode_getencoder(struct drm_device *dev,
+			struct drm_mode_get_encoder *enc_resp,
+			struct drm_file *file_priv);
 
 /* IOCTL */
-int drm_mode_getencoder(struct drm_device *dev,
-			void *data, struct drm_file *file_priv);
+int drm_mode_getencoder_ioctl(struct drm_device *dev,
+			      void *data, struct drm_file *file_priv);
 
 /* drm_connector.c */
 void drm_connector_ida_init(void);
@@ -170,16 +184,20 @@ int drm_framebuffer_check_src_coords(uint32_t src_x, uint32_t src_y,
 				     const struct drm_framebuffer *fb);
 void drm_fb_release(struct drm_file *file_priv);
 
+int drm_mode_addfb2(struct drm_device *dev, struct drm_mode_fb_cmd2 *r,
+		    struct drm_file *file_priv, const char *comm);
+int drm_mode_rmfb(struct drm_device *dev, u32 fb_id,
+		  struct drm_file *file_priv);
 int drm_mode_dirtyfb(struct drm_device *dev, struct drm_mode_fb_dirty_cmd *req,
 		     struct drm_file *file_priv, bool user);
 
 /* IOCTL */
 int drm_mode_addfb(struct drm_device *dev,
 		   void *data, struct drm_file *file_priv);
-int drm_mode_addfb2(struct drm_device *dev,
-		    void *data, struct drm_file *file_priv);
-int drm_mode_rmfb(struct drm_device *dev,
-		  void *data, struct drm_file *file_priv);
+int drm_mode_addfb2_ioctl(struct drm_device *dev,
+			  void *data, struct drm_file *file_priv);
+int drm_mode_rmfb_ioctl(struct drm_device *dev,
+			void *data, struct drm_file *file_priv);
 int drm_mode_getfb(struct drm_device *dev,
 		   void *data, struct drm_file *file_priv);
 int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
@@ -209,6 +227,9 @@ int drm_plane_register_all(struct drm_device *dev);
 void drm_plane_unregister_all(struct drm_device *dev);
 int drm_plane_check_pixel_format(const struct drm_plane *plane,
 				 u32 format);
+int drm_mode_page_flip(struct drm_device *dev,
+		       struct drm_mode_crtc_page_flip_target *page_flip,
+		       struct drm_file *file_priv);
 
 /* drm_bridge.c */
 void drm_bridge_detach(struct drm_bridge *bridge);
diff --git a/drivers/gpu/drm/drm_dumb_buffers.c b/drivers/gpu/drm/drm_dumb_buffers.c
index 39ac15ce4702..eed9687b8698 100644
--- a/drivers/gpu/drm/drm_dumb_buffers.c
+++ b/drivers/gpu/drm/drm_dumb_buffers.c
@@ -53,10 +53,10 @@
  * a hardware-specific ioctl to allocate suitable buffer objects.
  */
 
-int drm_mode_create_dumb_ioctl(struct drm_device *dev,
-			       void *data, struct drm_file *file_priv)
+int drm_mode_create_dumb(struct drm_device *dev,
+			 struct drm_mode_create_dumb *args,
+			 struct drm_file *file_priv)
 {
-	struct drm_mode_create_dumb *args = data;
 	u32 cpp, stride, size;
 
 	if (!dev->driver->dumb_create)
@@ -91,6 +91,12 @@ int drm_mode_create_dumb_ioctl(struct drm_device *dev,
 	return dev->driver->dumb_create(file_priv, dev, args);
 }
 
+int drm_mode_create_dumb_ioctl(struct drm_device *dev,
+			       void *data, struct drm_file *file_priv)
+{
+	return drm_mode_create_dumb(dev, data, file_priv);
+}
+
 /**
  * drm_mode_mmap_dumb_ioctl - create an mmap offset for a dumb backing storage buffer
  * @dev: DRM device
@@ -122,17 +128,22 @@ int drm_mode_mmap_dumb_ioctl(struct drm_device *dev,
 					       &args->offset);
 }
 
+int drm_mode_destroy_dumb(struct drm_device *dev, u32 handle,
+			  struct drm_file *file_priv)
+{
+	if (!dev->driver->dumb_create)
+		return -ENOSYS;
+
+	if (dev->driver->dumb_destroy)
+		return dev->driver->dumb_destroy(file_priv, dev, handle);
+	else
+		return drm_gem_dumb_destroy(file_priv, dev, handle);
+}
+
 int drm_mode_destroy_dumb_ioctl(struct drm_device *dev,
 				void *data, struct drm_file *file_priv)
 {
 	struct drm_mode_destroy_dumb *args = data;
 
-	if (!dev->driver->dumb_create)
-		return -ENOSYS;
-
-	if (dev->driver->dumb_destroy)
-		return dev->driver->dumb_destroy(file_priv, dev, args->handle);
-	else
-		return drm_gem_dumb_destroy(file_priv, dev, args->handle);
+	return drm_mode_destroy_dumb(dev, args->handle, file_priv);
 }
-
diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c
index 273e1c59c54a..466f3e28b3e9 100644
--- a/drivers/gpu/drm/drm_encoder.c
+++ b/drivers/gpu/drm/drm_encoder.c
@@ -214,10 +214,10 @@ static struct drm_crtc *drm_encoder_get_crtc(struct drm_encoder *encoder)
 	return encoder->crtc;
 }
 
-int drm_mode_getencoder(struct drm_device *dev, void *data,
+int drm_mode_getencoder(struct drm_device *dev,
+			struct drm_mode_get_encoder *enc_resp,
 			struct drm_file *file_priv)
 {
-	struct drm_mode_get_encoder *enc_resp = data;
 	struct drm_encoder *encoder;
 	struct drm_crtc *crtc;
 
@@ -244,3 +244,9 @@ int drm_mode_getencoder(struct drm_device *dev, void *data,
 
 	return 0;
 }
+
+int drm_mode_getencoder_ioctl(struct drm_device *dev, void *data,
+			      struct drm_file *file_priv)
+{
+	return drm_mode_getencoder(dev, data, file_priv);
+}
diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c
index e918c7124dcd..b41770d29e6c 100644
--- a/drivers/gpu/drm/drm_framebuffer.c
+++ b/drivers/gpu/drm/drm_framebuffer.c
@@ -121,7 +121,7 @@ int drm_mode_addfb(struct drm_device *dev,
 	r.pixel_format = drm_mode_legacy_fb_format(or->bpp, or->depth);
 	r.handles[0] = or->handle;
 
-	ret = drm_mode_addfb2(dev, &r, file_priv);
+	ret = drm_mode_addfb2_ioctl(dev, &r, file_priv);
 	if (ret)
 		return ret;
 
@@ -305,23 +305,23 @@ drm_internal_framebuffer_create(struct drm_device *dev,
 
 /**
  * drm_mode_addfb2 - add an FB to the graphics configuration
- * @dev: drm device for the ioctl
- * @data: data pointer for the ioctl
- * @file_priv: drm file for the ioctl call
+ * @dev: drm device
+ * @r: pointer to request structure
+ * @file_priv: drm file
+ * @comm: optionally override the allocator name used for debug output
  *
  * Add a new FB to the specified CRTC, given a user request with format. This is
  * the 2nd version of the addfb ioctl, which supports multi-planar framebuffers
  * and uses fourcc codes as pixel format specifiers.
  *
- * Called by the user via ioctl.
+ * Called by the user via ioctl, or by an in-kernel client.
  *
  * Returns:
  * Zero on success, negative errno on failure.
  */
-int drm_mode_addfb2(struct drm_device *dev,
-		    void *data, struct drm_file *file_priv)
+int drm_mode_addfb2(struct drm_device *dev, struct drm_mode_fb_cmd2 *r,
+		    struct drm_file *file_priv, const char *comm)
 {
-	struct drm_mode_fb_cmd2 *r = data;
 	struct drm_framebuffer *fb;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
@@ -331,6 +331,9 @@ int drm_mode_addfb2(struct drm_device *dev,
 	if (IS_ERR(fb))
 		return PTR_ERR(fb);
 
+	if (comm)
+		strscpy(fb->comm, comm, TASK_COMM_LEN);
+
 	DRM_DEBUG_KMS("[FB:%d]\n", fb->base.id);
 	r->fb_id = fb->base.id;
 
@@ -342,6 +345,12 @@ int drm_mode_addfb2(struct drm_device *dev,
 	return 0;
 }
 
+int drm_mode_addfb2_ioctl(struct drm_device *dev,
+			  void *data, struct drm_file *file_priv)
+{
+	return drm_mode_addfb2(dev, data, file_priv, NULL);
+}
+
 struct drm_mode_rmfb_work {
 	struct work_struct work;
 	struct list_head fbs;
@@ -362,29 +371,28 @@ static void drm_mode_rmfb_work_fn(struct work_struct *w)
 
 /**
  * drm_mode_rmfb - remove an FB from the configuration
- * @dev: drm device for the ioctl
- * @data: data pointer for the ioctl
- * @file_priv: drm file for the ioctl call
+ * @dev: drm device
+ * @fb_id: id of framebuffer to remove
+ * @file_priv: drm file
  *
- * Remove the FB specified by the user.
+ * Remove the specified FB.
  *
- * Called by the user via ioctl.
+ * Called by the user via ioctl, or by an in-kernel client.
  *
  * Returns:
  * Zero on success, negative errno on failure.
  */
-int drm_mode_rmfb(struct drm_device *dev,
-		   void *data, struct drm_file *file_priv)
+int drm_mode_rmfb(struct drm_device *dev, u32 fb_id,
+		  struct drm_file *file_priv)
 {
 	struct drm_framebuffer *fb = NULL;
 	struct drm_framebuffer *fbl = NULL;
-	uint32_t *id = data;
 	int found = 0;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
 		return -EINVAL;
 
-	fb = drm_framebuffer_lookup(dev, file_priv, *id);
+	fb = drm_framebuffer_lookup(dev, file_priv, fb_id);
 	if (!fb)
 		return -ENOENT;
 
@@ -430,6 +438,14 @@ int drm_mode_rmfb(struct drm_device *dev,
 	return -ENOENT;
 }
 
+int drm_mode_rmfb_ioctl(struct drm_device *dev,
+			void *data, struct drm_file *file_priv)
+{
+	uint32_t *fb_id = data;
+
+	return drm_mode_rmfb(dev, *fb_id, file_priv);
+}
+
 /**
  * drm_mode_getfb - get FB info
  * @dev: drm device for the ioctl
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
index 40179c5fc6b8..043814cbd286 100644
--- a/drivers/gpu/drm/drm_internal.h
+++ b/drivers/gpu/drm/drm_internal.h
@@ -37,6 +37,9 @@ void drm_pci_agp_destroy(struct drm_device *dev);
 int drm_pci_set_busid(struct drm_device *dev, struct drm_master *master);
 
 /* drm_prime.c */
+int drm_prime_handle_to_fd(struct drm_device *dev,
+			   struct drm_prime_handle *args,
+			   struct drm_file *file_priv);
 int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
 				 struct drm_file *file_priv);
 int drm_prime_fd_to_handle_ioctl(struct drm_device *dev, void *data,
@@ -59,6 +62,8 @@ int drm_gem_name_info(struct seq_file *m, void *data);
 /* drm_vblank.c */
 void drm_vblank_disable_and_save(struct drm_device *dev, unsigned int pipe);
 void drm_vblank_cleanup(struct drm_device *dev);
+int drm_wait_vblank(struct drm_device *dev, union drm_wait_vblank *vblwait,
+		    struct drm_file *file_priv);
 
 /* IOCTLS */
 int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
diff --git a/drivers/gpu/drm/drm_ioc32.c b/drivers/gpu/drm/drm_ioc32.c
index f8e96e648acf..576d00b7dad5 100644
--- a/drivers/gpu/drm/drm_ioc32.c
+++ b/drivers/gpu/drm/drm_ioc32.c
@@ -884,7 +884,7 @@ static int compat_drm_mode_addfb2(struct file *file, unsigned int cmd,
 			   sizeof(req64.modifier)))
 		return -EFAULT;
 
-	err = drm_ioctl_kernel(file, drm_mode_addfb2, &req64,
+	err = drm_ioctl_kernel(file, drm_mode_addfb2_ioctl, &req64,
 				DRM_CONTROL_ALLOW|DRM_UNLOCKED);
 	if (err)
 		return err;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 346b8060df7c..726fbdb8a4b0 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -619,14 +619,14 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
 	DRM_IOCTL_DEF(DRM_IOCTL_PRIME_FD_TO_HANDLE, drm_prime_fd_to_handle_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW),
 
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANERESOURCES, drm_mode_getplane_res, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANE, drm_mode_getplane, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPLANE, drm_mode_setplane, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETGAMMA, drm_mode_gamma_get_ioctl, DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETGAMMA, drm_mode_gamma_set_ioctl, DRM_MASTER|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETENCODER, drm_mode_getencoder, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETENCODER, drm_mode_getencoder_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATTACHMODE, drm_noop, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_DETACHMODE, drm_noop, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
@@ -635,8 +635,8 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPBLOB, drm_mode_getblob_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_DIRTYFB, drm_mode_dirtyfb_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
diff --git a/drivers/gpu/drm/drm_mode_object.c b/drivers/gpu/drm/drm_mode_object.c
index ce4d2fb32810..2bf7a9e8ac08 100644
--- a/drivers/gpu/drm/drm_mode_object.c
+++ b/drivers/gpu/drm/drm_mode_object.c
@@ -496,10 +496,10 @@ static int set_property_atomic(struct drm_mode_object *obj,
 	return ret;
 }
 
-int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
-				    struct drm_file *file_priv)
+int drm_mode_obj_set_property(struct drm_device *dev,
+			      struct drm_mode_obj_set_property *arg,
+			      struct drm_file *file_priv)
 {
-	struct drm_mode_obj_set_property *arg = data;
 	struct drm_mode_object *arg_obj;
 	struct drm_property *property;
 	int ret = -EINVAL;
@@ -527,3 +527,9 @@ int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
 	drm_mode_object_put(arg_obj);
 	return ret;
 }
+
+int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
+				    struct drm_file *file_priv)
+{
+	return drm_mode_obj_set_property(dev, data, file_priv);
+}
diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
index 22b54663b6e7..b1f55556e196 100644
--- a/drivers/gpu/drm/drm_plane.c
+++ b/drivers/gpu/drm/drm_plane.c
@@ -907,10 +907,10 @@ int drm_mode_cursor2_ioctl(struct drm_device *dev,
 	return drm_mode_cursor_common(dev, req, file_priv);
 }
 
-int drm_mode_page_flip_ioctl(struct drm_device *dev,
-			     void *data, struct drm_file *file_priv)
+int drm_mode_page_flip(struct drm_device *dev,
+		       struct drm_mode_crtc_page_flip_target *page_flip,
+		       struct drm_file *file_priv)
 {
-	struct drm_mode_crtc_page_flip_target *page_flip = data;
 	struct drm_crtc *crtc;
 	struct drm_framebuffer *fb = NULL;
 	struct drm_pending_vblank_event *e = NULL;
@@ -1082,3 +1082,9 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
 
 	return ret;
 }
+
+int drm_mode_page_flip_ioctl(struct drm_device *dev,
+			     void *data, struct drm_file *file_priv)
+{
+	return drm_mode_page_flip(dev, data, file_priv);
+}
diff --git a/drivers/gpu/drm/drm_prime.c b/drivers/gpu/drm/drm_prime.c
index e82a976f0fba..eaf8392ef815 100644
--- a/drivers/gpu/drm/drm_prime.c
+++ b/drivers/gpu/drm/drm_prime.c
@@ -853,11 +853,10 @@ int drm_gem_prime_fd_to_handle(struct drm_device *dev,
 }
 EXPORT_SYMBOL(drm_gem_prime_fd_to_handle);
 
-int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
-				 struct drm_file *file_priv)
+int drm_prime_handle_to_fd(struct drm_device *dev,
+			   struct drm_prime_handle *args,
+			   struct drm_file *file_priv)
 {
-	struct drm_prime_handle *args = data;
-
 	if (!drm_core_check_feature(dev, DRIVER_PRIME))
 		return -EINVAL;
 
@@ -872,6 +871,12 @@ int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
 			args->handle, args->flags, &args->fd);
 }
 
+int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
+				 struct drm_file *file_priv)
+{
+	return drm_prime_handle_to_fd(dev, data, file_priv);
+}
+
 int drm_prime_fd_to_handle_ioctl(struct drm_device *dev, void *data,
 				 struct drm_file *file_priv)
 {
diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c
index 32d9bcf5be7f..ef41508bc539 100644
--- a/drivers/gpu/drm/drm_vblank.c
+++ b/drivers/gpu/drm/drm_vblank.c
@@ -1445,12 +1445,11 @@ static void drm_wait_vblank_reply(struct drm_device *dev, unsigned int pipe,
 	reply->tval_usec = ts.tv_nsec / 1000;
 }
 
-int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
-			  struct drm_file *file_priv)
+int drm_wait_vblank(struct drm_device *dev, union drm_wait_vblank *vblwait,
+		    struct drm_file *file_priv)
 {
 	struct drm_crtc *crtc;
 	struct drm_vblank_crtc *vblank;
-	union drm_wait_vblank *vblwait = data;
 	int ret;
 	u64 req_seq, seq;
 	unsigned int pipe_index;
@@ -1567,6 +1566,12 @@ int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
 	return ret;
 }
 
+int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
+			  struct drm_file *file_priv)
+{
+	return drm_wait_vblank_ioctl(dev, data, file_priv);
+}
+
 static void drm_handle_vblank_events(struct drm_device *dev, unsigned int pipe)
 {
 	struct drm_pending_vblank_event *e, *t;
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 05/12] drm: Add _ioctl suffix to some functions
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (3 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 04/12] drm: Make ioctls available for in-kernel clients part 2 Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-02-22 20:06 ` [RFC v3 06/12] drm: Add DRM device iterator Noralf Trønnes
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

Add _ioctl suffix to the remaining ioctl functions so they match up with
the others:
- drm_mode_addfb()
- drm_mode_getfb()
- drm_mode_getplane_res()
- drm_mode_getplane()
- drm_mode_setplane()

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_crtc_internal.h | 20 ++++++++++----------
 drivers/gpu/drm/drm_framebuffer.c   | 12 ++++++------
 drivers/gpu/drm/drm_ioctl.c         | 10 +++++-----
 drivers/gpu/drm/drm_plane.c         | 12 ++++++------
 4 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h
index 45713af5a015..3a9dd1dae06e 100644
--- a/drivers/gpu/drm/drm_crtc_internal.h
+++ b/drivers/gpu/drm/drm_crtc_internal.h
@@ -192,14 +192,14 @@ int drm_mode_dirtyfb(struct drm_device *dev, struct drm_mode_fb_dirty_cmd *req,
 		     struct drm_file *file_priv, bool user);
 
 /* IOCTL */
-int drm_mode_addfb(struct drm_device *dev,
-		   void *data, struct drm_file *file_priv);
+int drm_mode_addfb_ioctl(struct drm_device *dev,
+			 void *data, struct drm_file *file_priv);
 int drm_mode_addfb2_ioctl(struct drm_device *dev,
 			  void *data, struct drm_file *file_priv);
 int drm_mode_rmfb_ioctl(struct drm_device *dev,
 			void *data, struct drm_file *file_priv);
-int drm_mode_getfb(struct drm_device *dev,
-		   void *data, struct drm_file *file_priv);
+int drm_mode_getfb_ioctl(struct drm_device *dev,
+			 void *data, struct drm_file *file_priv);
 int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 			   void *data, struct drm_file *file_priv);
 
@@ -235,12 +235,12 @@ int drm_mode_page_flip(struct drm_device *dev,
 void drm_bridge_detach(struct drm_bridge *bridge);
 
 /* IOCTL */
-int drm_mode_getplane_res(struct drm_device *dev, void *data,
-			  struct drm_file *file_priv);
-int drm_mode_getplane(struct drm_device *dev,
-		      void *data, struct drm_file *file_priv);
-int drm_mode_setplane(struct drm_device *dev,
-		      void *data, struct drm_file *file_priv);
+int drm_mode_getplane_res_ioctl(struct drm_device *dev, void *data,
+				struct drm_file *file_priv);
+int drm_mode_getplane_ioctl(struct drm_device *dev,
+			    void *data, struct drm_file *file_priv);
+int drm_mode_setplane_ioctl(struct drm_device *dev,
+			    void *data, struct drm_file *file_priv);
 int drm_mode_cursor_ioctl(struct drm_device *dev,
 			  void *data, struct drm_file *file_priv);
 int drm_mode_cursor2_ioctl(struct drm_device *dev,
diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c
index b41770d29e6c..ad8f7d308656 100644
--- a/drivers/gpu/drm/drm_framebuffer.c
+++ b/drivers/gpu/drm/drm_framebuffer.c
@@ -93,7 +93,7 @@ int drm_framebuffer_check_src_coords(uint32_t src_x, uint32_t src_y,
 }
 
 /**
- * drm_mode_addfb - add an FB to the graphics configuration
+ * drm_mode_addfb_ioctl - add an FB to the graphics configuration
  * @dev: drm device for the ioctl
  * @data: data pointer for the ioctl
  * @file_priv: drm file for the ioctl call
@@ -106,8 +106,8 @@ int drm_framebuffer_check_src_coords(uint32_t src_x, uint32_t src_y,
  * Returns:
  * Zero on success, negative errno on failure.
  */
-int drm_mode_addfb(struct drm_device *dev,
-		   void *data, struct drm_file *file_priv)
+int drm_mode_addfb_ioctl(struct drm_device *dev,
+			 void *data, struct drm_file *file_priv)
 {
 	struct drm_mode_fb_cmd *or = data;
 	struct drm_mode_fb_cmd2 r = {};
@@ -447,7 +447,7 @@ int drm_mode_rmfb_ioctl(struct drm_device *dev,
 }
 
 /**
- * drm_mode_getfb - get FB info
+ * drm_mode_getfb_ioctl - get FB info
  * @dev: drm device for the ioctl
  * @data: data pointer for the ioctl
  * @file_priv: drm file for the ioctl call
@@ -459,8 +459,8 @@ int drm_mode_rmfb_ioctl(struct drm_device *dev,
  * Returns:
  * Zero on success, negative errno on failure.
  */
-int drm_mode_getfb(struct drm_device *dev,
-		   void *data, struct drm_file *file_priv)
+int drm_mode_getfb_ioctl(struct drm_device *dev,
+			 void *data, struct drm_file *file_priv)
 {
 	struct drm_mode_fb_cmd *r = data;
 	struct drm_framebuffer *fb;
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index 726fbdb8a4b0..f00bea8b0da3 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -618,11 +618,11 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
 	DRM_IOCTL_DEF(DRM_IOCTL_PRIME_HANDLE_TO_FD, drm_prime_handle_to_fd_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW),
 	DRM_IOCTL_DEF(DRM_IOCTL_PRIME_FD_TO_HANDLE, drm_prime_fd_to_handle_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW),
 
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANERESOURCES, drm_mode_getplane_res, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANERESOURCES, drm_mode_getplane_res_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANE, drm_mode_getplane, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPLANE, drm_mode_setplane, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANE, drm_mode_getplane_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPLANE, drm_mode_setplane_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETGAMMA, drm_mode_gamma_get_ioctl, DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETGAMMA, drm_mode_gamma_set_ioctl, DRM_MASTER|DRM_UNLOCKED),
@@ -633,8 +633,8 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPERTY, drm_mode_getproperty_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPROPERTY, drm_mode_connector_property_set_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPBLOB, drm_mode_getblob_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
-	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
 	DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
index b1f55556e196..be52e627fd41 100644
--- a/drivers/gpu/drm/drm_plane.c
+++ b/drivers/gpu/drm/drm_plane.c
@@ -455,8 +455,8 @@ int drm_mode_plane_set_obj_prop(struct drm_plane *plane,
 }
 EXPORT_SYMBOL(drm_mode_plane_set_obj_prop);
 
-int drm_mode_getplane_res(struct drm_device *dev, void *data,
-			  struct drm_file *file_priv)
+int drm_mode_getplane_res_ioctl(struct drm_device *dev, void *data,
+				struct drm_file *file_priv)
 {
 	struct drm_mode_get_plane_res *plane_resp = data;
 	struct drm_mode_config *config;
@@ -495,8 +495,8 @@ int drm_mode_getplane_res(struct drm_device *dev, void *data,
 	return 0;
 }
 
-int drm_mode_getplane(struct drm_device *dev, void *data,
-		      struct drm_file *file_priv)
+int drm_mode_getplane_ioctl(struct drm_device *dev, void *data,
+			    struct drm_file *file_priv)
 {
 	struct drm_mode_get_plane *plane_resp = data;
 	struct drm_plane *plane;
@@ -679,8 +679,8 @@ static int setplane_internal(struct drm_plane *plane,
 	return ret;
 }
 
-int drm_mode_setplane(struct drm_device *dev, void *data,
-		      struct drm_file *file_priv)
+int drm_mode_setplane_ioctl(struct drm_device *dev, void *data,
+			    struct drm_file *file_priv)
 {
 	struct drm_mode_set_plane *plane_req = data;
 	struct drm_plane *plane;
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 06/12] drm: Add DRM device iterator
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (4 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 05/12] drm: Add _ioctl suffix to some functions Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-02-22 20:06 ` [RFC v3 07/12] drm/modes: Add drm_umode_equal() Noralf Trønnes
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

Add functions for iterating the registered DRM devices.
This is done looping through the primary minors instead of using an
iter on drm_class which is also a possibility. The reason is that
drm_minor_acquire() takes a ref on the drm_device which is needed.

Another option would be to add a separate drm_device list.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---

Does anyone know how I can make checkpatch happy, I've tried parentheses
around both dev and iter:

-:129: ERROR: Macros with complex values should be enclosed in parentheses
#129: FILE: include/drm/drm_drv.h:679:
+#define drm_for_each_device_iter(dev, iter) \
+       while ((dev = drm_device_list_iter_next(iter)))



 drivers/gpu/drm/drm_drv.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_drv.h     | 34 +++++++++++++++++++++++++++
 2 files changed, 94 insertions(+)

diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index 9acc1e157813..f869de185986 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -862,6 +862,66 @@ int drm_dev_set_unique(struct drm_device *dev, const char *name)
 }
 EXPORT_SYMBOL(drm_dev_set_unique);
 
+/**
+ * drm_device_list_iter_begin - Initialize a DRM device iterator
+ * @iter: DRM device iterator
+ *
+ * Sets @iter up to walk the registered DRM devices. @iter must always be
+ * cleaned up again by calling drm_device_list_iter_end(). Iteration itself
+ * happens using drm_device_list_iter_next() or drm_for_each_device_iter().
+ */
+void drm_device_list_iter_begin(struct drm_device_list_iter *iter)
+{
+	iter->dev = NULL;
+	iter->minor_id = 0;
+}
+EXPORT_SYMBOL(drm_device_list_iter_begin);
+
+/**
+ * drm_device_list_iter_next - Return the next DRM device
+ * @iter: DRM device iterator
+ *
+ * Returns the next DRM device for @iter, or NULL when there are no more
+ * devices.
+ */
+struct drm_device *
+drm_device_list_iter_next(struct drm_device_list_iter *iter)
+{
+	struct drm_minor *minor;
+
+	drm_dev_put(iter->dev);
+	iter->dev = NULL;
+
+	/* Loop through the primary minors */
+	for (; iter->minor_id < 64; iter->minor_id++) {
+		minor = drm_minor_acquire(iter->minor_id);
+		if (IS_ERR(minor))
+			continue;
+
+		iter->dev = minor->dev;
+		iter->minor_id++;
+		return minor->dev;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL(drm_device_list_iter_next);
+
+/**
+ * drm_device_list_iter_end - Tear down a DRM device iterator
+ * @iter: DRM device iterator
+ *
+ * Tears down @iter and releases any resources (like &drm_device references)
+ * acquired while walking the devices. This must always be called, both when
+ * the iteration completes fully or when it was aborted without walking the
+ * entire list.
+ */
+void drm_device_list_iter_end(struct drm_device_list_iter *iter)
+{
+	drm_dev_put(iter->dev);
+}
+EXPORT_SYMBOL(drm_device_list_iter_end);
+
 /*
  * DRM Core
  * The DRM core module initializes all global DRM objects and makes them
diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h
index d32b688eb346..313a23ee7b30 100644
--- a/include/drm/drm_drv.h
+++ b/include/drm/drm_drv.h
@@ -644,5 +644,39 @@ static inline int drm_dev_is_unplugged(struct drm_device *dev)
 
 int drm_dev_set_unique(struct drm_device *dev, const char *name);
 
+/**
+ * struct drm_device_list_iter - DRM device iterator
+ *
+ * This iterator tracks state needed to be able to walk the registered
+ * DRM devices. Only use together with drm_device_list_iter_begin(),
+ * drm_device_list_iter_end() and drm_device_list_iter_next() respectively
+ * the convenience macro drm_for_each_device_iter().
+ */
+struct drm_device_list_iter {
+/* private: */
+	unsigned int minor_id;
+	struct drm_device *dev;
+};
+
+void drm_device_list_iter_begin(struct drm_device_list_iter *iter);
+struct drm_device *
+drm_device_list_iter_next(struct drm_device_list_iter *iter);
+void drm_device_list_iter_end(struct drm_device_list_iter *iter);
+
+/**
+ * drm_for_each_device_iter - DRM device iterator macro
+ * @dev: DRM device pointer used as cursor
+ * @iter: DRM device iterator
+ *
+ * Note that @dev is only valid within the list body, if you want to use @dev
+ * after calling drm_device_list_iter_end() then you need to grab your own
+ * reference first using drm_dev_get().
+ *
+ * Note:
+ * The DRM device was registered at the point when the reference was taken,
+ * but it's not guaranteed that this is still the case inside the loop.
+ */
+#define drm_for_each_device_iter(dev, iter) \
+	while ((dev = drm_device_list_iter_next(iter)))
 
 #endif
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 07/12] drm/modes: Add drm_umode_equal()
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (5 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 06/12] drm: Add DRM device iterator Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-03-06  8:42   ` Daniel Vetter
  2018-02-22 20:06 ` [RFC v3 08/12] drm/framebuffer: Add drm_mode_can_dirtyfb() Noralf Trønnes
                   ` (5 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

Add a way to check if userspace modes are equal. Useful for in-kernel
clients. Also export drm_mode_convert_umode().

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_modes.c | 50 +++++++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_modes.h     |  2 ++
 2 files changed, 52 insertions(+)

diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
index 5a8033fda4e3..0e39164f15aa 100644
--- a/drivers/gpu/drm/drm_modes.c
+++ b/drivers/gpu/drm/drm_modes.c
@@ -1631,6 +1631,56 @@ int drm_mode_convert_umode(struct drm_device *dev,
 out:
 	return ret;
 }
+EXPORT_SYMBOL(drm_mode_convert_umode);
+
+/**
+ * drm_umode_equal - test modeinfo modes for equality
+ * @mode1: first mode
+ * @mode2: second mode
+ *
+ * Check to see if @mode1 and @mode2 are equivalent.
+ *
+ * Returns:
+ * True if the modes are equal, false otherwise.
+ */
+bool drm_umode_equal(const struct drm_mode_modeinfo *mode1,
+		     const struct drm_mode_modeinfo *mode2)
+{
+	if (!mode1 && !mode2)
+		return true;
+
+	if (!mode1 || !mode2)
+		return false;
+
+	/* do clock check convert to PICOS so fb modes get matched the same */
+	if (mode1->clock && mode2->clock) {
+		if (KHZ2PICOS(mode1->clock) != KHZ2PICOS(mode2->clock))
+			return false;
+	} else if (mode1->clock != mode2->clock) {
+		return false;
+	}
+
+	if ((mode1->flags & DRM_MODE_FLAG_3D_MASK) !=
+	    (mode2->flags & DRM_MODE_FLAG_3D_MASK))
+		return false;
+
+	if (mode1->hdisplay == mode2->hdisplay &&
+	    mode1->hsync_start == mode2->hsync_start &&
+	    mode1->hsync_end == mode2->hsync_end &&
+	    mode1->htotal == mode2->htotal &&
+	    mode1->hskew == mode2->hskew &&
+	    mode1->vdisplay == mode2->vdisplay &&
+	    mode1->vsync_start == mode2->vsync_start &&
+	    mode1->vsync_end == mode2->vsync_end &&
+	    mode1->vtotal == mode2->vtotal &&
+	    mode1->vscan == mode2->vscan &&
+	    (mode1->flags & ~DRM_MODE_FLAG_3D_MASK) ==
+	     (mode2->flags & ~DRM_MODE_FLAG_3D_MASK))
+		return true;
+
+	return false;
+}
+EXPORT_SYMBOL(drm_umode_equal);
 
 /**
  * drm_mode_is_420_only - if a given videomode can be only supported in YCBCR420
diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h
index 0d310beae6af..05e73ca4f2ae 100644
--- a/include/drm/drm_modes.h
+++ b/include/drm/drm_modes.h
@@ -447,6 +447,8 @@ void drm_mode_convert_to_umode(struct drm_mode_modeinfo *out,
 int drm_mode_convert_umode(struct drm_device *dev,
 			   struct drm_display_mode *out,
 			   const struct drm_mode_modeinfo *in);
+bool drm_umode_equal(const struct drm_mode_modeinfo *mode1,
+		     const struct drm_mode_modeinfo *mode2);
 void drm_mode_probed_add(struct drm_connector *connector, struct drm_display_mode *mode);
 void drm_mode_debug_printmodeline(const struct drm_display_mode *mode);
 bool drm_mode_is_420_only(const struct drm_display_info *display,
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 08/12] drm/framebuffer: Add drm_mode_can_dirtyfb()
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (6 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 07/12] drm/modes: Add drm_umode_equal() Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-03-06  8:45   ` Daniel Vetter
  2018-02-22 20:06 ` [RFC v3 09/12] drm: Add API for in-kernel clients Noralf Trønnes
                   ` (4 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

Add a function so the generic fbdev client can check if the framebuffer
does flushing. This is needed to set up deferred I/O.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_framebuffer.c | 31 +++++++++++++++++++++++++++++++
 include/drm/drm_framebuffer.h     |  2 ++
 2 files changed, 33 insertions(+)

diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c
index ad8f7d308656..a659cff45844 100644
--- a/drivers/gpu/drm/drm_framebuffer.c
+++ b/drivers/gpu/drm/drm_framebuffer.c
@@ -600,6 +600,37 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 	return drm_mode_dirtyfb(dev, data, file_priv, true);
 }
 
+/**
+ * drm_mode_can_dirtyfb - check if the FB does flushing
+ * @dev: drm device
+ * @fb_id: Framebuffer id
+ * @file_priv: drm file
+ *
+ * Returns:
+ * True if the framebuffer does flushing, false otherwise.
+ */
+bool drm_mode_can_dirtyfb(struct drm_device *dev, u32 fb_id,
+			  struct drm_file *file_priv)
+{
+	struct drm_framebuffer *fb;
+	bool ret = false;
+
+	if (!drm_core_check_feature(dev, DRIVER_MODESET))
+		return false;
+
+	fb = drm_framebuffer_lookup(dev, file_priv, fb_id);
+	if (!fb)
+		return false;
+
+	if (fb->funcs->dirty)
+		ret = true;
+
+	drm_framebuffer_put(fb);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_mode_can_dirtyfb);
+
 /**
  * drm_fb_release - remove and free the FBs on this file
  * @priv: drm file for the ioctl
diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h
index c50502c656e5..05d170f4e215 100644
--- a/include/drm/drm_framebuffer.h
+++ b/include/drm/drm_framebuffer.h
@@ -216,6 +216,8 @@ struct drm_framebuffer *drm_framebuffer_lookup(struct drm_device *dev,
 void drm_framebuffer_remove(struct drm_framebuffer *fb);
 void drm_framebuffer_cleanup(struct drm_framebuffer *fb);
 void drm_framebuffer_unregister_private(struct drm_framebuffer *fb);
+bool drm_mode_can_dirtyfb(struct drm_device *dev, u32 fb_id,
+			  struct drm_file *file_priv);
 
 /**
  * drm_framebuffer_get - acquire a framebuffer reference
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 09/12] drm: Add API for in-kernel clients
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (7 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 08/12] drm/framebuffer: Add drm_mode_can_dirtyfb() Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-03-06  8:56   ` Daniel Vetter
  2018-02-22 20:06 ` [RFC v3 10/12] drm/client: Add fbdev emulation client Noralf Trønnes
                   ` (3 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

This adds an API for writing in-kernel clients.

TODO:
- Flesh out and complete documentation.
- Cloned displays is not tested.
- Complete tiled display support and test it.
- Test plug/unplug different monitors.
- A runtime knob to prevent clients from attaching for debugging purposes.
- Maybe a way to unbind individual client instances.
- Maybe take the sysrq support in drm_fb_helper and move it here somehow.
- Add suspend/resume callbacks.
  Does anyone know why fbdev requires suspend/resume?

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/Kconfig             |    2 +
 drivers/gpu/drm/Makefile            |    3 +-
 drivers/gpu/drm/client/Kconfig      |    4 +
 drivers/gpu/drm/client/Makefile     |    1 +
 drivers/gpu/drm/client/drm_client.c | 1612 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_drv.c           |    6 +
 drivers/gpu/drm/drm_file.c          |    3 +
 drivers/gpu/drm/drm_probe_helper.c  |    3 +
 include/drm/drm_client.h            |  192 +++++
 include/drm/drm_device.h            |    1 +
 include/drm/drm_file.h              |    7 +
 11 files changed, 1833 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/client/Kconfig
 create mode 100644 drivers/gpu/drm/client/Makefile
 create mode 100644 drivers/gpu/drm/client/drm_client.c
 create mode 100644 include/drm/drm_client.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index deeefa7a1773..d4ae15f9ee9f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -154,6 +154,8 @@ config DRM_SCHED
 	tristate
 	depends on DRM
 
+source "drivers/gpu/drm/client/Kconfig"
+
 source "drivers/gpu/drm/i2c/Kconfig"
 
 source "drivers/gpu/drm/arm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 50093ff4479b..8e06dc7eeca1 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
 		drm_encoder.o drm_mode_object.o drm_property.o \
 		drm_plane.o drm_color_mgmt.o drm_print.o \
 		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
-		drm_syncobj.o drm_lease.o
+		drm_syncobj.o drm_lease.o client/drm_client.o
 
 drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
 drm-$(CONFIG_DRM_VM) += drm_vm.o
@@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
 obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
 obj-$(CONFIG_DRM_PL111) += pl111/
 obj-$(CONFIG_DRM_TVE200) += tve200/
+obj-y			+= client/
diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
new file mode 100644
index 000000000000..4bb8e4655ff7
--- /dev/null
+++ b/drivers/gpu/drm/client/Kconfig
@@ -0,0 +1,4 @@
+menu "DRM Clients"
+	depends on DRM
+
+endmenu
diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
new file mode 100644
index 000000000000..f66554cd5c45
--- /dev/null
+++ b/drivers/gpu/drm/client/Makefile
@@ -0,0 +1 @@
+# SPDX-License-Identifier: GPL-2.0
diff --git a/drivers/gpu/drm/client/drm_client.c b/drivers/gpu/drm/client/drm_client.c
new file mode 100644
index 000000000000..a633bf747316
--- /dev/null
+++ b/drivers/gpu/drm/client/drm_client.c
@@ -0,0 +1,1612 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2018 Noralf Trønnes
+
+#include <linux/dma-buf.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drmP.h>
+
+#include "drm_crtc_internal.h"
+#include "drm_internal.h"
+
+struct drm_client_funcs_entry {
+	struct list_head list;
+	const struct drm_client_funcs *funcs;
+};
+
+static LIST_HEAD(drm_client_list);
+static LIST_HEAD(drm_client_funcs_list);
+static DEFINE_MUTEX(drm_client_list_lock);
+
+static void drm_client_new(struct drm_device *dev,
+			   const struct drm_client_funcs *funcs)
+{
+	struct drm_client_dev *client;
+	int ret;
+
+	lockdep_assert_held(&drm_client_list_lock);
+
+	client = kzalloc(sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return;
+
+	mutex_init(&client->lock);
+	client->dev = dev;
+	client->funcs = funcs;
+
+	ret = funcs->new(client);
+	DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", funcs->name, ret);
+	if (ret) {
+		drm_client_free(client);
+		return;
+	}
+
+	list_add(&client->list, &drm_client_list);
+}
+
+/**
+ * drm_client_free - Free DRM client resources
+ * @client: DRM client
+ *
+ * This is called automatically on client removal unless the client returns
+ * non-zero in the &drm_client_funcs->remove callback. The fbdev client does
+ * this when it can't close &drm_file because userspace has an open fd.
+ */
+void drm_client_free(struct drm_client_dev *client)
+{
+	DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
+	if (WARN_ON(client->file)) {
+		client->file_ref_count = 1;
+		drm_client_put_file(client);
+	}
+	mutex_destroy(&client->lock);
+	kfree(client->crtcs);
+	kfree(client);
+}
+EXPORT_SYMBOL(drm_client_free);
+
+static void drm_client_remove(struct drm_client_dev *client)
+{
+	lockdep_assert_held(&drm_client_list_lock);
+
+	list_del(&client->list);
+
+	if (!client->funcs->remove || !client->funcs->remove(client))
+		drm_client_free(client);
+}
+
+/**
+ * drm_client_register - Register a DRM client
+ * @funcs: Client callbacks
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int drm_client_register(const struct drm_client_funcs *funcs)
+{
+	struct drm_client_funcs_entry *funcs_entry;
+	struct drm_device_list_iter iter;
+	struct drm_device *dev;
+
+	funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
+	if (!funcs_entry)
+		return -ENOMEM;
+
+	funcs_entry->funcs = funcs;
+
+	mutex_lock(&drm_global_mutex);
+	mutex_lock(&drm_client_list_lock);
+
+	drm_device_list_iter_begin(&iter);
+	drm_for_each_device_iter(dev, &iter)
+		if (drm_core_check_feature(dev, DRIVER_MODESET))
+			drm_client_new(dev, funcs);
+	drm_device_list_iter_end(&iter);
+
+	list_add(&funcs_entry->list, &drm_client_funcs_list);
+
+	mutex_unlock(&drm_client_list_lock);
+	mutex_unlock(&drm_global_mutex);
+
+	DRM_DEBUG_KMS("%s\n", funcs->name);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_client_register);
+
+/**
+ * drm_client_unregister - Unregister a DRM client
+ * @funcs: Client callbacks
+ */
+void drm_client_unregister(const struct drm_client_funcs *funcs)
+{
+	struct drm_client_funcs_entry *funcs_entry;
+	struct drm_client_dev *client, *tmp;
+
+	mutex_lock(&drm_client_list_lock);
+
+	list_for_each_entry_safe(client, tmp, &drm_client_list, list) {
+		if (client->funcs == funcs)
+			drm_client_remove(client);
+	}
+
+	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list) {
+		if (funcs_entry->funcs == funcs) {
+			list_del(&funcs_entry->list);
+			kfree(funcs_entry);
+			break;
+		}
+	}
+
+	mutex_unlock(&drm_client_list_lock);
+
+	DRM_DEBUG_KMS("%s\n", funcs->name);
+}
+EXPORT_SYMBOL(drm_client_unregister);
+
+void drm_client_dev_register(struct drm_device *dev)
+{
+	struct drm_client_funcs_entry *funcs_entry;
+
+	/*
+	 * Minors are created at the beginning of drm_dev_register(), but can
+	 * be removed again if the function fails. Since we iterate DRM devices
+	 * by walking DRM minors, we need to stay under this lock.
+	 */
+	lockdep_assert_held(&drm_global_mutex);
+
+	if (!drm_core_check_feature(dev, DRIVER_MODESET))
+		return;
+
+	mutex_lock(&drm_client_list_lock);
+	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list)
+		drm_client_new(dev, funcs_entry->funcs);
+	mutex_unlock(&drm_client_list_lock);
+}
+
+void drm_client_dev_unregister(struct drm_device *dev)
+{
+	struct drm_client_dev *client, *tmp;
+
+	if (!drm_core_check_feature(dev, DRIVER_MODESET))
+		return;
+
+	mutex_lock(&drm_client_list_lock);
+	list_for_each_entry_safe(client, tmp, &drm_client_list, list)
+		if (client->dev == dev)
+			drm_client_remove(client);
+	mutex_unlock(&drm_client_list_lock);
+}
+
+void drm_client_dev_hotplug(struct drm_device *dev)
+{
+	struct drm_client_dev *client;
+	int ret;
+
+	if (!drm_core_check_feature(dev, DRIVER_MODESET))
+		return;
+
+	mutex_lock(&drm_client_list_lock);
+	list_for_each_entry(client, &drm_client_list, list)
+		if (client->dev == dev && client->funcs->hotplug) {
+			ret = client->funcs->hotplug(client);
+			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
+					  client->funcs->name, ret);
+		}
+	mutex_unlock(&drm_client_list_lock);
+}
+
+void drm_client_dev_lastclose(struct drm_device *dev)
+{
+	struct drm_client_dev *client;
+	int ret;
+
+	if (!drm_core_check_feature(dev, DRIVER_MODESET))
+		return;
+
+	mutex_lock(&drm_client_list_lock);
+	list_for_each_entry(client, &drm_client_list, list)
+		if (client->dev == dev && client->funcs->lastclose) {
+			ret = client->funcs->lastclose(client);
+			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
+					  client->funcs->name, ret);
+		}
+	mutex_unlock(&drm_client_list_lock);
+}
+
+/* Get static info */
+static int drm_client_init(struct drm_client_dev *client, struct drm_file *file)
+{
+	struct drm_mode_card_res card_res = {};
+	struct drm_device *dev = client->dev;
+	u32 *crtcs;
+	int ret;
+
+	ret = drm_mode_getresources(dev, &card_res, file, false);
+	if (ret)
+		return ret;
+	if (!card_res.count_crtcs)
+		return -ENOENT;
+
+	crtcs = kmalloc_array(card_res.count_crtcs, sizeof(*crtcs), GFP_KERNEL);
+	if (!crtcs)
+		return -ENOMEM;
+
+	card_res.count_fbs = 0;
+	card_res.count_connectors = 0;
+	card_res.count_encoders = 0;
+	card_res.crtc_id_ptr = (unsigned long)crtcs;
+
+	ret = drm_mode_getresources(dev, &card_res, file, false);
+	if (ret) {
+		kfree(crtcs);
+		return ret;
+	}
+
+	client->crtcs = crtcs;
+	client->num_crtcs = card_res.count_crtcs;
+	client->min_width = card_res.min_width;
+	client->max_width = card_res.max_width;
+	client->min_height = card_res.min_height;
+	client->max_height = card_res.max_height;
+
+	return 0;
+}
+
+/**
+ * drm_client_get_file - Get a DRM file
+ * @client: DRM client
+ *
+ * This function makes sure the client has a &drm_file available. The client
+ * doesn't normally need to call this, since all client functions that depends
+ * on a DRM file will call it. A matching call to drm_client_put_file() is
+ * necessary.
+ *
+ * The reason for not opening a DRM file when a @client is created is because
+ * we have to take a ref on the driver module due to &drm_driver->postclose
+ * being called in drm_file_free(). Having a DRM file open for the lifetime of
+ * the client instance would block driver module unload.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int drm_client_get_file(struct drm_client_dev *client)
+{
+	struct drm_device *dev = client->dev;
+	struct drm_file *file;
+	int ret = 0;
+
+	mutex_lock(&client->lock);
+
+	if (client->file_ref_count++) {
+		mutex_unlock(&client->lock);
+		return 0;
+	}
+
+	if (!try_module_get(dev->driver->fops->owner)) {
+		ret = -ENODEV;
+		goto err_unlock;
+	}
+
+	drm_dev_get(dev);
+
+	file = drm_file_alloc(dev->primary);
+	if (IS_ERR(file)) {
+		ret = PTR_ERR(file);
+		goto err_put;
+	}
+
+	if (!client->crtcs) {
+		ret = drm_client_init(client, file);
+		if (ret)
+			goto err_free;
+	}
+
+	mutex_lock(&dev->filelist_mutex);
+	list_add(&file->lhead, &dev->filelist_internal);
+	mutex_unlock(&dev->filelist_mutex);
+
+	client->file = file;
+
+	mutex_unlock(&client->lock);
+
+	return 0;
+
+err_free:
+	drm_file_free(file);
+err_put:
+	drm_dev_put(dev);
+	module_put(dev->driver->fops->owner);
+err_unlock:
+	client->file_ref_count = 0;
+	mutex_unlock(&client->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_get_file);
+
+void drm_client_put_file(struct drm_client_dev *client)
+{
+	struct drm_device *dev = client->dev;
+
+	if (!client)
+		return;
+
+	mutex_lock(&client->lock);
+
+	if (WARN_ON(!client->file_ref_count))
+		goto out_unlock;
+
+	if (--client->file_ref_count)
+		goto out_unlock;
+
+	mutex_lock(&dev->filelist_mutex);
+	list_del(&client->file->lhead);
+	mutex_unlock(&dev->filelist_mutex);
+
+	drm_file_free(client->file);
+	client->file = NULL;
+	drm_dev_put(dev);
+	module_put(dev->driver->fops->owner);
+out_unlock:
+	mutex_unlock(&client->lock);
+}
+EXPORT_SYMBOL(drm_client_put_file);
+
+static struct drm_pending_event *
+drm_client_read_get_pending_event(struct drm_device *dev, struct drm_file *file)
+{
+	struct drm_pending_event *e = NULL;
+	int ret;
+
+	ret = mutex_lock_interruptible(&file->event_read_lock);
+	if (ret)
+		return ERR_PTR(ret);
+
+	spin_lock_irq(&dev->event_lock);
+	if (!list_empty(&file->event_list)) {
+		e = list_first_entry(&file->event_list,
+				     struct drm_pending_event, link);
+		file->event_space += e->event->length;
+		list_del(&e->link);
+	}
+	spin_unlock_irq(&dev->event_lock);
+
+	mutex_unlock(&file->event_read_lock);
+
+	return e;
+}
+
+struct drm_event *
+drm_client_read_event(struct drm_client_dev *client, bool block)
+{
+	struct drm_file *file = client->file;
+	struct drm_device *dev = client->dev;
+	struct drm_pending_event *e;
+	struct drm_event *event;
+	int ret;
+
+	/* Allocate so it fits all events, there's a sanity check later */
+	event = kzalloc(128, GFP_KERNEL);
+	if (!event)
+		return ERR_PTR(-ENOMEM);
+
+	e = drm_client_read_get_pending_event(dev, file);
+	if (IS_ERR(e)) {
+		ret = PTR_ERR(e);
+		goto err_free;
+	}
+
+	if (!e && !block) {
+		ret = 0;
+		goto err_free;
+	}
+
+	ret = wait_event_interruptible_timeout(file->event_wait,
+					       !list_empty(&file->event_list),
+					       5 * HZ);
+	if (!ret)
+		ret = -ETIMEDOUT;
+	if (ret < 0)
+		goto err_free;
+
+	e = drm_client_read_get_pending_event(dev, file);
+	if (IS_ERR_OR_NULL(e)) {
+		ret = PTR_ERR_OR_ZERO(e);
+		goto err_free;
+	}
+
+	if (WARN_ON(e->event->length > 128)) {
+		/* Increase buffer if this happens */
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	memcpy(event, e->event, e->event->length);
+	kfree(e);
+
+	return event;
+
+err_free:
+	kfree(event);
+
+	return ret ? ERR_PTR(ret) : NULL;
+}
+EXPORT_SYMBOL(drm_client_read_event);
+
+static void drm_client_connector_free(struct drm_client_connector *connector)
+{
+	if (!connector)
+		return;
+	kfree(connector->modes);
+	kfree(connector);
+}
+
+static struct drm_client_connector *
+drm_client_get_connector(struct drm_client_dev *client, unsigned int id)
+{
+	struct drm_mode_get_connector req = {
+		.connector_id = id,
+	};
+	struct drm_client_connector *connector;
+	struct drm_mode_modeinfo *modes = NULL;
+	struct drm_device *dev = client->dev;
+	struct drm_connector *conn;
+	bool non_desktop;
+	int ret;
+
+	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
+	if (!connector)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_mode_getconnector(dev, &req, client->file, false);
+	if (ret)
+		goto err_free;
+
+	connector->conn_id = id;
+	connector->status = req.connection;
+
+	conn = drm_connector_lookup(dev, client->file, id);
+	if (!conn) {
+		ret = -ENOENT;
+		goto err_free;
+	}
+
+	non_desktop = conn->display_info.non_desktop;
+
+	connector->has_tile = conn->has_tile;
+	connector->tile_h_loc = conn->tile_h_loc;
+	connector->tile_v_loc = conn->tile_v_loc;
+	if (conn->tile_group)
+		connector->tile_group = conn->tile_group->id;
+
+	drm_connector_put(conn);
+
+	if (non_desktop) {
+		kfree(connector);
+		return NULL;
+	}
+
+	if (!req.count_modes)
+		return connector;
+
+	modes = kcalloc(req.count_modes, sizeof(*modes), GFP_KERNEL);
+	if (!modes) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	connector->modes = modes;
+	connector->num_modes = req.count_modes;
+
+	req.count_props = 0;
+	req.count_encoders = 0;
+	req.modes_ptr = (unsigned long)modes;
+
+	ret = drm_mode_getconnector(dev, &req, client->file, false);
+	if (ret)
+		goto err_free;
+
+	return connector;
+
+err_free:
+	kfree(modes);
+	kfree(connector);
+
+	return ERR_PTR(ret);
+}
+
+static int drm_client_get_connectors(struct drm_client_dev *client,
+				     struct drm_client_connector ***connectors)
+{
+	struct drm_mode_card_res card_res = {};
+	struct drm_device *dev = client->dev;
+	int ret, num_connectors;
+	u32 *connector_ids;
+	unsigned int i;
+
+	ret = drm_mode_getresources(dev, &card_res, client->file, false);
+	if (ret)
+		return ret;
+	if (!card_res.count_connectors)
+		return 0;
+
+	num_connectors = card_res.count_connectors;
+	connector_ids = kcalloc(num_connectors,
+				sizeof(*connector_ids), GFP_KERNEL);
+	if (!connector_ids)
+		return -ENOMEM;
+
+	card_res.count_fbs = 0;
+	card_res.count_crtcs = 0;
+	card_res.count_encoders = 0;
+	card_res.connector_id_ptr = (unsigned long)connector_ids;
+
+	ret = drm_mode_getresources(dev, &card_res, client->file, false);
+	if (ret)
+		goto err_free;
+
+	*connectors = kcalloc(num_connectors, sizeof(**connectors), GFP_KERNEL);
+	if (!(*connectors)) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	for (i = 0; i < num_connectors; i++) {
+		struct drm_client_connector *connector;
+
+		connector = drm_client_get_connector(client, connector_ids[i]);
+		if (IS_ERR(connector)) {
+			ret = PTR_ERR(connector);
+			goto err_free;
+		}
+		if (connector)
+			(*connectors)[i] = connector;
+		else
+			num_connectors--;
+	}
+
+	if (!num_connectors) {
+		ret = 0;
+		goto err_free;
+	}
+
+	return num_connectors;
+
+err_free:
+	if (connectors)
+		for (i = 0; i < num_connectors; i++)
+			drm_client_connector_free((*connectors)[i]);
+
+	kfree(connectors);
+	kfree(connector_ids);
+
+	return ret;
+}
+
+static bool
+drm_client_connector_is_enabled(struct drm_client_connector *connector,
+				bool strict)
+{
+	if (strict)
+		return connector->status == connector_status_connected;
+	else
+		return connector->status != connector_status_disconnected;
+}
+
+struct drm_mode_modeinfo *
+drm_client_display_first_mode(struct drm_client_display *display)
+{
+	if (!display->num_modes)
+		return NULL;
+	return display->modes;
+}
+EXPORT_SYMBOL(drm_client_display_first_mode);
+
+struct drm_mode_modeinfo *
+drm_client_display_next_mode(struct drm_client_display *display,
+			     struct drm_mode_modeinfo *mode)
+{
+	struct drm_mode_modeinfo *modes = display->modes;
+
+	if (++mode < &modes[display->num_modes])
+		return mode;
+
+	return NULL;
+}
+EXPORT_SYMBOL(drm_client_display_next_mode);
+
+static void
+drm_client_display_fill_tile_modes(struct drm_client_display *display,
+				   struct drm_mode_modeinfo *tile_modes)
+{
+	unsigned int i, j, num_modes = display->connectors[0]->num_modes;
+	struct drm_mode_modeinfo *tile_mode, *conn_mode;
+
+	if (!num_modes) {
+		kfree(tile_modes);
+		kfree(display->modes);
+		display->modes = NULL;
+		display->num_modes = 0;
+		return;
+	}
+
+	for (i = 0; i < num_modes; i++) {
+		tile_mode = &tile_modes[i];
+
+		conn_mode = &display->connectors[0]->modes[i];
+		tile_mode->clock = conn_mode->clock;
+		tile_mode->vscan = conn_mode->vscan;
+		tile_mode->vrefresh = conn_mode->vrefresh;
+		tile_mode->flags = conn_mode->flags;
+		tile_mode->type = conn_mode->type;
+
+		for (j = 0; j < display->num_connectors; j++) {
+			conn_mode = &display->connectors[j]->modes[i];
+
+			if (!display->connectors[j]->tile_h_loc) {
+				tile_mode->hdisplay += conn_mode->hdisplay;
+				tile_mode->hsync_start += conn_mode->hsync_start;
+				tile_mode->hsync_end += conn_mode->hsync_end;
+				tile_mode->htotal += conn_mode->htotal;
+			}
+
+			if (!display->connectors[j]->tile_v_loc) {
+				tile_mode->vdisplay += conn_mode->vdisplay;
+				tile_mode->vsync_start += conn_mode->vsync_start;
+				tile_mode->vsync_end += conn_mode->vsync_end;
+				tile_mode->vtotal += conn_mode->vtotal;
+			}
+		}
+	}
+
+	kfree(display->modes);
+	display->modes = tile_modes;
+	display->num_modes = num_modes;
+}
+
+/**
+ * drm_client_display_update_modes - Fetch display modes
+ * @display: Client display
+ * @mode_changed: Optional pointer to boolen which return whether the modes
+ *                have changed or not.
+ *
+ * This function can be used in the client hotplug callback to check if the
+ * video modes have changed and get them up-to-date.
+ *
+ * Returns:
+ * Number of modes on success, negative error code on failure.
+ */
+int drm_client_display_update_modes(struct drm_client_display *display,
+				    bool *mode_changed)
+{
+	unsigned int num_connectors = display->num_connectors;
+	struct drm_client_dev *client = display->client;
+	struct drm_mode_modeinfo *display_tile_modes;
+	struct drm_client_connector **connectors;
+	unsigned int i, num_modes = 0;
+	bool dummy_changed = false;
+	int ret;
+
+	if (mode_changed)
+		*mode_changed = false;
+	else
+		mode_changed = &dummy_changed;
+
+	if (display->cloned)
+		return 2;
+
+	ret = drm_client_get_file(client);
+	if (ret)
+		return ret;
+
+	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
+	if (!connectors) {
+		ret = -ENOMEM;
+		goto out_put_file;
+	}
+
+	/* Get a new set for comparison */
+	for (i = 0; i < num_connectors; i++) {
+		connectors[i] = drm_client_get_connector(client, display->connectors[i]->conn_id);
+		if (IS_ERR_OR_NULL(connectors[i])) {
+			ret = PTR_ERR_OR_ZERO(connectors[i]);
+			if (!ret)
+				ret = -ENOENT;
+			goto out_cleanup;
+		}
+	}
+
+	/* All connectors should have the same number of modes */
+	num_modes = connectors[0]->num_modes;
+	for (i = 0; i < num_connectors; i++) {
+		if (num_modes != connectors[i]->num_modes) {
+			ret = -EINVAL;
+			goto out_cleanup;
+		}
+	}
+
+	if (num_connectors > 1) {
+		display_tile_modes = kcalloc(num_modes, sizeof(*display_tile_modes), GFP_KERNEL);
+		if (!display_tile_modes) {
+			ret = -ENOMEM;
+			goto out_cleanup;
+		}
+	}
+
+	mutex_lock(&display->modes_lock);
+
+	for (i = 0; i < num_connectors; i++) {
+		display->connectors[i]->status = connectors[i]->status;
+		if (display->connectors[i]->num_modes != connectors[i]->num_modes) {
+			display->connectors[i]->num_modes = connectors[i]->num_modes;
+			kfree(display->connectors[i]->modes);
+			display->connectors[i]->modes = connectors[i]->modes;
+			connectors[i]->modes = NULL;
+			*mode_changed = true;
+		}
+	}
+
+	if (num_connectors > 1)
+		drm_client_display_fill_tile_modes(display, display_tile_modes);
+	else
+		display->modes = display->connectors[0]->modes;
+
+	mutex_unlock(&display->modes_lock);
+
+out_cleanup:
+	for (i = 0; i < num_connectors; i++)
+		drm_client_connector_free(connectors[i]);
+	kfree(connectors);
+out_put_file:
+	drm_client_put_file(client);
+
+	return ret ? ret : num_modes;
+}
+EXPORT_SYMBOL(drm_client_display_update_modes);
+
+void drm_client_display_free(struct drm_client_display *display)
+{
+	unsigned int i;
+
+	if (!display)
+		return;
+
+	/* tile modes? */
+	if (display->modes != display->connectors[0]->modes)
+		kfree(display->modes);
+
+	for (i = 0; i < display->num_connectors; i++)
+		drm_client_connector_free(display->connectors[i]);
+
+	kfree(display->connectors);
+	mutex_destroy(&display->modes_lock);
+	kfree(display);
+}
+EXPORT_SYMBOL(drm_client_display_free);
+
+static struct drm_client_display *
+drm_client_display_alloc(struct drm_client_dev *client,
+			 unsigned int num_connectors)
+{
+	struct drm_client_display *display;
+	struct drm_client_connector **connectors;
+
+	display = kzalloc(sizeof(*display), GFP_KERNEL);
+	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
+	if (!display || !connectors) {
+		kfree(display);
+		kfree(connectors);
+		return NULL;
+	}
+
+	mutex_init(&display->modes_lock);
+	display->client = client;
+	display->connectors = connectors;
+	display->num_connectors = num_connectors;
+
+	return display;
+}
+
+/* Logic is from drm_fb_helper */
+static struct drm_client_display *
+drm_client_connector_pick_cloned(struct drm_client_dev *client,
+				 struct drm_client_connector **connectors,
+				 unsigned int num_connectors)
+{
+	struct drm_mode_modeinfo modes[2], udmt_mode, *mode, *tmp;
+	struct drm_display_mode *dmt_display_mode = NULL;
+	unsigned int i, j, conns[2], num_conns = 0;
+	struct drm_client_connector *connector;
+	struct drm_device *dev = client->dev;
+	struct drm_client_display *display;
+
+	/* only contemplate cloning in the single crtc case */
+	if (dev->mode_config.num_crtc > 1)
+		return NULL;
+retry:
+	for (i = 0; i < num_connectors; i++) {
+		connector = connectors[i];
+		if (!connector || connector->has_tile || !connector->num_modes)
+			continue;
+
+		for (j = 0; j < connector->num_modes; j++) {
+			mode = &connector->modes[j];
+			if (dmt_display_mode) {
+				if (drm_umode_equal(&udmt_mode, mode)) {
+					conns[num_conns] = i;
+					modes[num_conns++] = *mode;
+					break;
+				}
+			} else {
+				if (mode->type & DRM_MODE_TYPE_USERDEF) {
+					conns[num_conns] = i;
+					modes[num_conns++] = *mode;
+					break;
+				}
+			}
+		}
+		if (num_conns == 2)
+			break;
+	}
+
+	if (num_conns == 2)
+		goto found;
+
+	if (dmt_display_mode)
+		return NULL;
+
+	dmt_display_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
+	drm_mode_convert_to_umode(&udmt_mode, dmt_display_mode);
+	drm_mode_destroy(dev, dmt_display_mode);
+
+	goto retry;
+
+found:
+	tmp = kcalloc(2, sizeof(*tmp), GFP_KERNEL);
+	if (!tmp)
+		return ERR_PTR(-ENOMEM);
+
+	display = drm_client_display_alloc(client, 2);
+	if (!display) {
+		kfree(tmp);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	for (i = 0; i < 2; i++) {
+		connector = connectors[conns[i]];
+		display->connectors[i] = connector;
+		connectors[conns[i]] = NULL;
+		kfree(connector->modes);
+		tmp[i] = modes[i];
+		connector->modes = &tmp[i];
+		connector->num_modes = 1;
+	}
+
+	display->cloned = true;
+	display->modes = &tmp[0];
+	display->num_modes = 1;
+
+	return display;
+}
+
+static struct drm_client_display *
+drm_client_connector_pick_tile(struct drm_client_dev *client,
+			       struct drm_client_connector **connectors,
+			       unsigned int num_connectors)
+{
+	unsigned int i, num_conns, num_modes, tile_group = 0;
+	struct drm_mode_modeinfo *tile_modes = NULL;
+	struct drm_client_connector *connector;
+	struct drm_client_display *display;
+	u16 conns[32];
+
+	for (i = 0; i < num_connectors; i++) {
+		connector = connectors[i];
+		if (!connector || !connector->tile_group)
+			continue;
+
+		if (!tile_group) {
+			tile_group = connector->tile_group;
+			num_modes = connector->num_modes;
+		}
+
+		if (connector->tile_group != tile_group)
+			continue;
+
+		if (num_modes != connector->num_modes) {
+			DRM_ERROR("Tile connectors must have the same number of modes\n");
+			return ERR_PTR(-EINVAL);
+		}
+
+		conns[num_conns++] = i;
+		if (WARN_ON(num_conns == 33))
+			return ERR_PTR(-EINVAL);
+	}
+
+	if (!num_conns)
+		return NULL;
+
+	if (num_modes) {
+		tile_modes = kcalloc(num_modes, sizeof(*tile_modes), GFP_KERNEL);
+		if (!tile_modes)
+			return ERR_PTR(-ENOMEM);
+	}
+
+	display = drm_client_display_alloc(client, num_conns);
+	if (!display) {
+		kfree(tile_modes);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	if (num_modes)
+		drm_client_display_fill_tile_modes(display, tile_modes);
+
+	return display;
+}
+
+static struct drm_client_display *
+drm_client_connector_pick_not_tile(struct drm_client_dev *client,
+				   struct drm_client_connector **connectors,
+				   unsigned int num_connectors)
+{
+	struct drm_client_display *display;
+	unsigned int i;
+
+	for (i = 0; i < num_connectors; i++) {
+		if (!connectors[i] || connectors[i]->has_tile)
+			continue;
+		break;
+	}
+
+	if (i == num_connectors)
+		return NULL;
+
+	display = drm_client_display_alloc(client, 1);
+	if (!display)
+		return ERR_PTR(-ENOMEM);
+
+	display->connectors[0] = connectors[i];
+	connectors[i] = NULL;
+	display->modes = display->connectors[0]->modes;
+	display->num_modes = display->connectors[0]->num_modes;
+
+	return display;
+}
+
+/* Get connectors and bundle them up into displays */
+static int drm_client_get_displays(struct drm_client_dev *client,
+				   struct drm_client_display ***displays)
+{
+	int ret, num_connectors, num_displays = 0;
+	struct drm_client_connector **connectors;
+	struct drm_client_display *display;
+	unsigned int i;
+
+	ret = drm_client_get_file(client);
+	if (ret)
+		return ret;
+
+	num_connectors = drm_client_get_connectors(client, &connectors);
+	if (num_connectors <= 0) {
+		ret = num_connectors;
+		goto err_put_file;
+	}
+
+	*displays = kcalloc(num_connectors, sizeof(*displays), GFP_KERNEL);
+	if (!(*displays)) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	display = drm_client_connector_pick_cloned(client, connectors,
+						   num_connectors);
+	if (IS_ERR(display)) {
+		ret = PTR_ERR(display);
+		goto err_free;
+	}
+	if (display)
+		(*displays)[num_displays++] = display;
+
+	for (i = 0; i < num_connectors; i++) {
+		display = drm_client_connector_pick_tile(client, connectors,
+							 num_connectors);
+		if (IS_ERR(display)) {
+			ret = PTR_ERR(display);
+			goto err_free;
+		}
+		if (!display)
+			break;
+		(*displays)[num_displays++] = display;
+	}
+
+	for (i = 0; i < num_connectors; i++) {
+		display = drm_client_connector_pick_not_tile(client, connectors,
+							     num_connectors);
+		if (IS_ERR(display)) {
+			ret = PTR_ERR(display);
+			goto err_free;
+		}
+		if (!display)
+			break;
+		(*displays)[num_displays++] = display;
+	}
+
+	for (i = 0; i < num_connectors; i++) {
+		if (connectors[i]) {
+			DRM_INFO("Connector %u fell through the cracks.\n",
+				 connectors[i]->conn_id);
+			drm_client_connector_free(connectors[i]);
+		}
+	}
+
+	drm_client_put_file(client);
+	kfree(connectors);
+
+	return num_displays;
+
+err_free:
+	for (i = 0; i < num_displays; i++)
+		drm_client_display_free((*displays)[i]);
+	kfree(*displays);
+	*displays = NULL;
+	for (i = 0; i < num_connectors; i++)
+		drm_client_connector_free(connectors[i]);
+	kfree(connectors);
+err_put_file:
+	drm_client_put_file(client);
+
+	return ret;
+}
+
+static bool
+drm_client_display_is_enabled(struct drm_client_display *display, bool strict)
+{
+	unsigned int i;
+
+	if (!display->num_modes)
+		return false;
+
+	for (i = 0; i < display->num_connectors; i++)
+		if (!drm_client_connector_is_enabled(display->connectors[i], strict))
+			return false;
+
+	return true;
+}
+
+/**
+ * drm_client_display_get_first_enabled - Get first enabled display
+ * @client: DRM client
+ * @strict: If true the connector(s) have to be connected, if false they can
+ *          also have unknown status.
+ *
+ * This function gets all connectors and bundles them into displays
+ * (tiled/cloned). It then picks the first one with connectors that is enabled
+ * according to @strict.
+ *
+ * Returns:
+ * Pointer to a client display if such a display was found, NULL if not found
+ * or an error pointer on failure.
+ */
+struct drm_client_display *
+drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict)
+{
+	struct drm_client_display **displays, *display = NULL;
+	int num_displays;
+	unsigned int i;
+
+	num_displays = drm_client_get_displays(client, &displays);
+	if (num_displays < 0)
+		return ERR_PTR(num_displays);
+	if (!num_displays)
+		return NULL;
+
+	for (i = 0; i < num_displays; i++) {
+		if (!display &&
+		    drm_client_display_is_enabled(displays[i], strict)) {
+			display = displays[i];
+			continue;
+		}
+		drm_client_display_free(displays[i]);
+	}
+
+	kfree(displays);
+
+	return display;
+}
+EXPORT_SYMBOL(drm_client_display_get_first_enabled);
+
+unsigned int
+drm_client_display_preferred_depth(struct drm_client_display *display)
+{
+	struct drm_connector *conn;
+	unsigned int ret;
+
+	conn = drm_connector_lookup(display->client->dev, NULL,
+				    display->connectors[0]->conn_id);
+	if (!conn)
+		return 0;
+
+	if (conn->cmdline_mode.bpp_specified)
+		ret = conn->cmdline_mode.bpp;
+	else
+		ret = display->client->dev->mode_config.preferred_depth;
+
+	drm_connector_put(conn);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_display_preferred_depth);
+
+int drm_client_display_dpms(struct drm_client_display *display, int mode)
+{
+	struct drm_mode_obj_set_property prop;
+
+	prop.value = mode;
+	prop.prop_id = display->client->dev->mode_config.dpms_property->base.id;
+	prop.obj_id = display->connectors[0]->conn_id;
+	prop.obj_type = DRM_MODE_OBJECT_CONNECTOR;
+
+	return drm_mode_obj_set_property(display->client->dev, &prop,
+					 display->client->file);
+}
+EXPORT_SYMBOL(drm_client_display_dpms);
+
+int drm_client_display_wait_vblank(struct drm_client_display *display)
+{
+	struct drm_crtc *crtc;
+	union drm_wait_vblank vblank_req = {
+		.request = {
+			.type = _DRM_VBLANK_RELATIVE,
+			.sequence = 1,
+		},
+	};
+
+	crtc = drm_crtc_find(display->client->dev, display->client->file,
+			     display->connectors[0]->crtc_id);
+	if (!crtc)
+		return -ENOENT;
+
+	vblank_req.request.type |= drm_crtc_index(crtc) << _DRM_VBLANK_HIGH_CRTC_SHIFT;
+
+	return drm_wait_vblank(display->client->dev, &vblank_req,
+			       display->client->file);
+}
+EXPORT_SYMBOL(drm_client_display_wait_vblank);
+
+static int drm_client_get_crtc_index(struct drm_client_dev *client, u32 id)
+{
+	int i;
+
+	for (i = 0; i < client->num_crtcs; i++)
+		if (client->crtcs[i] == id)
+			return i;
+
+	return -ENOENT;
+}
+
+static int drm_client_display_find_crtcs(struct drm_client_display *display)
+{
+	struct drm_client_dev *client = display->client;
+	struct drm_device *dev = client->dev;
+	struct drm_file *file = client->file;
+	u32 encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
+	unsigned int i, j, available_crtcs = ~0;
+	struct drm_mode_get_connector conn_req;
+	struct drm_mode_get_encoder enc_req;
+	int ret;
+
+	/* Already done? */
+	if (display->connectors[0]->crtc_id)
+		return 0;
+
+	for (i = 0; i < display->num_connectors; i++) {
+		u32 active_crtcs = 0, crtcs_for_connector = 0;
+		int crtc_idx;
+
+		memset(&conn_req, 0, sizeof(conn_req));
+		conn_req.connector_id = display->connectors[i]->conn_id;
+		conn_req.encoders_ptr = (unsigned long)(encoder_ids);
+		conn_req.count_encoders = DRM_CONNECTOR_MAX_ENCODER;
+		ret = drm_mode_getconnector(dev, &conn_req, file, false);
+		if (ret)
+			return ret;
+
+		if (conn_req.encoder_id) {
+			memset(&enc_req, 0, sizeof(enc_req));
+			enc_req.encoder_id = conn_req.encoder_id;
+			ret = drm_mode_getencoder(dev, &enc_req, file);
+			if (ret)
+				return ret;
+			crtcs_for_connector |= enc_req.possible_crtcs;
+			if (crtcs_for_connector & available_crtcs)
+				goto found;
+		}
+
+		for (j = 0; j < conn_req.count_encoders; j++) {
+			memset(&enc_req, 0, sizeof(enc_req));
+			enc_req.encoder_id = encoder_ids[j];
+			ret = drm_mode_getencoder(dev, &enc_req, file);
+			if (ret)
+				return ret;
+
+			crtcs_for_connector |= enc_req.possible_crtcs;
+
+			if (enc_req.crtc_id) {
+				crtc_idx = drm_client_get_crtc_index(client, enc_req.crtc_id);
+				if (crtc_idx >= 0)
+					active_crtcs |= 1 << crtc_idx;
+			}
+		}
+
+found:
+		crtcs_for_connector &= available_crtcs;
+		active_crtcs &= available_crtcs;
+
+		if (!crtcs_for_connector)
+			return -ENOENT;
+
+		if (active_crtcs)
+			crtc_idx = ffs(active_crtcs) - 1;
+		else
+			crtc_idx = ffs(crtcs_for_connector) - 1;
+
+		if (crtc_idx >= client->num_crtcs)
+			return -EINVAL;
+
+		display->connectors[i]->crtc_id = client->crtcs[crtc_idx];
+		available_crtcs &= ~BIT(crtc_idx);
+	}
+
+	return 0;
+}
+
+/**
+ * drm_client_display_commit_mode - Commit a mode to the crtc(s)
+ * @display: Client display
+ * @fb_id: Framebuffer id
+ * @mode: Video mode
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int drm_client_display_commit_mode(struct drm_client_display *display,
+				   u32 fb_id, struct drm_mode_modeinfo *mode)
+{
+	struct drm_client_dev *client = display->client;
+	struct drm_device *dev = client->dev;
+	unsigned int num_crtcs = client->num_crtcs;
+	struct drm_file *file = client->file;
+	unsigned int *xoffsets = NULL, *yoffsets = NULL;
+	struct drm_mode_crtc *crtc_reqs, *req;
+	u32 cloned_conn_ids[2];
+	unsigned int i;
+	int idx, ret;
+
+	ret = drm_client_display_find_crtcs(display);
+	if (ret)
+		return ret;
+
+	crtc_reqs = kcalloc(num_crtcs, sizeof(*crtc_reqs), GFP_KERNEL);
+	if (!crtc_reqs)
+		return -ENOMEM;
+
+	for (i = 0; i < num_crtcs; i++)
+		crtc_reqs[i].crtc_id = client->crtcs[i];
+
+	if (drm_client_display_is_tiled(display)) {
+		/* TODO calculate tile crtc offsets */
+	}
+
+	for (i = 0; i < display->num_connectors; i++) {
+		idx = drm_client_get_crtc_index(client, display->connectors[i]->crtc_id);
+		if (idx < 0)
+			return -ENOENT;
+
+		req = &crtc_reqs[idx];
+
+		req->fb_id = fb_id;
+		if (xoffsets) {
+			req->x = xoffsets[i];
+			req->y = yoffsets[i];
+		}
+		req->mode_valid = 1;
+		req->mode = *mode;
+
+		if (display->cloned) {
+			cloned_conn_ids[0] = display->connectors[0]->conn_id;
+			cloned_conn_ids[1] = display->connectors[1]->conn_id;
+			req->set_connectors_ptr = (unsigned long)(cloned_conn_ids);
+			req->count_connectors = 2;
+			break;
+		}
+
+		req->set_connectors_ptr = (unsigned long)(&display->connectors[i]->conn_id);
+		req->count_connectors = 1;
+	}
+
+	for (i = 0; i < num_crtcs; i++) {
+		ret = drm_mode_setcrtc(dev, &crtc_reqs[i], file, false);
+		if (ret)
+			break;
+	}
+
+	kfree(xoffsets);
+	kfree(yoffsets);
+	kfree(crtc_reqs);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_display_commit_mode);
+
+unsigned int drm_client_display_current_fb(struct drm_client_display *display)
+{
+	struct drm_client_dev *client = display->client;
+	int ret;
+	struct drm_mode_crtc crtc_req = {
+		.crtc_id = display->connectors[0]->crtc_id,
+	};
+
+	ret = drm_mode_getcrtc(client->dev, &crtc_req, client->file);
+	if (ret)
+		return 0;
+
+	return crtc_req.fb_id;
+}
+EXPORT_SYMBOL(drm_client_display_current_fb);
+
+int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
+			     struct drm_clip_rect *clips, unsigned int num_clips)
+{
+	struct drm_client_dev *client = display->client;
+	struct drm_mode_fb_dirty_cmd dirty_req = {
+		.fb_id = fb_id,
+		.clips_ptr = (unsigned long)clips,
+		.num_clips = num_clips,
+	};
+	int ret;
+
+	if (display->no_flushing)
+		return 0;
+
+	ret = drm_mode_dirtyfb(client->dev, &dirty_req, client->file, false);
+	if (ret == -ENOSYS) {
+		ret = 0;
+		display->no_flushing = true;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_display_flush);
+
+int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
+				 bool event)
+{
+	struct drm_client_dev *client = display->client;
+	struct drm_mode_crtc_page_flip_target page_flip_req = {
+		.crtc_id = display->connectors[0]->crtc_id,
+		.fb_id = fb_id,
+	};
+
+	if (event)
+		page_flip_req.flags = DRM_MODE_PAGE_FLIP_EVENT;
+
+	return drm_mode_page_flip(client->dev, &page_flip_req, client->file);
+	/*
+	 * TODO:
+	 * Where do we flush on page flip? Should the driver handle that?
+	 */
+}
+EXPORT_SYMBOL(drm_client_display_page_flip);
+
+/**
+ * drm_client_framebuffer_create - Create a client framebuffer
+ * @client: DRM client
+ * @mode: Display mode to create a buffer for
+ * @format: Buffer format
+ *
+ * This function creates a &drm_client_buffer which consists of a
+ * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf
+ * exported to aquire a virtual address which is stored in
+ * &drm_client_buffer->vaddr.
+ * Call drm_client_framebuffer_delete() to free the buffer.
+ *
+ * Returns:
+ * Pointer to a client buffer or an error pointer on failure.
+ */
+struct drm_client_buffer *
+drm_client_framebuffer_create(struct drm_client_dev *client,
+			      struct drm_mode_modeinfo *mode, u32 format)
+{
+	struct drm_client_buffer *buffer;
+	int ret;
+
+	buffer = drm_client_buffer_create(client, mode->hdisplay,
+					  mode->vdisplay, format);
+	if (IS_ERR(buffer))
+		return buffer;
+
+	ret = drm_client_buffer_addfb(buffer, mode);
+	if (ret) {
+		drm_client_buffer_delete(buffer);
+		return ERR_PTR(ret);
+	}
+
+	return buffer;
+}
+EXPORT_SYMBOL(drm_client_framebuffer_create);
+
+void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
+{
+	drm_client_buffer_rmfb(buffer);
+	drm_client_buffer_delete(buffer);
+}
+EXPORT_SYMBOL(drm_client_framebuffer_delete);
+
+struct drm_client_buffer *
+drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
+			 u32 format)
+{
+	struct drm_mode_create_dumb dumb_args = { 0 };
+	struct drm_prime_handle prime_args = { 0 };
+	struct drm_client_buffer *buffer;
+	struct dma_buf *dma_buf;
+	void *vaddr;
+	int ret;
+
+	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
+	if (!buffer)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_client_get_file(client);
+	if (ret)
+		goto err_free;
+
+	buffer->client = client;
+	buffer->width = width;
+	buffer->height = height;
+	buffer->format = format;
+
+	dumb_args.width = buffer->width;
+	dumb_args.height = buffer->height;
+	dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
+	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
+	if (ret)
+		goto err_put_file;
+
+	buffer->handle = dumb_args.handle;
+	buffer->pitch = dumb_args.pitch;
+	buffer->size = dumb_args.size;
+
+	prime_args.handle = dumb_args.handle;
+	ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file);
+	if (ret)
+		goto err_delete;
+
+	dma_buf = dma_buf_get(prime_args.fd);
+	if (IS_ERR(dma_buf)) {
+		ret = PTR_ERR(dma_buf);
+		goto err_delete;
+	}
+
+	buffer->dma_buf = dma_buf;
+
+	vaddr = dma_buf_vmap(dma_buf);
+	if (!vaddr) {
+		ret = -ENOMEM;
+		goto err_delete;
+	}
+
+	buffer->vaddr = vaddr;
+
+	return buffer;
+
+err_delete:
+	drm_client_buffer_delete(buffer);
+err_put_file:
+	drm_client_put_file(client);
+err_free:
+	kfree(buffer);
+
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(drm_client_buffer_create);
+
+void drm_client_buffer_delete(struct drm_client_buffer *buffer)
+{
+	if (!buffer)
+		return;
+
+	if (buffer->vaddr)
+		dma_buf_vunmap(buffer->dma_buf, buffer->vaddr);
+
+	if (buffer->dma_buf)
+		dma_buf_put(buffer->dma_buf);
+
+	drm_mode_destroy_dumb(buffer->client->dev, buffer->handle,
+			      buffer->client->file);
+	drm_client_put_file(buffer->client);
+	kfree(buffer);
+}
+EXPORT_SYMBOL(drm_client_buffer_delete);
+
+int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
+			    struct drm_mode_modeinfo *mode)
+{
+	struct drm_client_dev *client = buffer->client;
+	struct drm_mode_fb_cmd2 fb_req = { };
+	unsigned int num_fbs, *fb_ids;
+	int i, ret;
+
+	if (buffer->num_fbs)
+		return -EINVAL;
+
+	if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
+		return -EINVAL;
+
+	num_fbs = buffer->height / mode->vdisplay;
+	fb_ids = kcalloc(num_fbs, sizeof(*fb_ids), GFP_KERNEL);
+	if (!fb_ids)
+		return -ENOMEM;
+
+	fb_req.width = mode->hdisplay;
+	fb_req.height = mode->vdisplay;
+	fb_req.pixel_format = buffer->format;
+	fb_req.handles[0] = buffer->handle;
+	fb_req.pitches[0] = buffer->pitch;
+
+	for (i = 0; i < num_fbs; i++) {
+		fb_req.offsets[0] = i * mode->vdisplay * buffer->pitch;
+		ret = drm_mode_addfb2(client->dev, &fb_req, client->file,
+				      client->funcs->name);
+		if (ret)
+			goto err_remove;
+		fb_ids[i] = fb_req.fb_id;
+	}
+
+	buffer->fb_ids = fb_ids;
+	buffer->num_fbs = num_fbs;
+
+	return 0;
+
+err_remove:
+	for (i--; i >= 0; i--)
+		drm_mode_rmfb(client->dev, fb_ids[i], client->file);
+	kfree(fb_ids);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_buffer_addfb);
+
+int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
+{
+	unsigned int i;
+	int ret;
+
+	if (!buffer || !buffer->num_fbs)
+		return 0;
+
+	for (i = 0; i < buffer->num_fbs; i++) {
+		ret = drm_mode_rmfb(buffer->client->dev, buffer->fb_ids[i],
+				    buffer->client->file);
+		if (ret)
+			DRM_DEV_ERROR(buffer->client->dev->dev,
+				      "Error removing FB:%u (%d)\n",
+				      buffer->fb_ids[i], ret);
+	}
+
+	kfree(buffer->fb_ids);
+	buffer->fb_ids = NULL;
+	buffer->num_fbs = 0;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_client_buffer_rmfb);
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index f869de185986..db161337d87c 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -33,6 +33,7 @@
 #include <linux/mount.h>
 #include <linux/slab.h>
 
+#include <drm/drm_client.h>
 #include <drm/drm_drv.h>
 #include <drm/drmP.h>
 
@@ -463,6 +464,7 @@ int drm_dev_init(struct drm_device *dev,
 	dev->driver = driver;
 
 	INIT_LIST_HEAD(&dev->filelist);
+	INIT_LIST_HEAD(&dev->filelist_internal);
 	INIT_LIST_HEAD(&dev->ctxlist);
 	INIT_LIST_HEAD(&dev->vmalist);
 	INIT_LIST_HEAD(&dev->maplist);
@@ -787,6 +789,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
 		 dev->dev ? dev_name(dev->dev) : "virtual device",
 		 dev->primary->index);
 
+	drm_client_dev_register(dev);
+
 	goto out_unlock;
 
 err_minors:
@@ -839,6 +843,8 @@ void drm_dev_unregister(struct drm_device *dev)
 	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
 	drm_minor_unregister(dev, DRM_MINOR_RENDER);
 	drm_minor_unregister(dev, DRM_MINOR_CONTROL);
+
+	drm_client_dev_unregister(dev);
 }
 EXPORT_SYMBOL(drm_dev_unregister);
 
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index 55505378df47..bcc688e58776 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -35,6 +35,7 @@
 #include <linux/slab.h>
 #include <linux/module.h>
 
+#include <drm/drm_client.h>
 #include <drm/drm_file.h>
 #include <drm/drmP.h>
 
@@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
 
 	if (drm_core_check_feature(dev, DRIVER_LEGACY))
 		drm_legacy_dev_reinit(dev);
+
+	drm_client_dev_lastclose(dev);
 }
 
 /**
diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c
index 2d1643bdae78..5d2a6c6717f5 100644
--- a/drivers/gpu/drm/drm_probe_helper.c
+++ b/drivers/gpu/drm/drm_probe_helper.c
@@ -33,6 +33,7 @@
 #include <linux/moduleparam.h>
 
 #include <drm/drmP.h>
+#include <drm/drm_client.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_crtc_helper.h>
@@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
 	drm_sysfs_hotplug_event(dev);
 	if (dev->mode_config.funcs->output_poll_changed)
 		dev->mode_config.funcs->output_poll_changed(dev);
+
+	drm_client_dev_hotplug(dev);
 }
 EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
 
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
new file mode 100644
index 000000000000..88f6f87919c5
--- /dev/null
+++ b/include/drm/drm_client.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/mutex.h>
+
+struct dma_buf;
+struct drm_clip_rect;
+struct drm_device;
+struct drm_file;
+struct drm_mode_modeinfo;
+
+struct drm_client_dev;
+
+/**
+ * struct drm_client_funcs - DRM client  callbacks
+ */
+struct drm_client_funcs {
+	/**
+	 * @name:
+	 *
+	 * Name of the client.
+	 */
+	const char *name;
+
+	/**
+	 * @new:
+	 *
+	 * Called when a client or a &drm_device is registered.
+	 * If the callback returns anything but zero, then this client instance
+	 * is dropped.
+	 *
+	 * This callback is mandatory.
+	 */
+	int (*new)(struct drm_client_dev *client);
+
+	/**
+	 * @remove:
+	 *
+	 * Called when a &drm_device is unregistered or the client is
+	 * unregistered. If zero is returned drm_client_free() is called
+	 * automatically. If the client can't drop it's resources it should
+	 * return non-zero and call drm_client_free() later.
+	 *
+	 * This callback is optional.
+	 */
+	int (*remove)(struct drm_client_dev *client);
+
+	/**
+	 * @lastclose:
+	 *
+	 * Called on drm_lastclose(). The first client instance in the list
+	 * that returns zero gets the privilege to restore and no more clients
+	 * are called.
+	 *
+	 * This callback is optional.
+	 */
+	int (*lastclose)(struct drm_client_dev *client);
+
+	/**
+	 * @hotplug:
+	 *
+	 * Called on drm_kms_helper_hotplug_event().
+	 *
+	 * This callback is optional.
+	 */
+	int (*hotplug)(struct drm_client_dev *client);
+
+// TODO
+//	void (*suspend)(struct drm_client_dev *client);
+//	void (*resume)(struct drm_client_dev *client);
+};
+
+/**
+ * struct drm_client_dev - DRM client instance
+ */
+struct drm_client_dev {
+	struct list_head list;
+	struct drm_device *dev;
+	const struct drm_client_funcs *funcs;
+	struct mutex lock;
+	struct drm_file *file;
+	unsigned int file_ref_count;
+	u32 *crtcs;
+	unsigned int num_crtcs;
+	u32 min_width;
+	u32 max_width;
+	u32 min_height;
+	u32 max_height;
+	void *private;
+};
+
+void drm_client_free(struct drm_client_dev *client);
+int drm_client_register(const struct drm_client_funcs *funcs);
+void drm_client_unregister(const struct drm_client_funcs *funcs);
+
+void drm_client_dev_register(struct drm_device *dev);
+void drm_client_dev_unregister(struct drm_device *dev);
+void drm_client_dev_hotplug(struct drm_device *dev);
+void drm_client_dev_lastclose(struct drm_device *dev);
+
+int drm_client_get_file(struct drm_client_dev *client);
+void drm_client_put_file(struct drm_client_dev *client);
+struct drm_event *
+drm_client_read_event(struct drm_client_dev *client, bool block);
+
+struct drm_client_connector {
+	unsigned int conn_id;
+	unsigned int status;
+	unsigned int crtc_id;
+	struct drm_mode_modeinfo *modes;
+	unsigned int num_modes;
+	bool has_tile;
+	int tile_group;
+	u8 tile_h_loc, tile_v_loc;
+};
+
+struct drm_client_display {
+	struct drm_client_dev *client;
+
+	struct drm_client_connector **connectors;
+	unsigned int num_connectors;
+
+	struct mutex modes_lock;
+	struct drm_mode_modeinfo *modes;
+	unsigned int num_modes;
+
+	bool cloned;
+	bool no_flushing;
+};
+
+void drm_client_display_free(struct drm_client_display *display);
+struct drm_client_display *
+drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict);
+
+int drm_client_display_update_modes(struct drm_client_display *display,
+				    bool *mode_changed);
+
+static inline bool
+drm_client_display_is_tiled(struct drm_client_display *display)
+{
+	return !display->cloned && display->num_connectors > 1;
+}
+
+int drm_client_display_dpms(struct drm_client_display *display, int mode);
+int drm_client_display_wait_vblank(struct drm_client_display *display);
+
+struct drm_mode_modeinfo *
+drm_client_display_first_mode(struct drm_client_display *display);
+struct drm_mode_modeinfo *
+drm_client_display_next_mode(struct drm_client_display *display,
+			     struct drm_mode_modeinfo *mode);
+
+#define drm_client_display_for_each_mode(display, mode) \
+	for (mode = drm_client_display_first_mode(display); mode; \
+	     mode = drm_client_display_next_mode(display, mode))
+
+unsigned int
+drm_client_display_preferred_depth(struct drm_client_display *display);
+
+int drm_client_display_commit_mode(struct drm_client_display *display,
+				   u32 fb_id, struct drm_mode_modeinfo *mode);
+unsigned int drm_client_display_current_fb(struct drm_client_display *display);
+int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
+			     struct drm_clip_rect *clips, unsigned int num_clips);
+int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
+				 bool event);
+
+struct drm_client_buffer {
+	struct drm_client_dev *client;
+	u32 width;
+	u32 height;
+	u32 format;
+	u32 handle;
+	u32 pitch;
+	u64 size;
+	struct dma_buf *dma_buf;
+	void *vaddr;
+
+	unsigned int *fb_ids;
+	unsigned int num_fbs;
+};
+
+struct drm_client_buffer *
+drm_client_framebuffer_create(struct drm_client_dev *client,
+			      struct drm_mode_modeinfo *mode, u32 format);
+void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
+struct drm_client_buffer *
+drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
+			 u32 format);
+void drm_client_buffer_delete(struct drm_client_buffer *buffer);
+int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
+			    struct drm_mode_modeinfo *mode);
+int drm_client_buffer_rmfb(struct drm_client_buffer *buffer);
diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
index 7c4fa32f3fc6..32dfed3d5a86 100644
--- a/include/drm/drm_device.h
+++ b/include/drm/drm_device.h
@@ -67,6 +67,7 @@ struct drm_device {
 
 	struct mutex filelist_mutex;
 	struct list_head filelist;
+	struct list_head filelist_internal;
 
 	/** \name Memory management */
 	/*@{ */
diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
index 5176c3797680..39af8a4be7b3 100644
--- a/include/drm/drm_file.h
+++ b/include/drm/drm_file.h
@@ -248,6 +248,13 @@ struct drm_file {
 	 */
 	void *driver_priv;
 
+	/**
+	 * @user_priv:
+	 *
+	 * Optional pointer for user private data. Useful for in-kernel clients.
+	 */
+	void *user_priv;
+
 	/**
 	 * @fbs:
 	 *
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 10/12] drm/client: Add fbdev emulation client
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (8 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 09/12] drm: Add API for in-kernel clients Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-03-06  9:06   ` Daniel Vetter
  2018-02-22 20:06 ` [RFC v3 11/12] drm/client: Add bootsplash client Noralf Trønnes
                   ` (2 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

This adds generic fbdev emulation for drivers that support the
dumb buffer API. No fbdev code is necessary in the driver.

Differences from drm_fb_helper:
- The backing buffer is created when the first fd is opened.
- Supports changing the mode from userspace.
- Doesn't restore on lastclose if there is no fd/fbcon open.
- Supports changing the buffer size (yres_virtual) from userspace before
  the fd is opened (double/trippel/... buffering).
- Panning is only supported as page flipping, so no partial offset.
- Supports real page flipping with FBIO_WAITFORVSYNC waiting on the
  actual flip.
- Supports framebuffer flushing for fbcon on buffers that doesn't support
  fbdev deferred I/O (shmem). mmap doesn't work but fbcon does.

TODO:
- suspend/resume
- sysrq
- Look more into plane format selection/support.
- Need a way for the driver to say that it wants generic fbdev emulation.
  The client .new hook is run in drm_dev_register() which is before
  drivers set up fbdev themselves. So the client can't look at
  drm_device->fb_helper to find out.
- Do we need to support FB_VISUAL_PSEUDOCOLOR?

TROUBLE:
- fbcon can't handle fb_open returning an error, it just heads on. This
  results in a NULL deref in fbcon_init(). fbcon/vt is awful when it
  comes to error handling. It doesn't look to be easily fixed, so I guess
  a buffer has to be pre-allocated to ensure health and safety.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/client/Kconfig     |  16 +
 drivers/gpu/drm/client/Makefile    |   2 +
 drivers/gpu/drm/client/drm_fbdev.c | 997 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1015 insertions(+)
 create mode 100644 drivers/gpu/drm/client/drm_fbdev.c

diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
index 4bb8e4655ff7..73902ab44c75 100644
--- a/drivers/gpu/drm/client/Kconfig
+++ b/drivers/gpu/drm/client/Kconfig
@@ -1,4 +1,20 @@
 menu "DRM Clients"
 	depends on DRM
 
+config DRM_CLIENT_FBDEV
+	tristate "Generic fbdev emulation"
+	depends on DRM
+	select FB
+	select FRAMEBUFFER_CONSOLE if !EXPERT
+	select FRAMEBUFFER_CONSOLE_DETECT_PRIMARY if FRAMEBUFFER_CONSOLE
+	select FB_SYS_FOPS
+	select FB_SYS_FILLRECT
+	select FB_SYS_COPYAREA
+	select FB_SYS_IMAGEBLIT
+	select FB_DEFERRED_IO
+	select FB_MODE_HELPERS
+	select VIDEOMODE_HELPERS
+	help
+	  Generic fbdev emulation
+
 endmenu
diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
index f66554cd5c45..3ff694429dec 100644
--- a/drivers/gpu/drm/client/Makefile
+++ b/drivers/gpu/drm/client/Makefile
@@ -1 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_DRM_CLIENT_FBDEV) += drm_fbdev.o
diff --git a/drivers/gpu/drm/client/drm_fbdev.c b/drivers/gpu/drm/client/drm_fbdev.c
new file mode 100644
index 000000000000..e28416d72de1
--- /dev/null
+++ b/drivers/gpu/drm/client/drm_fbdev.c
@@ -0,0 +1,997 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2018 Noralf Trønnes
+
+#include <linux/console.h>
+#include <linux/dma-buf.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <video/videomode.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_print.h>
+
+struct drm_fbdev {
+	struct mutex lock;
+
+	struct drm_client_dev *client;
+	struct drm_client_display *display;
+
+	unsigned int open_count;
+	struct drm_client_buffer *buffer;
+	bool page_flip_sent;
+	u32 curr_fb;
+
+	struct fb_info *info;
+	u32 pseudo_palette[17];
+
+	bool flush;
+	bool defio_no_flushing;
+	struct drm_clip_rect dirty_clip;
+	spinlock_t dirty_lock;
+	struct work_struct dirty_work;
+};
+
+static int drm_fbdev_mode_to_fb_mode(struct drm_device *dev,
+				     struct drm_mode_modeinfo *mode,
+				     struct fb_videomode *fb_mode)
+{
+	struct drm_display_mode display_mode = { };
+	struct videomode videomode = { };
+	int ret;
+
+	ret = drm_mode_convert_umode(dev, &display_mode, mode);
+	if (ret)
+		return ret;
+
+	memset(fb_mode, 0, sizeof(*fb_mode));
+	drm_display_mode_to_videomode(&display_mode, &videomode);
+	fb_videomode_from_videomode(&videomode, fb_mode);
+
+	return 0;
+}
+
+static void drm_fbdev_destroy_modelist(struct fb_info *info)
+{
+	struct fb_modelist *modelist, *tmp;
+
+	list_for_each_entry_safe(modelist, tmp, &info->modelist, list) {
+		kfree(modelist->mode.name);
+		list_del(&modelist->list);
+		kfree(modelist);
+	}
+}
+
+static void drm_fbdev_use_first_mode(struct fb_info *info)
+{
+	struct fb_modelist *modelist;
+
+	modelist = list_first_entry(&info->modelist, struct fb_modelist, list);
+	fb_videomode_to_var(&info->var, &modelist->mode);
+	info->mode = &modelist->mode;
+}
+
+static struct drm_mode_modeinfo *drm_fbdev_get_drm_mode(struct drm_fbdev *fbdev)
+{
+	struct drm_mode_modeinfo *mode_pos, *mode = NULL;
+	struct fb_info *info = fbdev->info;
+	struct fb_videomode tmp;
+
+	mutex_lock(&fbdev->display->modes_lock);
+	drm_client_display_for_each_mode(fbdev->display, mode_pos) {
+		if (drm_fbdev_mode_to_fb_mode(fbdev->client->dev, mode_pos, &tmp))
+			continue;
+		if (fb_mode_is_equal(info->mode, &tmp)) {
+			mode = mode_pos;
+			break;
+		}
+	}
+	mutex_unlock(&fbdev->display->modes_lock);
+
+	return mode;
+}
+
+/* Return number of modes or negative error */
+static int drm_fbdev_sync_modes(struct drm_fbdev *fbdev, bool force)
+{
+	struct fb_info *info = fbdev->info;
+	struct drm_mode_modeinfo *mode;
+	struct fb_videomode fb_mode;
+	bool changed;
+
+	struct fb_modelist *fbdev_modelist;
+	int num_modes;
+
+	num_modes = drm_client_display_update_modes(fbdev->display, &changed);
+	if (num_modes <= 0)
+		return num_modes;
+
+	if (!info)
+		return num_modes;
+
+	if (!force && !changed)
+		return num_modes;
+
+	drm_fbdev_destroy_modelist(info);
+
+	mutex_lock(&fbdev->display->modes_lock);
+	drm_client_display_for_each_mode(fbdev->display, mode) {
+		if (drm_fbdev_mode_to_fb_mode(fbdev->client->dev, mode, &fb_mode)) {
+			num_modes--;
+			continue;
+		}
+
+		fbdev_modelist = kzalloc(sizeof(*fbdev_modelist), GFP_KERNEL);
+		if (!fbdev_modelist) {
+			drm_fbdev_destroy_modelist(info);
+			mutex_unlock(&fbdev->display->modes_lock);
+			return -ENOMEM;
+		}
+
+		fbdev_modelist->mode = fb_mode;
+		fbdev_modelist->mode.name = kstrndup(mode->name,
+						     DRM_DISPLAY_MODE_LEN,
+						     GFP_KERNEL);
+
+		if (mode->type & DRM_MODE_TYPE_PREFERRED)
+			fbdev_modelist->mode.flag |= FB_MODE_IS_FIRST;
+
+		list_add_tail(&fbdev_modelist->list, &info->modelist);
+	}
+	mutex_unlock(&fbdev->display->modes_lock);
+
+	if (!fbdev->open_count)
+		drm_fbdev_use_first_mode(info);
+
+	return num_modes;
+}
+
+static void drm_fbdev_format_fill_var(u32 format, struct fb_var_screeninfo *var)
+{
+	switch (format) {
+	case DRM_FORMAT_XRGB1555:
+		var->red.offset = 10;
+		var->red.length = 5;
+		var->green.offset = 5;
+		var->green.length = 5;
+		var->blue.offset = 0;
+		var->blue.length = 5;
+		var->transp.offset = 0;
+		var->transp.length = 0;
+		break;
+	case DRM_FORMAT_ARGB1555:
+		var->red.offset = 10;
+		var->red.length = 5;
+		var->green.offset = 5;
+		var->green.length = 5;
+		var->blue.offset = 0;
+		var->blue.length = 5;
+		var->transp.offset = 15;
+		var->transp.length = 1;
+		break;
+	case DRM_FORMAT_RGB565:
+		var->red.offset = 11;
+		var->red.length = 5;
+		var->green.offset = 5;
+		var->green.length = 6;
+		var->blue.offset = 0;
+		var->blue.length = 5;
+		var->transp.offset = 0;
+		var->transp.length = 0;
+		break;
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_XRGB8888:
+		var->red.offset = 16;
+		var->red.length = 8;
+		var->green.offset = 8;
+		var->green.length = 8;
+		var->blue.offset = 0;
+		var->blue.length = 8;
+		var->transp.offset = 0;
+		var->transp.length = 0;
+		break;
+	case DRM_FORMAT_ARGB8888:
+		var->red.offset = 16;
+		var->red.length = 8;
+		var->green.offset = 8;
+		var->green.length = 8;
+		var->blue.offset = 0;
+		var->blue.length = 8;
+		var->transp.offset = 24;
+		var->transp.length = 8;
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		return;
+	}
+
+	var->colorspace = 0;
+	var->grayscale = 0;
+	var->nonstd = 0;
+}
+
+int drm_fbdev_var_to_format(struct fb_var_screeninfo *var, u32 *format)
+{
+	switch (var->bits_per_pixel) {
+	case 15:
+		*format = DRM_FORMAT_ARGB1555;
+		break;
+	case 16:
+		if (var->green.length != 5)
+			*format = DRM_FORMAT_RGB565;
+		else if (var->transp.length > 0)
+			*format = DRM_FORMAT_ARGB1555;
+		else
+			*format = DRM_FORMAT_XRGB1555;
+		break;
+	case 24:
+		*format = DRM_FORMAT_RGB888;
+		break;
+	case 32:
+		if (var->transp.length > 0)
+			*format = DRM_FORMAT_ARGB8888;
+		else
+			*format = DRM_FORMAT_XRGB8888;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void drm_fbdev_dirty_work(struct work_struct *work)
+{
+	struct drm_fbdev *fbdev = container_of(work, struct drm_fbdev,
+					       dirty_work);
+	struct drm_clip_rect *clip = &fbdev->dirty_clip;
+	struct drm_clip_rect clip_copy;
+	unsigned long flags;
+
+	spin_lock_irqsave(&fbdev->dirty_lock, flags);
+	clip_copy = *clip;
+	clip->x1 = clip->y1 = ~0;
+	clip->x2 = clip->y2 = 0;
+	spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
+
+	/* call dirty callback only when it has been really touched */
+	if (clip_copy.x1 < clip_copy.x2 && clip_copy.y1 < clip_copy.y2)
+		drm_client_display_flush(fbdev->display, fbdev->curr_fb,
+					 &clip_copy, 1);
+}
+
+static void drm_fbdev_dirty(struct fb_info *info, u32 x, u32 y,
+			    u32 width, u32 height)
+{
+	struct drm_fbdev *fbdev = info->par;
+	struct drm_clip_rect *clip = &fbdev->dirty_clip;
+	unsigned long flags;
+
+	if (!fbdev->flush)
+		return;
+
+	spin_lock_irqsave(&fbdev->dirty_lock, flags);
+	clip->x1 = min_t(u32, clip->x1, x);
+	clip->y1 = min_t(u32, clip->y1, y);
+	clip->x2 = max_t(u32, clip->x2, x + width);
+	clip->y2 = max_t(u32, clip->y2, y + height);
+	spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
+
+	schedule_work(&fbdev->dirty_work);
+}
+
+static void drm_fbdev_deferred_io(struct fb_info *info,
+				  struct list_head *pagelist)
+{
+	struct drm_fbdev *fbdev = info->par;
+	unsigned long start, end, min, max;
+	struct page *page;
+	u32 y1, y2;
+
+	/* Is userspace doing explicit pageflip flushing? */
+	if (fbdev->defio_no_flushing)
+		return;
+
+	min = ULONG_MAX;
+	max = 0;
+	list_for_each_entry(page, pagelist, lru) {
+		start = page->index << PAGE_SHIFT;
+		end = start + PAGE_SIZE;
+		min = min(min, start);
+		max = max(max, end);
+	}
+
+	if (min < max) {
+		y1 = min / info->fix.line_length;
+		y2 = DIV_ROUND_UP(max, info->fix.line_length);
+		y2 = min(y2, info->var.yres);
+		drm_fbdev_dirty(info, 0, y1, info->var.xres, y2 - y1);
+	}
+}
+
+static struct fb_deferred_io drm_fbdev_fbdefio = {
+	.delay		= HZ / 20,
+	.deferred_io	= drm_fbdev_deferred_io,
+};
+
+static int
+drm_fbdev_fb_mmap_notsupp(struct fb_info *info, struct vm_area_struct *vma)
+{
+	return -ENOTSUPP;
+}
+
+static void drm_fbdev_delete_buffer(struct drm_fbdev *fbdev)
+{
+	struct fb_info *info = fbdev->info;
+
+	if (info->fbdefio) {
+		/* Stop worker and clear page->mapping */
+		fb_deferred_io_cleanup(info);
+		info->fbdefio = NULL;
+	}
+	if (fbdev->flush) {
+		fbdev->flush = false;
+		cancel_work_sync(&fbdev->dirty_work);
+	}
+
+	drm_client_buffer_rmfb(fbdev->buffer);
+	drm_client_buffer_delete(fbdev->buffer);
+
+	fbdev->buffer = NULL;
+	fbdev->curr_fb = 0;
+	fbdev->page_flip_sent = false;
+	info->screen_buffer = NULL;
+	info->screen_size = 0;
+	info->fix.smem_len = 0;
+	info->fix.line_length = 0;
+}
+
+/* Temporary hack to make tinydrm work before converting to vmalloc buffers */
+static int drm_fbdev_cma_deferred_io_mmap(struct fb_info *info,
+					  struct vm_area_struct *vma)
+{
+	fb_deferred_io_mmap(info, vma);
+	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+	return 0;
+}
+
+static int drm_fbdev_create_buffer(struct drm_fbdev *fbdev)
+{
+	struct drm_client_dev *client = fbdev->client;
+	struct fb_info *info = fbdev->info;
+	struct drm_client_buffer *buffer;
+	struct drm_mode_modeinfo *mode;
+	u32 format;
+	int ret;
+
+	ret = drm_fbdev_var_to_format(&info->var, &format);
+	if (ret)
+		return ret;
+
+	buffer = drm_client_buffer_create(client, info->var.xres_virtual,
+					  info->var.yres_virtual, format);
+	if (IS_ERR(buffer))
+		return PTR_ERR(buffer);
+
+	mode = drm_fbdev_get_drm_mode(fbdev);
+	if (!mode)
+		return -EINVAL;
+
+	ret = drm_client_buffer_addfb(buffer, mode);
+	if (ret)
+		goto err_free_buffer;
+
+	fbdev->curr_fb = buffer->fb_ids[0];
+
+	if (drm_mode_can_dirtyfb(client->dev, fbdev->curr_fb, client->file)) {
+		fbdev->flush = true;
+/*		if (is_vmalloc_addr(buffer->vaddr)) { */
+/* Temporary hack for testing on tinydrm before it has moved to vmalloc */
+		if (1) {
+			fbdev->dirty_clip.x1 = fbdev->dirty_clip.y1 = ~0;
+			fbdev->dirty_clip.x2 = fbdev->dirty_clip.y2 = 0;
+			info->fbdefio = &drm_fbdev_fbdefio;
+
+			/* tinydrm hack */
+			info->fix.smem_start = page_to_phys(virt_to_page(buffer->vaddr));
+
+			fb_deferred_io_init(info);
+			/* tinydrm hack */
+			info->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap;
+		} else {
+			info->fbops->fb_mmap = drm_fbdev_fb_mmap_notsupp;
+		}
+	}
+
+	fbdev->buffer = buffer;
+	info->screen_buffer = buffer->vaddr;
+	info->screen_size = buffer->size;
+	info->fix.smem_len = buffer->size;
+	info->fix.line_length = buffer->pitch;
+
+	return 0;
+
+err_free_buffer:
+	drm_client_buffer_delete(buffer);
+
+	return ret;
+}
+
+static int drm_fbdev_fb_open(struct fb_info *info, int user)
+{
+	struct drm_fbdev *fbdev = info->par;
+	int ret = 0;
+
+	DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
+
+	mutex_lock(&fbdev->lock);
+
+	if (!fbdev->display) {
+		ret = -ENODEV;
+		goto out_unlock;
+	}
+
+	if (!fbdev->open_count) {
+		/* Pipeline is disabled, make sure it's forced on */
+		info->var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
+		ret = drm_fbdev_create_buffer(fbdev);
+		if (ret)
+			goto out_unlock;
+	}
+
+	fbdev->open_count++;
+
+out_unlock:
+	mutex_unlock(&fbdev->lock);
+
+	if (ret)
+		DRM_DEV_ERROR(fbdev->client->dev->dev, "fb_open failed (%d)\n", ret);
+
+	return ret;
+}
+
+static int drm_fbdev_fb_release(struct fb_info *info, int user)
+{
+	struct drm_fbdev *fbdev = info->par;
+
+	DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
+	mutex_lock(&fbdev->lock);
+
+	if (--fbdev->open_count == 0) {
+		drm_client_display_dpms(fbdev->display, DRM_MODE_DPMS_OFF);
+		drm_fbdev_delete_buffer(fbdev);
+	}
+
+	fbdev->defio_no_flushing = false;
+
+	mutex_unlock(&fbdev->lock);
+
+	return 0;
+}
+
+static ssize_t drm_fbdev_fb_write(struct fb_info *info, const char __user *buf,
+				  size_t count, loff_t *ppos)
+{
+	ssize_t ret;
+
+	ret = fb_sys_write(info, buf, count, ppos);
+	if (ret > 0)
+		drm_fbdev_dirty(info, 0, 0, info->var.xres, info->var.yres);
+
+	return ret;
+}
+
+static void
+drm_fbdev_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
+{
+	sys_fillrect(info, rect);
+	drm_fbdev_dirty(info, rect->dx, rect->dy, rect->width, rect->height);
+}
+
+static void
+drm_fbdev_fb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
+{
+	sys_copyarea(info, area);
+	drm_fbdev_dirty(info, area->dx, area->dy, area->width, area->height);
+}
+
+static void
+drm_fbdev_fb_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+	sys_imageblit(info, image);
+	drm_fbdev_dirty(info, image->dx, image->dy, image->width, image->height);
+}
+
+static int
+drm_fbdev_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	u32 new_format, old_format, yres_virtual;
+	struct drm_fbdev *fbdev = info->par;
+	const struct fb_videomode *fb_mode;
+	bool is_open;
+	int ret;
+
+	mutex_lock(&fbdev->lock);
+	is_open = fbdev->open_count;
+	mutex_unlock(&fbdev->lock);
+
+	if (!is_open && in_dbg_master())
+		return -EINVAL;
+
+	/* Can be called from sysfs */
+	if (is_open && (var->xres_virtual > fbdev->buffer->width ||
+	    var->yres_virtual > fbdev->buffer->height)) {
+		DRM_DEBUG_KMS("Cannot increase virtual resolution while open\n");
+		return -EBUSY;
+	}
+
+	if (var->xres > var->xres_virtual || var->yres > var->yres_virtual) {
+		DRM_DEBUG_KMS("Requested width/height to big: %dx%d > virtual %dx%d\n",
+			      var->xres, var->yres, var->xres_virtual,
+			      var->yres_virtual);
+		return -EINVAL;
+	}
+
+	ret = drm_fbdev_var_to_format(var, &new_format);
+	if (ret) {
+		DRM_DEBUG_KMS("Unsupported format\n");
+		return -EINVAL;
+	}
+
+	ret = drm_fbdev_var_to_format(&info->var, &old_format);
+	if (ret)
+		return ret;
+
+	if (new_format != old_format && is_open) {
+		DRM_DEBUG_KMS("Cannot change format while open\n");
+		return -EBUSY;
+	}
+
+	drm_fbdev_format_fill_var(new_format, var);
+
+	fb_mode = fb_find_best_mode(var, &info->modelist);
+	if (!fb_mode)
+		return -EINVAL;
+
+	yres_virtual = var->yres_virtual;
+	fb_videomode_to_var(var, fb_mode);
+	var->yres_virtual = yres_virtual;
+
+	return 0;
+}
+
+static int drm_fbdev_fb_set_par(struct fb_info *info)
+{
+	struct drm_fbdev *fbdev = info->par;
+	const struct fb_videomode *fb_mode;
+	struct drm_mode_modeinfo *mode;
+	bool mode_changed;
+	int ret;
+
+	mutex_lock(&fbdev->lock);
+
+	if (!fbdev->open_count) {
+		ret = 0;
+		goto out_unlock;
+	}
+
+	fb_mode = fb_match_mode(&info->var, &info->modelist);
+	if (!fb_mode) {
+		DRM_DEBUG_KMS("Couldn't find var mode\n");
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	mode_changed = !fb_mode_is_equal(info->mode, fb_mode);
+	info->mode = (struct fb_videomode *)fb_mode;
+
+	mode = drm_fbdev_get_drm_mode(fbdev);
+	if (!mode) {
+		DRM_DEBUG_KMS("Couldn't find the matching DRM mode\n");
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	if (mode_changed) {
+		drm_client_buffer_rmfb(fbdev->buffer);
+		fbdev->curr_fb = 0;
+		ret = drm_client_buffer_addfb(fbdev->buffer, mode);
+		if (ret)
+			goto out_unlock;
+
+		fbdev->curr_fb = fbdev->buffer->fb_ids[0];
+		info->var.yoffset = 0;
+	}
+
+//	info->var.width = drm_mode->width_mm;
+//	info->var.height = drm_mode->height_mm;
+
+	/* Panning is only supported to do page flipping */
+	info->fix.ypanstep = info->var.yres;
+
+	ret = drm_client_display_commit_mode(fbdev->display, fbdev->curr_fb, mode);
+
+out_unlock:
+	mutex_unlock(&fbdev->lock);
+
+	return ret;
+}
+
+/*
+ * Do we need to support FB_VISUAL_PSEUDOCOLOR?
+
+static int drm_fbdev_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
+		    unsigned blue, unsigned transp, struct fb_info *info)
+{
+
+}
+*/
+
+static int setcmap_pseudo_palette(struct fb_cmap *cmap, struct fb_info *info)
+{
+	u32 *palette = (u32 *)info->pseudo_palette;
+	int i;
+
+	if (cmap->start + cmap->len > 16)
+		return -EINVAL;
+
+	for (i = 0; i < cmap->len; ++i) {
+		u16 red = cmap->red[i];
+		u16 green = cmap->green[i];
+		u16 blue = cmap->blue[i];
+		u32 value;
+
+		red >>= 16 - info->var.red.length;
+		green >>= 16 - info->var.green.length;
+		blue >>= 16 - info->var.blue.length;
+		value = (red << info->var.red.offset) |
+			(green << info->var.green.offset) |
+			(blue << info->var.blue.offset);
+		if (info->var.transp.length > 0) {
+			u32 mask = (1 << info->var.transp.length) - 1;
+
+			mask <<= info->var.transp.offset;
+			value |= mask;
+		}
+		palette[cmap->start + i] = value;
+	}
+
+	return 0;
+}
+
+static int drm_fbdev_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
+{
+	if (oops_in_progress)
+		return -EBUSY;
+
+	if (info->fix.visual == FB_VISUAL_TRUECOLOR)
+		return setcmap_pseudo_palette(cmap, info);
+
+	return -EINVAL;
+}
+
+static int drm_fbdev_fb_blank(int blank, struct fb_info *info)
+{
+	struct drm_fbdev *fbdev = info->par;
+	bool is_open;
+	int mode;
+
+	if (oops_in_progress)
+		return -EBUSY;
+
+	mutex_lock(&fbdev->lock);
+	is_open = fbdev->open_count;
+	mutex_unlock(&fbdev->lock);
+
+	if (!is_open)
+		return -EINVAL;
+
+	if (blank == FB_BLANK_UNBLANK)
+		mode = DRM_MODE_DPMS_ON;
+	else
+		mode = DRM_MODE_DPMS_OFF;
+
+	return drm_client_display_dpms(fbdev->display, mode);
+}
+
+static int
+drm_fbdev_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	struct drm_fbdev *fbdev = info->par;
+	struct drm_event *event;
+	unsigned int fb_idx;
+	int ret = 0;
+
+	mutex_lock(&fbdev->lock);
+
+	if (!fbdev->open_count)
+		goto out_unlock;
+
+	fb_idx = var->yoffset / info->var.yres;
+	if (fb_idx >= fbdev->buffer->num_fbs) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	/* Drain previous flip event if userspace didn't care */
+	if (fbdev->page_flip_sent) {
+		event = drm_client_read_event(fbdev->client, false);
+		if (!IS_ERR(event))
+			kfree(event);
+		fbdev->page_flip_sent = false;
+	}
+
+	if (fbdev->curr_fb == fbdev->buffer->fb_ids[fb_idx])
+		goto out_unlock;
+
+	fbdev->curr_fb = fbdev->buffer->fb_ids[fb_idx];
+	fbdev->defio_no_flushing = true;
+
+	ret = drm_client_display_page_flip(fbdev->display, fbdev->curr_fb, true);
+	if (ret)
+		goto out_unlock;
+
+	fbdev->page_flip_sent = true;
+
+out_unlock:
+	mutex_unlock(&fbdev->lock);
+
+	return ret;
+}
+
+static int drm_fbdev_fb_ioctl(struct fb_info *info, unsigned int cmd,
+			      unsigned long arg)
+{
+	struct drm_fbdev *fbdev = info->par;
+	struct drm_event *event;
+	bool page_flip_sent;
+	int ret = 0;
+
+	switch (cmd) {
+//	case FBIOGET_VBLANK:
+//		break;
+	case FBIO_WAITFORVSYNC:
+		mutex_lock(&fbdev->lock);
+		page_flip_sent = fbdev->page_flip_sent;
+		fbdev->page_flip_sent = false;
+		mutex_unlock(&fbdev->lock);
+
+		if (page_flip_sent) {
+			event = drm_client_read_event(fbdev->client, true);
+			if (IS_ERR(event))
+				ret = PTR_ERR(event);
+			else
+				kfree(event);
+		} else {
+			drm_client_display_wait_vblank(fbdev->display);
+		}
+
+		break;
+	default:
+		ret = -ENOTTY;
+	}
+
+	return ret;
+}
+
+static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+	struct drm_fbdev *fbdev = info->par;
+
+	return dma_buf_mmap(fbdev->buffer->dma_buf, vma, 0);
+}
+
+static void drm_fbdev_fb_destroy(struct fb_info *info)
+{
+	struct drm_fbdev *fbdev = info->par;
+
+	DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
+	drm_client_display_free(fbdev->display);
+	drm_client_free(fbdev->client);
+	kfree(fbdev);
+}
+
+static struct fb_ops drm_fbdev_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_open	= drm_fbdev_fb_open,
+	.fb_release	= drm_fbdev_fb_release,
+	.fb_read	= fb_sys_read,
+	.fb_write	= drm_fbdev_fb_write,
+	.fb_check_var	= drm_fbdev_fb_check_var,
+	.fb_set_par	= drm_fbdev_fb_set_par,
+//	.fb_setcolreg	= drm_fbdev_fb_setcolreg,
+	.fb_setcmap	= drm_fbdev_fb_setcmap,
+	.fb_blank	= drm_fbdev_fb_blank,
+	.fb_pan_display	= drm_fbdev_fb_pan_display,
+	.fb_fillrect	= drm_fbdev_fb_fillrect,
+	.fb_copyarea	= drm_fbdev_fb_copyarea,
+	.fb_imageblit	= drm_fbdev_fb_imageblit,
+	.fb_ioctl	= drm_fbdev_fb_ioctl,
+	.fb_mmap	= drm_fbdev_fb_mmap,
+	.fb_destroy	= drm_fbdev_fb_destroy,
+};
+
+static int drm_fbdev_register_framebuffer(struct drm_fbdev *fbdev)
+{
+	struct drm_client_display *display;
+	struct fb_info *info;
+	struct fb_ops *fbops;
+	u32 format;
+	int ret;
+
+	display = drm_client_display_get_first_enabled(fbdev->client, false);
+	if (IS_ERR_OR_NULL(display))
+		return PTR_ERR_OR_ZERO(display);
+
+	fbdev->display = display;
+
+	/*
+	 * fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per instance
+	 * version is necessary. We do it for all users since we don't know
+	 * yet if the fb has a dirty callback.
+	 */
+	fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
+	if (!fbops) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	*fbops = drm_fbdev_fb_ops;
+
+	info = framebuffer_alloc(0, fbdev->client->dev->dev);
+	if (!info) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	ret = fb_alloc_cmap(&info->cmap, 256, 0);
+	if (ret)
+		goto err_release;
+
+	info->par = fbdev;
+	info->fbops = fbops;
+	INIT_LIST_HEAD(&info->modelist);
+	info->pseudo_palette = fbdev->pseudo_palette;
+
+	info->fix.type = FB_TYPE_PACKED_PIXELS;
+	info->fix.visual = FB_VISUAL_TRUECOLOR;
+	info->fix.ypanstep = info->var.yres;
+
+	strcpy(info->fix.id, "DRM emulated");
+
+	fbdev->info = info;
+	ret = drm_fbdev_sync_modes(fbdev, true);
+	if (ret < 0)
+		goto err_free_cmap;
+
+	info->var.bits_per_pixel = drm_client_display_preferred_depth(fbdev->display);
+	ret = drm_fbdev_var_to_format(&info->var, &format);
+	if (ret) {
+		DRM_WARN("Unsupported bpp, assuming x8r8g8b8 pixel format\n");
+		format = DRM_FORMAT_XRGB8888;
+	}
+	drm_fbdev_format_fill_var(format, &info->var);
+
+	info->var.xres_virtual = info->var.xres;
+	info->var.yres_virtual = info->var.yres;
+
+	info->var.yres_virtual *= CONFIG_DRM_FBDEV_OVERALLOC;
+	info->var.yres_virtual /= 100;
+
+	ret = register_framebuffer(info);
+	if (ret)
+		goto err_free_cmap;
+
+	dev_info(fbdev->client->dev->dev, "fb%d: %s frame buffer device\n",
+		 info->node, info->fix.id);
+
+	return 0;
+
+err_free_cmap:
+	fb_dealloc_cmap(&info->cmap);
+err_release:
+	framebuffer_release(info);
+err_free:
+	kfree(fbops);
+	fbdev->info = NULL;
+	drm_client_display_free(fbdev->display);
+	fbdev->display = NULL;
+
+	return ret;
+}
+
+static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
+{
+	struct drm_fbdev *fbdev = client->private;
+	int ret;
+
+	if (!fbdev->info)
+		ret = drm_fbdev_register_framebuffer(fbdev);
+	else
+		ret = drm_fbdev_sync_modes(fbdev, false);
+
+	return ret;
+}
+
+static int drm_fbdev_client_new(struct drm_client_dev *client)
+{
+	struct drm_fbdev *fbdev;
+
+	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
+	if (!fbdev)
+		return -ENOMEM;
+
+	mutex_init(&fbdev->lock);
+	spin_lock_init(&fbdev->dirty_lock);
+	INIT_WORK(&fbdev->dirty_work, drm_fbdev_dirty_work);
+
+	fbdev->client = client;
+	client->private = fbdev;
+
+	/*
+	 * vc4 isn't done with it's setup when drm_dev_register() is called.
+	 * It should have shouldn't it?
+	 * So to keep it from crashing defer setup to hotplug...
+	 */
+	if (client->dev->mode_config.max_width)
+		drm_fbdev_client_hotplug(client);
+
+	return 0;
+}
+
+static int drm_fbdev_client_remove(struct drm_client_dev *client)
+{
+	struct drm_fbdev *fbdev = client->private;
+
+	if (!fbdev->info) {
+		kfree(fbdev);
+		return 0;
+	}
+
+	unregister_framebuffer(fbdev->info);
+
+	/* drm_fbdev_fb_destroy() frees the client */
+	return 1;
+}
+
+static int drm_fbdev_client_lastclose(struct drm_client_dev *client)
+{
+	struct drm_fbdev *fbdev = client->private;
+	int ret = -ENOENT;
+
+	if (fbdev->info)
+		ret = fbdev->info->fbops->fb_set_par(fbdev->info);
+
+	return ret;
+}
+
+static const struct drm_client_funcs drm_fbdev_client_funcs = {
+	.name		= "drm_fbdev",
+	.new		= drm_fbdev_client_new,
+	.remove		= drm_fbdev_client_remove,
+	.lastclose	= drm_fbdev_client_lastclose,
+	.hotplug	= drm_fbdev_client_hotplug,
+};
+
+static int __init drm_fbdev_init(void)
+{
+	return drm_client_register(&drm_fbdev_client_funcs);
+}
+module_init(drm_fbdev_init);
+
+static void __exit drm_fbdev_exit(void)
+{
+	drm_client_unregister(&drm_fbdev_client_funcs);
+}
+module_exit(drm_fbdev_exit);
+
+MODULE_DESCRIPTION("DRM Generic fbdev emulation");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 11/12] drm/client: Add bootsplash client
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (9 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 10/12] drm/client: Add fbdev emulation client Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-03-06  9:12   ` Daniel Vetter
  2018-02-22 20:06 ` [RFC v3 12/12] drm/client: Add VT console client Noralf Trønnes
  2018-02-22 21:03 ` ✗ Fi.CI.BAT: failure for drm: Add generic fbdev emulation (rev3) Patchwork
  12 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

Just a hack to test the client API.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/client/Kconfig          |   5 +
 drivers/gpu/drm/client/Makefile         |   1 +
 drivers/gpu/drm/client/drm_bootsplash.c | 205 ++++++++++++++++++++++++++++++++
 3 files changed, 211 insertions(+)
 create mode 100644 drivers/gpu/drm/client/drm_bootsplash.c

diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
index 73902ab44c75..16cf1e14620a 100644
--- a/drivers/gpu/drm/client/Kconfig
+++ b/drivers/gpu/drm/client/Kconfig
@@ -17,4 +17,9 @@ config DRM_CLIENT_FBDEV
 	help
 	  Generic fbdev emulation
 
+config DRM_CLIENT_BOOTSPLASH
+	tristate "DRM Bootsplash"
+	help
+	  DRM Bootsplash
+
 endmenu
diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
index 3ff694429dec..8660530e4646 100644
--- a/drivers/gpu/drm/client/Makefile
+++ b/drivers/gpu/drm/client/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0
 
 obj-$(CONFIG_DRM_CLIENT_FBDEV) += drm_fbdev.o
+obj-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += drm_bootsplash.o
diff --git a/drivers/gpu/drm/client/drm_bootsplash.c b/drivers/gpu/drm/client/drm_bootsplash.c
new file mode 100644
index 000000000000..43c703606e74
--- /dev/null
+++ b/drivers/gpu/drm/client/drm_bootsplash.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_print.h>
+
+struct drm_bootsplash {
+	struct drm_client_dev *client;
+	struct drm_client_display *display;
+	struct drm_client_buffer *buffer[2];
+	struct work_struct worker;
+	bool stop;
+};
+
+static u32 drm_bootsplash_color_table[3] = {
+	0x00ff0000, 0x0000ff00, 0x000000ff,
+};
+
+/* Draw a box with changing colors */
+static void
+drm_bootsplash_draw(struct drm_client_buffer *buffer, unsigned int sequence)
+{
+	unsigned int x, y;
+	u32 *pix;
+
+	pix = buffer->vaddr;
+	pix += ((buffer->height / 2) - 50) * buffer->width;
+	pix += (buffer->width / 2) - 50;
+
+	for (y = 0; y < 100; y++) {
+		for (x = 0; x < 100; x++)
+			*pix++ = drm_bootsplash_color_table[sequence];
+		pix += buffer->width - 100;
+	}
+}
+
+static void drm_bootsplash_worker(struct work_struct *work)
+{
+	struct drm_bootsplash *splash = container_of(work, struct drm_bootsplash,
+						     worker);
+	struct drm_event *event;
+	unsigned int i = 0, sequence = 0, fb_id;
+	int ret;
+
+	while (!splash->stop) {
+		/* Are we still in charge? */
+		fb_id = drm_client_display_current_fb(splash->display);
+		if (fb_id != splash->buffer[i]->fb_ids[0])
+			break;
+
+		/*
+		 * We can race with userspace here between checking and doing
+		 * the page flip, so double buffering isn't such a good idea.
+		 * Tearing probably isn't a problem on a presumably small splash
+		 * animation. I've kept it to test the page flip code.
+		 */
+
+		i = !i;
+		drm_bootsplash_draw(splash->buffer[i], sequence++);
+		if (sequence == 3)
+			sequence = 0;
+
+		ret = drm_client_display_page_flip(splash->display,
+						   splash->buffer[i]->fb_ids[0],
+						   true);
+		if (!ret) {
+			event = drm_client_read_event(splash->client, true);
+			if (!IS_ERR(event))
+				kfree(event);
+		}
+		msleep(500);
+	}
+
+	for (i = 0; i < 2; i++)
+		drm_client_framebuffer_delete(splash->buffer[i]);
+	drm_client_display_free(splash->display);
+}
+
+static int drm_bootsplash_setup(struct drm_bootsplash *splash)
+{
+	struct drm_client_dev *client = splash->client;
+	struct drm_client_buffer *buffer[2];
+	struct drm_client_display *display;
+	struct drm_mode_modeinfo *mode;
+	int ret, i;
+
+	display = drm_client_display_get_first_enabled(client, false);
+	if (IS_ERR(display))
+		return PTR_ERR(display);
+	if (!display)
+		return -ENOENT;
+
+	mode = drm_client_display_first_mode(display);
+	if (!mode) {
+		ret = -EINVAL;
+		goto err_free_display;
+	}
+
+	for (i = 0; i < 2; i++) {
+		buffer[i] = drm_client_framebuffer_create(client, mode,
+							  DRM_FORMAT_XRGB8888);
+		if (IS_ERR(buffer[i])) {
+			ret = PTR_ERR(buffer[i]);
+			goto err_free_buffer;
+		}
+	}
+
+	ret = drm_client_display_commit_mode(display, buffer[0]->fb_ids[0], mode);
+	if (ret)
+		goto err_free_buffer;
+
+	splash->display = display;
+	splash->buffer[0] = buffer[0];
+	splash->buffer[1] = buffer[1];
+
+	schedule_work(&splash->worker);
+
+	return 0;
+
+err_free_buffer:
+	for (i--; i >= 0; i--)
+		drm_client_framebuffer_delete(buffer[i]);
+err_free_display:
+	drm_client_display_free(display);
+
+	return ret;
+}
+
+static int drm_bootsplash_client_hotplug(struct drm_client_dev *client)
+{
+	struct drm_bootsplash *splash = client->private;
+	int ret = 0;
+
+	if (!splash->display)
+		ret = drm_bootsplash_setup(splash);
+
+	return ret;
+}
+
+static int drm_bootsplash_client_new(struct drm_client_dev *client)
+{
+	struct drm_bootsplash *splash;
+
+	splash = kzalloc(sizeof(*splash), GFP_KERNEL);
+	if (!splash)
+		return -ENOMEM;
+
+	INIT_WORK(&splash->worker, drm_bootsplash_worker);
+
+	splash->client = client;
+	client->private = splash;
+
+	/*
+	 * vc4 isn't done with it's setup when drm_dev_register() is called.
+	 * It should have shouldn't it?
+	 * So to keep it from crashing defer setup to hotplug...
+	 */
+	if (client->dev->mode_config.max_width)
+		drm_bootsplash_client_hotplug(client);
+
+	return 0;
+}
+
+static int drm_bootsplash_client_remove(struct drm_client_dev *client)
+{
+	struct drm_bootsplash *splash = client->private;
+
+	if (splash->display) {
+		splash->stop = true;
+		flush_work(&splash->worker);
+	}
+
+	kfree(splash);
+
+	return 0;
+}
+
+static const struct drm_client_funcs drm_bootsplash_client_funcs = {
+	.name		= "drm_bootsplash",
+	.new		= drm_bootsplash_client_new,
+	.remove		= drm_bootsplash_client_remove,
+	.hotplug	= drm_bootsplash_client_hotplug,
+};
+
+static int __init drm_bootsplash_init(void)
+{
+	return drm_client_register(&drm_bootsplash_client_funcs);
+}
+module_init(drm_bootsplash_init);
+
+static void __exit drm_bootsplash_exit(void)
+{
+	drm_client_unregister(&drm_bootsplash_client_funcs);
+}
+module_exit(drm_bootsplash_exit);
+
+MODULE_LICENSE("GPL");
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [RFC v3 12/12] drm/client: Add VT console client
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (10 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 11/12] drm/client: Add bootsplash client Noralf Trønnes
@ 2018-02-22 20:06 ` Noralf Trønnes
  2018-02-22 21:03 ` ✗ Fi.CI.BAT: failure for drm: Add generic fbdev emulation (rev3) Patchwork
  12 siblings, 0 replies; 26+ messages in thread
From: Noralf Trønnes @ 2018-02-22 20:06 UTC (permalink / raw)
  To: dri-devel; +Cc: daniel.vetter, intel-gfx, laurent.pinchart

Just a hack to test the client API.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/client/Kconfig     |   5 +
 drivers/gpu/drm/client/Makefile    |   1 +
 drivers/gpu/drm/client/drm_vtcon.c | 760 +++++++++++++++++++++++++++++++++++++
 3 files changed, 766 insertions(+)
 create mode 100644 drivers/gpu/drm/client/drm_vtcon.c

diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
index 16cf1e14620a..50ce88515b65 100644
--- a/drivers/gpu/drm/client/Kconfig
+++ b/drivers/gpu/drm/client/Kconfig
@@ -22,4 +22,9 @@ config DRM_CLIENT_BOOTSPLASH
 	help
 	  DRM Bootsplash
 
+config DRM_CLIENT_VTCON
+	tristate "DRM VT console"
+	help
+	  DRM VT console
+
 endmenu
diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
index 8660530e4646..47c8eaee5dab 100644
--- a/drivers/gpu/drm/client/Makefile
+++ b/drivers/gpu/drm/client/Makefile
@@ -2,3 +2,4 @@
 
 obj-$(CONFIG_DRM_CLIENT_FBDEV) += drm_fbdev.o
 obj-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += drm_bootsplash.o
+obj-$(CONFIG_DRM_CLIENT_VTCON) += drm_vtcon.o
diff --git a/drivers/gpu/drm/client/drm_vtcon.c b/drivers/gpu/drm/client/drm_vtcon.c
new file mode 100644
index 000000000000..19297d065509
--- /dev/null
+++ b/drivers/gpu/drm/client/drm_vtcon.c
@@ -0,0 +1,760 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2018 Noralf Trønnes
+
+#include <linux/console.h>
+#include <linux/font.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/vt_buffer.h>
+#include <linux/vt_kern.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+
+/* TODO Need a way to unbind */
+
+/* TODO: Scrolling */
+
+/*
+ * The code consists of 3 parts:
+ *
+ * 1. The DRM client
+ *    Gets a display, uses the first mode to find a font,
+ *    sets the max cols/rows and a matching text buffer.
+ *
+ * 2. The VT console
+ *    Writes to the text buffer which consists of CGA colored characters.
+ *    Schedules the worker when it needs rendering or blanking.
+ *
+ * 3. Worker
+ *    Does modesetting, blanking and rendering.
+ *    It takes a snapshot of the VT text buffer and renders the changes since
+ *    last.
+ */
+
+struct drm_vtcon_vc {
+	struct mutex lock;
+
+	u16 *text_buf;
+	size_t buf_len;
+
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int max_rows;
+	unsigned int max_cols;
+	const struct font_desc *font;
+	bool blank;
+	unsigned long cursor_blink_jiffies;
+};
+
+static struct drm_vtcon_vc *drm_vtcon_vc;
+
+struct drm_vtcon {
+	struct drm_client_dev *client;
+	struct drm_client_display *display;
+	struct drm_client_buffer *buffer;
+
+	unsigned int rows;
+	unsigned int cols;
+
+	u16 *text_buf[2];
+	size_t buf_len;
+	unsigned int buf_idx;
+	bool blank;
+};
+
+static struct drm_vtcon *vtcon_instance;
+
+#define drm_vtcon_debug(vc, fmt, ...) \
+	printk(KERN_DEBUG "%s[%u]: " fmt, __func__, vc->vc_num, ##__VA_ARGS__)
+
+/* CGA color palette: 4-bit RGBI: intense red green blue */
+static const u32 drm_vtcon_paletteX888[16] = {
+	0x00000000, /*  0 black */
+	0x000000aa, /*  1 blue */
+	0x0000aa00, /*  2 green */
+	0x0000aaaa, /*  3 cyan */
+	0x00aa0000, /*  4 red */
+	0x00aa00aa, /*  5 magenta */
+	0x00aa5500, /*  6 brown */
+	0x00aaaaaa, /*  7 light gray */
+	0x00555555, /*  8 dark gray */
+	0x005555ff, /*  9 bright blue */
+	0x0055ff55, /* 10 bright green */
+	0x0055ffff, /* 11 bright cyan */
+	0x00ff5555, /* 12 bright red */
+	0x00ff55ff, /* 13 bright magenta */
+	0x00ffff55, /* 14 yellow */
+	0x00ffffff  /* 15 white */
+};
+
+static void
+drm_vtcon_render_char(struct drm_vtcon *vtcon, unsigned int x, unsigned int y,
+		      u16 cc, const struct font_desc *font)
+{
+	struct drm_client_buffer *buffer = vtcon->buffer;
+	unsigned int h, w;
+	const u8 *src;
+	void *dst;
+	u32 *pix;
+	u32 fg_col = drm_vtcon_paletteX888[(cc & 0x0f00) >> 8];
+	u32 bg_col = drm_vtcon_paletteX888[cc >> 12];
+
+	src = font->data + (cc & 0xff) * font->height;
+	dst = vtcon->buffer->vaddr + y * buffer->pitch + x * sizeof(u32);
+
+	for (h = 0; h < font->height; h++) {
+		u8 fontline = *(src + h);
+
+		pix = dst;
+		for (w = 0; w < font->width; w++)
+			pix[w] = fontline & BIT(7 - w) ? fg_col : bg_col;
+		dst += buffer->pitch;
+	}
+}
+
+static int drm_vtcon_modeset(struct drm_vtcon *vtcon, unsigned int cols,
+			     unsigned int rows, const struct font_desc *font)
+{
+	struct drm_client_display *display = vtcon->display;
+	struct drm_client_dev *client = vtcon->client;
+	struct drm_mode_modeinfo use_mode, *mode, *best_mode = NULL;
+	unsigned int best_cols = ~0, best_rows = ~0;
+	struct drm_client_buffer *buffer;
+	int ret;
+
+	DRM_DEV_INFO(client->dev->dev, "IN %ux%u\n", cols, rows);
+
+	/* Find the smallest mode that fits */
+	mutex_lock(&display->modes_lock);
+	drm_client_display_for_each_mode(display, mode) {
+		unsigned int mode_cols = mode->hdisplay / font->width;
+		unsigned int mode_rows = mode->vdisplay / font->height;
+
+		DRM_DEV_INFO(client->dev->dev,
+			     "try: %ux%u\n", mode_cols, mode_rows);
+
+		if (mode_cols < cols || mode_rows < rows)
+			break;
+
+		if (mode_cols >= best_cols || mode_rows >= best_rows)
+			continue;
+
+		best_cols = mode_cols;
+		best_rows = mode_rows;
+		best_mode = mode;
+	}
+	if (best_mode)
+		use_mode = *best_mode;
+	mutex_unlock(&display->modes_lock);
+
+	if (!best_mode) {
+		DRM_DEV_ERROR(client->dev->dev,
+			      "Couldn't find mode for %ux%u\n", cols, rows);
+		return -EINVAL;
+	}
+
+	DRM_DEV_INFO(client->dev->dev,
+		     "Chosen: %ux%u\n", best_cols, best_rows);
+
+	buffer = drm_client_framebuffer_create(client, &use_mode,
+					       DRM_FORMAT_XRGB8888);
+	DRM_DEV_INFO(client->dev->dev, "buffer=%p\n", buffer);
+	if (IS_ERR(buffer)) {
+		DRM_DEV_ERROR(client->dev->dev,
+			      "Failed to create framebuffer: %d\n", ret);
+		return PTR_ERR(buffer);
+	}
+
+	ret = drm_client_display_commit_mode(display, buffer->fb_ids[0],
+					     &use_mode);
+	DRM_DEV_INFO(client->dev->dev, "commit ret=%d\n", ret);
+	if (ret) {
+		DRM_DEV_ERROR(client->dev->dev,
+			      "Failed to commit mode: %d\n", ret);
+		goto err_free_buffer;
+	}
+
+	drm_client_framebuffer_delete(vtcon->buffer);
+
+	vtcon->buffer = buffer;
+	vtcon->cols = cols;
+	vtcon->rows = rows;
+
+	DRM_DEV_INFO(client->dev->dev, "OUT\n");
+	return 0;
+
+err_free_buffer:
+	drm_client_framebuffer_delete(buffer);
+
+	DRM_DEV_INFO(client->dev->dev, "OUT ret=%d\n", ret);
+	return ret;
+}
+
+static void drm_vtcon_blank(struct drm_vtcon *vtcon, bool blank)
+{
+	int ret;
+
+	if (blank)
+		ret = drm_client_display_dpms(vtcon->display, DRM_MODE_DPMS_OFF);
+	else
+		ret = drm_client_display_dpms(vtcon->display, DRM_MODE_DPMS_ON);
+	if (ret)
+		DRM_DEBUG_KMS("Error %sblanking display: %d\n",
+			      blank ? "" : "un", ret);
+
+	vtcon->blank = blank;
+}
+
+static int drm_vtcon_resize_buf(struct drm_vtcon *vtcon, size_t len)
+{
+	u16 *text_buf[2];
+
+	text_buf[0] = kzalloc(len, GFP_KERNEL);
+	text_buf[1] = kzalloc(len, GFP_KERNEL);
+	if (!text_buf[0] || !text_buf[1]) {
+		kfree(text_buf[0]);
+		kfree(text_buf[1]);
+		return -ENOMEM;
+	}
+
+	kfree(vtcon->text_buf[0]);
+	kfree(vtcon->text_buf[1]);
+
+	vtcon->text_buf[0] = text_buf[0];
+	vtcon->text_buf[1] = text_buf[1];
+	vtcon->buf_len = len;
+
+	return 0;
+}
+
+static void drm_vtcon_work_fn(struct work_struct *work)
+{
+	struct drm_vtcon *vtcon = vtcon_instance;
+	unsigned int vc_cols, vc_rows, col, row, x, y;
+	const struct font_desc *font;
+	u16 prev, curr;
+	bool blank, render_all = false;
+	struct drm_clip_rect clip = {
+		.x1 = ~0,
+		.y1 = ~0,
+		.x2 = 0,
+		.y2 = 0,
+	};
+	char *str;
+	int ret;
+
+	if (!vtcon->display)
+		return;
+
+	mutex_lock(&drm_vtcon_vc->lock);
+
+	vc_cols = drm_vtcon_vc->cols;
+	vc_rows = drm_vtcon_vc->rows;
+	font = drm_vtcon_vc->font;
+	blank = drm_vtcon_vc->blank;
+
+	if (vtcon->buf_len != drm_vtcon_vc->buf_len) {
+		ret = drm_vtcon_resize_buf(vtcon, drm_vtcon_vc->buf_len);
+		if (ret) {
+			mutex_unlock(&drm_vtcon_vc->lock);
+			return;
+		}
+		render_all = true;
+	}
+
+	vtcon->buf_idx = !vtcon->buf_idx;
+	memcpy(vtcon->text_buf[vtcon->buf_idx], drm_vtcon_vc->text_buf,
+	       vc_cols * vc_rows * sizeof(u16));
+
+	mutex_unlock(&drm_vtcon_vc->lock);
+
+	if (vtcon->cols != vc_cols || vtcon->rows != vc_rows) {
+		ret = drm_vtcon_modeset(vtcon, vc_cols, vc_rows, font);
+		if (ret)
+			return;
+		render_all = true;
+	} else if (vtcon->blank != blank) {
+		drm_vtcon_blank(vtcon, blank);
+	}
+
+	str = kmalloc(vc_cols + 1, GFP_KERNEL);
+	if (!str)
+		return;
+
+	for (row = 0; row < vc_rows; row++) {
+		for (col = 0; col < vc_cols; col++) {
+			prev = vtcon->text_buf[!vtcon->buf_idx][col + (row * vc_cols)];
+			curr = vtcon->text_buf[vtcon->buf_idx][col + (row * vc_cols)];
+
+			if (render_all || prev != curr) {
+				str[col] = curr;
+				x = col * font->width;
+				y = row * font->height;
+
+				clip.x1 = min_t(u32, clip.x1, x);
+				clip.y1 = min_t(u32, clip.y1, y);
+				clip.x2 = max_t(u32, clip.x2, x + font->width);
+				clip.y2 = max_t(u32, clip.y2, y + font->height);
+
+				drm_vtcon_render_char(vtcon, x, y, curr, font);
+			} else {
+				str[col] = ' ';
+			}
+		}
+		str[vc_cols] = '\0';
+//		pr_info("%02u|%s\n", row, str);
+	}
+
+	kfree(str);
+
+	if (clip.x1 < clip.x2)
+		drm_client_display_flush(vtcon->display, vtcon->buffer->fb_ids[0],
+					 &clip, 1);
+}
+
+static DECLARE_WORK(drm_vtcon_work, drm_vtcon_work_fn);
+
+static const char *drm_vtcon_con_startup(void)
+{
+	return "drm-vt";
+}
+
+static void drm_vtcon_con_init(struct vc_data *vc, int init)
+{
+	drm_vtcon_debug(vc, "(init=%d) drm_vtcon_vc=%p\n", init, drm_vtcon_vc);
+
+	vc->vc_can_do_color = 1;
+
+	if (init) {
+		vc->vc_cols = drm_vtcon_vc->cols;
+		vc->vc_rows = drm_vtcon_vc->rows;
+	} else {
+		vc_resize(vc, drm_vtcon_vc->cols, drm_vtcon_vc->rows);
+	}
+}
+
+static void drm_vtcon_con_deinit(struct vc_data *vc)
+{
+	drm_vtcon_debug(vc, "\n");
+}
+
+static void drm_vtcon_con_putcs(struct vc_data *vc, const unsigned short *s,
+				int count, int y, int x)
+{
+	u16 *dest;
+
+	dest = &drm_vtcon_vc->text_buf[x + (y * drm_vtcon_vc->cols)];
+
+	for (; count > 0; count--)
+		scr_writew(scr_readw(s++), dest++);
+
+	schedule_work(&drm_vtcon_work);
+}
+
+static void drm_vtcon_con_putc(struct vc_data *vc, int ch, int y, int x)
+{
+	unsigned short chr;
+
+	scr_writew(ch, &chr);
+	drm_vtcon_con_putcs(vc, &chr, 1, y, x);
+}
+
+/* TODO: How do I actually test this? */
+static void drm_vtcon_con_clear(struct vc_data *vc, int y, int x,
+				int height, int width)
+{
+	drm_vtcon_debug(vc, "\n");
+
+	scr_memcpyw(drm_vtcon_vc->text_buf, (unsigned short *)vc->vc_pos,
+		    vc->vc_cols * vc->vc_rows);
+	schedule_work(&drm_vtcon_work);
+}
+
+static int drm_vtcon_con_switch(struct vc_data *vc)
+{
+	drm_vtcon_debug(vc, "%ux%u\n", vc->vc_cols, vc->vc_rows);
+
+	mutex_lock(&drm_vtcon_vc->lock);
+	drm_vtcon_vc->cols = vc->vc_cols;
+	drm_vtcon_vc->rows = vc->vc_rows;
+	mutex_unlock(&drm_vtcon_vc->lock);
+
+	return 1; /* redraw */
+}
+
+static int drm_vtcon_con_resize(struct vc_data *vc, unsigned int width,
+				unsigned int height, unsigned int user)
+{
+	int ret = 0;
+
+	drm_vtcon_debug(vc, "width=%u, height=%u, user=%u\n",
+			width, height, user);
+
+	mutex_lock(&drm_vtcon_vc->lock);
+
+	if (width > drm_vtcon_vc->max_cols || height > drm_vtcon_vc->max_rows)
+		ret = -EINVAL;
+
+	mutex_unlock(&drm_vtcon_vc->lock);
+
+	drm_vtcon_debug(vc, "ret=%d\n", ret);
+
+	return ret;
+}
+
+static void drm_vtcon_con_set_palette(struct vc_data *vc,
+				      const unsigned char *table)
+{
+	drm_vtcon_debug(vc, "\n");
+}
+
+static int drm_vtcon_con_blank(struct vc_data *vc, int blank, int mode_switch)
+{
+	drm_vtcon_debug(vc, "(blank=%d, mode_switch=%d)\n", blank, mode_switch);
+
+	mutex_lock(&drm_vtcon_vc->lock);
+	drm_vtcon_vc->blank = blank;
+	mutex_unlock(&drm_vtcon_vc->lock);
+
+	schedule_work(&drm_vtcon_work);
+
+	return 0;
+}
+
+static void drm_vtcon_con_scrolldelta(struct vc_data *vc, int lines)
+{
+	drm_vtcon_debug(vc, "(lines=%d)\n", lines);
+}
+
+static void drm_vtcon_con_cursor_draw(bool show)
+{
+	static unsigned short set_chr, saved_chr;
+	static int prev_x, prev_y;
+	struct vc_data *vc = vc_cons[fg_console].d;
+	unsigned short *pos;
+
+	if (saved_chr) {
+		pos = &drm_vtcon_vc->text_buf[prev_x + (prev_y * drm_vtcon_vc->cols)];
+		if (*pos == set_chr)
+			*pos = saved_chr;
+		saved_chr = 0;
+	}
+
+	if (show) {
+		pos = &drm_vtcon_vc->text_buf[vc->vc_x + (vc->vc_y * drm_vtcon_vc->cols)];
+		*pos = scr_readw((u16 *)vc->vc_pos);
+		saved_chr = *pos;
+		set_chr = *pos & 0xff00;
+		set_chr |= '_';
+		*pos = set_chr;
+		prev_x = vc->vc_x;
+		prev_y = vc->vc_y;
+	}
+
+	schedule_work(&drm_vtcon_work);
+}
+
+static void drm_vtcon_con_cursor_timer_handler(struct timer_list *t)
+{
+	static bool show;
+
+	show = !show;
+	drm_vtcon_con_cursor_draw(show);
+	mod_timer(t, jiffies + drm_vtcon_vc->cursor_blink_jiffies);
+}
+DEFINE_TIMER(drm_vtcon_con_cursor_timer, drm_vtcon_con_cursor_timer_handler);
+
+static void drm_vtcon_con_cursor(struct vc_data *vc, int mode)
+{
+	//drm_vtcon_debug(vc, "(mode=%d)\n", mode);
+
+	switch (mode) {
+	case CM_ERASE:
+		drm_vtcon_con_cursor_draw(false);
+		del_timer_sync(&drm_vtcon_con_cursor_timer);
+		break;
+	case CM_MOVE:
+	case CM_DRAW:
+		drm_vtcon_vc->cursor_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
+		mod_timer(&drm_vtcon_con_cursor_timer,
+			  jiffies + drm_vtcon_vc->cursor_blink_jiffies);
+		break;
+	}
+}
+
+static bool drm_vtcon_con_scroll(struct vc_data *vc, unsigned int top,
+				 unsigned int bottom, enum con_scroll dir,
+				 unsigned int lines)
+{
+	size_t count;
+
+	switch (dir) {
+	case SM_UP:
+		count = vc->vc_cols * (vc->vc_rows - lines);
+		memmove(drm_vtcon_vc->text_buf, drm_vtcon_vc->text_buf + vc->vc_cols, count * sizeof(u16));
+		memset(drm_vtcon_vc->text_buf + count, 0, vc->vc_cols * lines * sizeof(u16));
+		break;
+	case SM_DOWN:
+		drm_vtcon_debug(vc, "TODO\n");
+		break;
+	}
+
+	return false;
+}
+
+static const struct consw drm_vtcon_consw = {
+	.owner			= THIS_MODULE,
+	.con_startup		= drm_vtcon_con_startup,
+	.con_init		= drm_vtcon_con_init,
+	.con_deinit		= drm_vtcon_con_deinit,
+	.con_clear		= drm_vtcon_con_clear,
+	.con_putc		= drm_vtcon_con_putc,
+	.con_putcs		= drm_vtcon_con_putcs,
+	.con_cursor		= drm_vtcon_con_cursor,
+	.con_scroll		= drm_vtcon_con_scroll,
+	.con_switch		= drm_vtcon_con_switch,
+	.con_blank		= drm_vtcon_con_blank,
+	.con_resize		= drm_vtcon_con_resize,
+	.con_set_palette	= drm_vtcon_con_set_palette,
+	.con_scrolldelta	= drm_vtcon_con_scrolldelta,
+};
+
+static int drm_vtcon_vc_set_max(struct drm_mode_modeinfo *mode)
+{
+	u16 *new_buf = NULL, *old_buf = NULL;
+	const struct font_desc *font;
+	unsigned int cols, rows, i;
+	size_t buf_len;
+
+	/* only 8 bit wide and 8 or 16-bit high */
+	font = get_default_font(mode->hdisplay, mode->vdisplay,
+				BIT(8 - 1), BIT(8 - 1) | BIT(16 - 1));
+	if (!font)
+		return -ENODEV;
+
+	DRM_INFO("font: %s\n", font->name);
+
+	cols = mode->hdisplay / font->width;
+	rows = mode->vdisplay / font->height;
+
+	DRM_INFO("ASKED: cols=%zu, rows=%zu\n", cols, rows);
+
+	if (drm_vtcon_vc->max_cols == cols && drm_vtcon_vc->max_rows == rows &&
+	    drm_vtcon_vc->font == font)
+		return 0;
+
+	buf_len = cols * rows * sizeof(u16);
+	if (buf_len > drm_vtcon_vc->buf_len) {
+		new_buf = kzalloc(buf_len, GFP_KERNEL);
+		if (!new_buf)
+			return -ENOMEM;
+	}
+
+	mutex_lock(&drm_vtcon_vc->lock);
+
+	if (new_buf) {
+		DRM_INFO("Allocated new buf: buf_len=%zu\n", buf_len);
+		old_buf = drm_vtcon_vc->text_buf;
+		drm_vtcon_vc->text_buf = new_buf;
+		drm_vtcon_vc->buf_len = buf_len;
+	}
+
+	drm_vtcon_vc->max_cols = cols;
+	drm_vtcon_vc->max_rows = rows;
+	drm_vtcon_vc->font = font;
+
+	mutex_unlock(&drm_vtcon_vc->lock);
+
+	DRM_INFO("max_cols=%u, max_rows=%u\n", drm_vtcon_vc->max_cols,
+		 drm_vtcon_vc->max_rows);
+
+	console_lock();
+	for (i = 0; i < 2; i++)
+		vc_resize(vc_cons[i].d, drm_vtcon_vc->max_cols,
+			  drm_vtcon_vc->max_rows);
+	console_unlock();
+
+	kfree(old_buf);
+
+	return 0;
+}
+
+static int drm_vtcon_setup_dev(struct drm_vtcon *vtcon)
+{
+	struct drm_client_dev *client = vtcon->client;
+	struct drm_client_display *display;
+	struct drm_mode_modeinfo *mode;
+	int ret;
+
+	display = drm_client_display_get_first_enabled(client, false);
+	if (IS_ERR(display))
+		return PTR_ERR(display);
+	if (!display)
+		return -ENOENT;
+
+	mode = drm_client_display_first_mode(display);
+	if (!mode) {
+		ret = -EINVAL;
+		goto err_free_display;
+	}
+
+	ret = drm_vtcon_vc_set_max(mode);
+	if (ret)
+		goto err_free_display;
+
+	DRM_INFO("cols=%zu, rows=%zu\n", drm_vtcon_vc->cols, drm_vtcon_vc->rows);
+
+	vtcon->display = display;
+
+	schedule_work(&drm_vtcon_work);
+
+	return 0;
+
+err_free_display:
+	drm_client_display_free(display);
+
+	return ret;
+}
+
+static int drm_vtcon_client_hotplug(struct drm_client_dev *client)
+{
+	struct drm_vtcon *vtcon = client->private;
+	int ret = 0;
+
+	if (!vtcon->display)
+		ret = drm_vtcon_setup_dev(vtcon);
+
+	return ret;
+}
+
+static int drm_vtcon_client_new(struct drm_client_dev *client)
+{
+	struct drm_vtcon *vtcon = vtcon_instance;
+
+	if (vtcon->client) {
+		DRM_DEV_INFO(client->dev->dev, "Console is taken\n");
+		return -EBUSY;
+	}
+
+	vtcon->client = client;
+	client->private = vtcon;
+
+	/*
+	 * vc4 isn't done with it's setup when drm_dev_register() is called.
+	 * It should have shouldn't it?
+	 * So to keep it from crashing defer setup to hotplug...
+	 */
+	if (client->dev->mode_config.max_width)
+		drm_vtcon_client_hotplug(client);
+
+	return 0;
+}
+
+static int drm_vtcon_client_remove(struct drm_client_dev *client)
+{
+	struct drm_vtcon *vtcon = client->private;
+
+	if (vtcon->display) {
+		flush_work(&drm_vtcon_work);
+		kfree(vtcon->text_buf[0]);
+		kfree(vtcon->text_buf[1]);
+		drm_client_framebuffer_delete(vtcon->buffer);
+		drm_client_display_free(vtcon->display);
+	}
+
+	return 0;
+}
+
+static const struct drm_client_funcs drm_vtcon_client_funcs = {
+	.name		= "drm_vtcon",
+	.new		= drm_vtcon_client_new,
+	.remove		= drm_vtcon_client_remove,
+	.hotplug	= drm_vtcon_client_hotplug,
+};
+
+static void drm_vtcon_teardown(void)
+{
+	struct drm_vtcon *vtcon = vtcon_instance;
+
+	if (!drm_vtcon_vc)
+		return;
+
+	kfree(drm_vtcon_vc->text_buf);
+	mutex_destroy(&drm_vtcon_vc->lock);
+	kfree(drm_vtcon_vc);
+	kfree(vtcon);
+}
+
+static int __init drm_vtcon_setup(void)
+{
+	struct drm_vtcon *vtcon;
+
+	drm_vtcon_vc = kzalloc(sizeof(*drm_vtcon_vc), GFP_KERNEL);
+	vtcon = kzalloc(sizeof(*vtcon), GFP_KERNEL);
+	if (!drm_vtcon_vc || !vtcon)
+		goto err_free;
+
+	drm_vtcon_vc->cols = drm_vtcon_vc->max_cols = 80;
+	drm_vtcon_vc->rows = drm_vtcon_vc->max_rows = 25;
+	DRM_INFO("cols=%zu, rows=%zu\n", drm_vtcon_vc->cols, drm_vtcon_vc->rows);
+
+	drm_vtcon_vc->buf_len = drm_vtcon_vc->max_cols * drm_vtcon_vc->max_rows * sizeof(u16);
+	drm_vtcon_vc->text_buf = kzalloc(drm_vtcon_vc->buf_len, GFP_KERNEL);
+	if (!drm_vtcon_vc->text_buf)
+		goto err_free;
+
+	mutex_init(&drm_vtcon_vc->lock);
+	vtcon_instance = vtcon;
+
+	return 0;
+
+err_free:
+	kfree(drm_vtcon_vc);
+	kfree(vtcon);
+
+	return -ENOMEM;
+}
+
+static int __init drm_vtcon_module_init(void)
+{
+	int ret;
+
+	ret = drm_vtcon_setup();
+	if (ret)
+		return ret;
+
+	console_lock();
+	ret = do_take_over_console(&drm_vtcon_consw, 0, 1, 0);
+	console_unlock();
+	if (ret)
+		goto err_free;
+
+	ret = drm_client_register(&drm_vtcon_client_funcs);
+	if (ret)
+		goto err_give_up;
+
+	return 0;
+
+err_give_up:
+	give_up_console(&drm_vtcon_consw);
+err_free:
+	drm_vtcon_teardown();
+
+	return ret;
+}
+module_init(drm_vtcon_module_init);
+
+static void __exit drm_vtcon_module_exit(void)
+{
+	drm_client_unregister(&drm_vtcon_client_funcs);
+	give_up_console(&drm_vtcon_consw);
+	drm_vtcon_teardown();
+}
+module_exit(drm_vtcon_module_exit);
+
+MODULE_LICENSE("GPL");
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* ✗ Fi.CI.BAT: failure for drm: Add generic fbdev emulation (rev3)
  2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
                   ` (11 preceding siblings ...)
  2018-02-22 20:06 ` [RFC v3 12/12] drm/client: Add VT console client Noralf Trønnes
@ 2018-02-22 21:03 ` Patchwork
  12 siblings, 0 replies; 26+ messages in thread
From: Patchwork @ 2018-02-22 21:03 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: intel-gfx

== Series Details ==

Series: drm: Add generic fbdev emulation (rev3)
URL   : https://patchwork.freedesktop.org/series/35873/
State : failure

== Summary ==

  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
  CHK     include/generated/bounds.h
  CHK     include/generated/timeconst.h
  CHK     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  DESCEND  objtool
  CHK     scripts/mod/devicetable-offsets.h
  CHK     include/generated/compile.h
  CHK     kernel/config_data.h
  CC      drivers/gpu/drm/client/drm_client.o
drivers/gpu/drm/client/drm_client.c:17:31: fatal error: drm_crtc_internal.h: No such file or directory
 #include "drm_crtc_internal.h"
                               ^
compilation terminated.
scripts/Makefile.build:316: recipe for target 'drivers/gpu/drm/client/drm_client.o' failed
make[3]: *** [drivers/gpu/drm/client/drm_client.o] Error 1
scripts/Makefile.build:575: recipe for target 'drivers/gpu/drm' failed
make[2]: *** [drivers/gpu/drm] Error 2
scripts/Makefile.build:575: recipe for target 'drivers/gpu' failed
make[1]: *** [drivers/gpu] Error 2
Makefile:1048: recipe for target 'drivers' failed
make: *** [drivers] Error 2

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

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

* Re: [RFC v3 02/12] drm/file: Don't set master on in-kernel clients
  2018-02-22 20:06 ` [RFC v3 02/12] drm/file: Don't set master on in-kernel clients Noralf Trønnes
@ 2018-03-06  8:28   ` Daniel Vetter
  0 siblings, 0 replies; 26+ messages in thread
From: Daniel Vetter @ 2018-03-06  8:28 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart

On Thu, Feb 22, 2018 at 09:06:43PM +0100, Noralf Trønnes wrote:
> It only makes sense for userspace clients.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

I think we might be able to reuse some of the master stuff to better
handle hand-over between in-kernel clients and userspace masters for kms.
But this definitely makes sense.

Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>

> ---
>  drivers/gpu/drm/drm_file.c | 18 +++++++++---------
>  1 file changed, 9 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
> index d4588d33f91c..55505378df47 100644
> --- a/drivers/gpu/drm/drm_file.c
> +++ b/drivers/gpu/drm/drm_file.c
> @@ -155,17 +155,8 @@ struct drm_file *drm_file_alloc(struct drm_minor *minor)
>  			goto out_prime_destroy;
>  	}
>  
> -	if (drm_is_primary_client(file)) {
> -		ret = drm_master_open(file);
> -		if (ret)
> -			goto out_close;
> -	}
> -
>  	return file;
>  
> -out_close:
> -	if (dev->driver->postclose)
> -		dev->driver->postclose(dev, file);
>  out_prime_destroy:
>  	if (drm_core_check_feature(dev, DRIVER_PRIME))
>  		drm_prime_destroy_file_private(&file->prime);
> @@ -365,6 +356,7 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
>  {
>  	struct drm_device *dev = minor->dev;
>  	struct drm_file *priv;
> +	int ret;
>  
>  	if (filp->f_flags & O_EXCL)
>  		return -EBUSY;	/* No exclusive opens */
> @@ -379,6 +371,14 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
>  	if (IS_ERR(priv))
>  		return PTR_ERR(priv);
>  
> +	if (drm_is_primary_client(priv)) {
> +		ret = drm_master_open(priv);
> +		if (ret) {
> +			drm_file_free(priv);
> +			return ret;
> +		}
> +	}
> +
>  	filp->private_data = priv;
>  	priv->filp = filp;
>  
> -- 
> 2.15.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v3 04/12] drm: Make ioctls available for in-kernel clients part 2
  2018-02-22 20:06 ` [RFC v3 04/12] drm: Make ioctls available for in-kernel clients part 2 Noralf Trønnes
@ 2018-03-06  8:41   ` Daniel Vetter
  0 siblings, 0 replies; 26+ messages in thread
From: Daniel Vetter @ 2018-03-06  8:41 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart, dh.herrmann

On Thu, Feb 22, 2018 at 09:06:45PM +0100, Noralf Trønnes wrote:
> This is part 2 of making ioctls useable for in-kernel clients.
> Make an ioctl wrapper function that calls a function that can be used by
> in-kernel clients.
> 
> It adjusts the signature of the following functions:
> - drm_mode_getcrtc()
> - drm_mode_create_dumb_ioctl()
> - drm_mode_destroy_dumb_ioctl()
> - drm_mode_getencoder()
> - drm_mode_addfb2()
> - drm_mode_rmfb()
> - drm_mode_obj_set_property_ioctl()
> - drm_mode_page_flip_ioctl()
> - drm_prime_handle_to_fd_ioctl()
> - drm_wait_vblank_ioctl()
> 
> drm_mode_addfb2() also gets the ability to override the debug name.
> 
> There is no functional change from the userspace side.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Do we really need all of these? For in-kernel KMS users I think it would
be much better to keep using the in-kernel KMS interfaces directly (like
the current fbdev emulation code does). Doing all the marshalling and
demarshalling for the generic KMS ioctls looks like lots of fragile code.

I think the only ioctl we really absolutely need are:
- dumb_create/mmap_offset/destroy
- addfb2

As soon as we've called addfb2 we can use the idr lookup to go from the id
to the drm_framebuffer * (including a full reference), and once we have
the drm_framebuffer we don't need any of the other ioctls. Or am I missing
something? Maybe the addfb wrapper for internal clients could even
directly convert to the drm_framebuffer * and never expose the KMS ID.
-Daniel


> ---
>  drivers/gpu/drm/drm_crtc.c          | 15 +++++++----
>  drivers/gpu/drm/drm_crtc_internal.h | 37 +++++++++++++++++++++------
>  drivers/gpu/drm/drm_dumb_buffers.c  | 33 ++++++++++++++++--------
>  drivers/gpu/drm/drm_encoder.c       | 10 ++++++--
>  drivers/gpu/drm/drm_framebuffer.c   | 50 ++++++++++++++++++++++++-------------
>  drivers/gpu/drm/drm_internal.h      |  5 ++++
>  drivers/gpu/drm/drm_ioc32.c         |  2 +-
>  drivers/gpu/drm/drm_ioctl.c         |  8 +++---
>  drivers/gpu/drm/drm_mode_object.c   | 12 ++++++---
>  drivers/gpu/drm/drm_plane.c         | 12 ++++++---
>  drivers/gpu/drm/drm_prime.c         | 13 +++++++---
>  drivers/gpu/drm/drm_vblank.c        | 11 +++++---
>  12 files changed, 147 insertions(+), 61 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
> index c9ab1cc6b412..61a6a90fae7e 100644
> --- a/drivers/gpu/drm/drm_crtc.c
> +++ b/drivers/gpu/drm/drm_crtc.c
> @@ -387,8 +387,8 @@ EXPORT_SYMBOL(drm_crtc_cleanup);
>  /**
>   * drm_mode_getcrtc - get CRTC configuration
>   * @dev: drm device for the ioctl
> - * @data: data pointer for the ioctl
> - * @file_priv: drm file for the ioctl call
> + * @crtc_resp: pointer to crtc request structure
> + * @file_priv: drm file
>   *
>   * Construct a CRTC configuration structure to return to the user.
>   *
> @@ -397,10 +397,9 @@ EXPORT_SYMBOL(drm_crtc_cleanup);
>   * Returns:
>   * Zero on success, negative errno on failure.
>   */
> -int drm_mode_getcrtc(struct drm_device *dev,
> -		     void *data, struct drm_file *file_priv)
> +int drm_mode_getcrtc(struct drm_device *dev, struct drm_mode_crtc *crtc_resp,
> +		     struct drm_file *file_priv)
>  {
> -	struct drm_mode_crtc *crtc_resp = data;
>  	struct drm_crtc *crtc;
>  
>  	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> @@ -451,6 +450,12 @@ int drm_mode_getcrtc(struct drm_device *dev,
>  	return 0;
>  }
>  
> +int drm_mode_getcrtc_ioctl(struct drm_device *dev,
> +			   void *data, struct drm_file *file_priv)
> +{
> +	return drm_mode_getcrtc(dev, data, file_priv);
> +}
> +
>  static int __drm_mode_set_config_internal(struct drm_mode_set *set,
>  					  struct drm_modeset_acquire_ctx *ctx)
>  {
> diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h
> index 29c59ce7e56e..45713af5a015 100644
> --- a/drivers/gpu/drm/drm_crtc_internal.h
> +++ b/drivers/gpu/drm/drm_crtc_internal.h
> @@ -45,12 +45,14 @@ void drm_crtc_unregister_all(struct drm_device *dev);
>  
>  struct dma_fence *drm_crtc_create_fence(struct drm_crtc *crtc);
>  
> +int drm_mode_getcrtc(struct drm_device *dev, struct drm_mode_crtc *crtc_resp,
> +		     struct drm_file *file_priv);
>  int drm_mode_setcrtc(struct drm_device *dev, struct drm_mode_crtc *crtc_req,
>  		     struct drm_file *file_priv, bool user);
>  
>  /* IOCTLs */
> -int drm_mode_getcrtc(struct drm_device *dev,
> -		     void *data, struct drm_file *file_priv);
> +int drm_mode_getcrtc_ioctl(struct drm_device *dev,
> +			   void *data, struct drm_file *file_priv);
>  int drm_mode_setcrtc_ioctl(struct drm_device *dev,
>  			   void *data, struct drm_file *file_priv);
>  
> @@ -68,6 +70,12 @@ int drm_mode_getresources_ioctl(struct drm_device *dev, void *data,
>  
>  
>  /* drm_dumb_buffers.c */
> +int drm_mode_create_dumb(struct drm_device *dev,
> +			 struct drm_mode_create_dumb *args,
> +			 struct drm_file *file_priv);
> +int drm_mode_destroy_dumb(struct drm_device *dev, u32 handle,
> +			  struct drm_file *file_priv);
> +
>  /* IOCTLs */
>  int drm_mode_create_dumb_ioctl(struct drm_device *dev,
>  			       void *data, struct drm_file *file_priv);
> @@ -122,6 +130,9 @@ int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
>  				   uint32_t *arg_count_props);
>  struct drm_property *drm_mode_obj_find_prop_id(struct drm_mode_object *obj,
>  					       uint32_t prop_id);
> +int drm_mode_obj_set_property(struct drm_device *dev,
> +			      struct drm_mode_obj_set_property *arg,
> +			      struct drm_file *file_priv);
>  
>  /* IOCTL */
>  
> @@ -133,10 +144,13 @@ int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
>  /* drm_encoder.c */
>  int drm_encoder_register_all(struct drm_device *dev);
>  void drm_encoder_unregister_all(struct drm_device *dev);
> +int drm_mode_getencoder(struct drm_device *dev,
> +			struct drm_mode_get_encoder *enc_resp,
> +			struct drm_file *file_priv);
>  
>  /* IOCTL */
> -int drm_mode_getencoder(struct drm_device *dev,
> -			void *data, struct drm_file *file_priv);
> +int drm_mode_getencoder_ioctl(struct drm_device *dev,
> +			      void *data, struct drm_file *file_priv);
>  
>  /* drm_connector.c */
>  void drm_connector_ida_init(void);
> @@ -170,16 +184,20 @@ int drm_framebuffer_check_src_coords(uint32_t src_x, uint32_t src_y,
>  				     const struct drm_framebuffer *fb);
>  void drm_fb_release(struct drm_file *file_priv);
>  
> +int drm_mode_addfb2(struct drm_device *dev, struct drm_mode_fb_cmd2 *r,
> +		    struct drm_file *file_priv, const char *comm);
> +int drm_mode_rmfb(struct drm_device *dev, u32 fb_id,
> +		  struct drm_file *file_priv);
>  int drm_mode_dirtyfb(struct drm_device *dev, struct drm_mode_fb_dirty_cmd *req,
>  		     struct drm_file *file_priv, bool user);
>  
>  /* IOCTL */
>  int drm_mode_addfb(struct drm_device *dev,
>  		   void *data, struct drm_file *file_priv);
> -int drm_mode_addfb2(struct drm_device *dev,
> -		    void *data, struct drm_file *file_priv);
> -int drm_mode_rmfb(struct drm_device *dev,
> -		  void *data, struct drm_file *file_priv);
> +int drm_mode_addfb2_ioctl(struct drm_device *dev,
> +			  void *data, struct drm_file *file_priv);
> +int drm_mode_rmfb_ioctl(struct drm_device *dev,
> +			void *data, struct drm_file *file_priv);
>  int drm_mode_getfb(struct drm_device *dev,
>  		   void *data, struct drm_file *file_priv);
>  int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
> @@ -209,6 +227,9 @@ int drm_plane_register_all(struct drm_device *dev);
>  void drm_plane_unregister_all(struct drm_device *dev);
>  int drm_plane_check_pixel_format(const struct drm_plane *plane,
>  				 u32 format);
> +int drm_mode_page_flip(struct drm_device *dev,
> +		       struct drm_mode_crtc_page_flip_target *page_flip,
> +		       struct drm_file *file_priv);
>  
>  /* drm_bridge.c */
>  void drm_bridge_detach(struct drm_bridge *bridge);
> diff --git a/drivers/gpu/drm/drm_dumb_buffers.c b/drivers/gpu/drm/drm_dumb_buffers.c
> index 39ac15ce4702..eed9687b8698 100644
> --- a/drivers/gpu/drm/drm_dumb_buffers.c
> +++ b/drivers/gpu/drm/drm_dumb_buffers.c
> @@ -53,10 +53,10 @@
>   * a hardware-specific ioctl to allocate suitable buffer objects.
>   */
>  
> -int drm_mode_create_dumb_ioctl(struct drm_device *dev,
> -			       void *data, struct drm_file *file_priv)
> +int drm_mode_create_dumb(struct drm_device *dev,
> +			 struct drm_mode_create_dumb *args,
> +			 struct drm_file *file_priv)
>  {
> -	struct drm_mode_create_dumb *args = data;
>  	u32 cpp, stride, size;
>  
>  	if (!dev->driver->dumb_create)
> @@ -91,6 +91,12 @@ int drm_mode_create_dumb_ioctl(struct drm_device *dev,
>  	return dev->driver->dumb_create(file_priv, dev, args);
>  }
>  
> +int drm_mode_create_dumb_ioctl(struct drm_device *dev,
> +			       void *data, struct drm_file *file_priv)
> +{
> +	return drm_mode_create_dumb(dev, data, file_priv);
> +}
> +
>  /**
>   * drm_mode_mmap_dumb_ioctl - create an mmap offset for a dumb backing storage buffer
>   * @dev: DRM device
> @@ -122,17 +128,22 @@ int drm_mode_mmap_dumb_ioctl(struct drm_device *dev,
>  					       &args->offset);
>  }
>  
> +int drm_mode_destroy_dumb(struct drm_device *dev, u32 handle,
> +			  struct drm_file *file_priv)
> +{
> +	if (!dev->driver->dumb_create)
> +		return -ENOSYS;
> +
> +	if (dev->driver->dumb_destroy)
> +		return dev->driver->dumb_destroy(file_priv, dev, handle);
> +	else
> +		return drm_gem_dumb_destroy(file_priv, dev, handle);
> +}
> +
>  int drm_mode_destroy_dumb_ioctl(struct drm_device *dev,
>  				void *data, struct drm_file *file_priv)
>  {
>  	struct drm_mode_destroy_dumb *args = data;
>  
> -	if (!dev->driver->dumb_create)
> -		return -ENOSYS;
> -
> -	if (dev->driver->dumb_destroy)
> -		return dev->driver->dumb_destroy(file_priv, dev, args->handle);
> -	else
> -		return drm_gem_dumb_destroy(file_priv, dev, args->handle);
> +	return drm_mode_destroy_dumb(dev, args->handle, file_priv);
>  }
> -
> diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c
> index 273e1c59c54a..466f3e28b3e9 100644
> --- a/drivers/gpu/drm/drm_encoder.c
> +++ b/drivers/gpu/drm/drm_encoder.c
> @@ -214,10 +214,10 @@ static struct drm_crtc *drm_encoder_get_crtc(struct drm_encoder *encoder)
>  	return encoder->crtc;
>  }
>  
> -int drm_mode_getencoder(struct drm_device *dev, void *data,
> +int drm_mode_getencoder(struct drm_device *dev,
> +			struct drm_mode_get_encoder *enc_resp,
>  			struct drm_file *file_priv)
>  {
> -	struct drm_mode_get_encoder *enc_resp = data;
>  	struct drm_encoder *encoder;
>  	struct drm_crtc *crtc;
>  
> @@ -244,3 +244,9 @@ int drm_mode_getencoder(struct drm_device *dev, void *data,
>  
>  	return 0;
>  }
> +
> +int drm_mode_getencoder_ioctl(struct drm_device *dev, void *data,
> +			      struct drm_file *file_priv)
> +{
> +	return drm_mode_getencoder(dev, data, file_priv);
> +}
> diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c
> index e918c7124dcd..b41770d29e6c 100644
> --- a/drivers/gpu/drm/drm_framebuffer.c
> +++ b/drivers/gpu/drm/drm_framebuffer.c
> @@ -121,7 +121,7 @@ int drm_mode_addfb(struct drm_device *dev,
>  	r.pixel_format = drm_mode_legacy_fb_format(or->bpp, or->depth);
>  	r.handles[0] = or->handle;
>  
> -	ret = drm_mode_addfb2(dev, &r, file_priv);
> +	ret = drm_mode_addfb2_ioctl(dev, &r, file_priv);
>  	if (ret)
>  		return ret;
>  
> @@ -305,23 +305,23 @@ drm_internal_framebuffer_create(struct drm_device *dev,
>  
>  /**
>   * drm_mode_addfb2 - add an FB to the graphics configuration
> - * @dev: drm device for the ioctl
> - * @data: data pointer for the ioctl
> - * @file_priv: drm file for the ioctl call
> + * @dev: drm device
> + * @r: pointer to request structure
> + * @file_priv: drm file
> + * @comm: optionally override the allocator name used for debug output
>   *
>   * Add a new FB to the specified CRTC, given a user request with format. This is
>   * the 2nd version of the addfb ioctl, which supports multi-planar framebuffers
>   * and uses fourcc codes as pixel format specifiers.
>   *
> - * Called by the user via ioctl.
> + * Called by the user via ioctl, or by an in-kernel client.
>   *
>   * Returns:
>   * Zero on success, negative errno on failure.
>   */
> -int drm_mode_addfb2(struct drm_device *dev,
> -		    void *data, struct drm_file *file_priv)
> +int drm_mode_addfb2(struct drm_device *dev, struct drm_mode_fb_cmd2 *r,
> +		    struct drm_file *file_priv, const char *comm)
>  {
> -	struct drm_mode_fb_cmd2 *r = data;
>  	struct drm_framebuffer *fb;
>  
>  	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> @@ -331,6 +331,9 @@ int drm_mode_addfb2(struct drm_device *dev,
>  	if (IS_ERR(fb))
>  		return PTR_ERR(fb);
>  
> +	if (comm)
> +		strscpy(fb->comm, comm, TASK_COMM_LEN);
> +
>  	DRM_DEBUG_KMS("[FB:%d]\n", fb->base.id);
>  	r->fb_id = fb->base.id;
>  
> @@ -342,6 +345,12 @@ int drm_mode_addfb2(struct drm_device *dev,
>  	return 0;
>  }
>  
> +int drm_mode_addfb2_ioctl(struct drm_device *dev,
> +			  void *data, struct drm_file *file_priv)
> +{
> +	return drm_mode_addfb2(dev, data, file_priv, NULL);
> +}
> +
>  struct drm_mode_rmfb_work {
>  	struct work_struct work;
>  	struct list_head fbs;
> @@ -362,29 +371,28 @@ static void drm_mode_rmfb_work_fn(struct work_struct *w)
>  
>  /**
>   * drm_mode_rmfb - remove an FB from the configuration
> - * @dev: drm device for the ioctl
> - * @data: data pointer for the ioctl
> - * @file_priv: drm file for the ioctl call
> + * @dev: drm device
> + * @fb_id: id of framebuffer to remove
> + * @file_priv: drm file
>   *
> - * Remove the FB specified by the user.
> + * Remove the specified FB.
>   *
> - * Called by the user via ioctl.
> + * Called by the user via ioctl, or by an in-kernel client.
>   *
>   * Returns:
>   * Zero on success, negative errno on failure.
>   */
> -int drm_mode_rmfb(struct drm_device *dev,
> -		   void *data, struct drm_file *file_priv)
> +int drm_mode_rmfb(struct drm_device *dev, u32 fb_id,
> +		  struct drm_file *file_priv)
>  {
>  	struct drm_framebuffer *fb = NULL;
>  	struct drm_framebuffer *fbl = NULL;
> -	uint32_t *id = data;
>  	int found = 0;
>  
>  	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>  		return -EINVAL;
>  
> -	fb = drm_framebuffer_lookup(dev, file_priv, *id);
> +	fb = drm_framebuffer_lookup(dev, file_priv, fb_id);
>  	if (!fb)
>  		return -ENOENT;
>  
> @@ -430,6 +438,14 @@ int drm_mode_rmfb(struct drm_device *dev,
>  	return -ENOENT;
>  }
>  
> +int drm_mode_rmfb_ioctl(struct drm_device *dev,
> +			void *data, struct drm_file *file_priv)
> +{
> +	uint32_t *fb_id = data;
> +
> +	return drm_mode_rmfb(dev, *fb_id, file_priv);
> +}
> +
>  /**
>   * drm_mode_getfb - get FB info
>   * @dev: drm device for the ioctl
> diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
> index 40179c5fc6b8..043814cbd286 100644
> --- a/drivers/gpu/drm/drm_internal.h
> +++ b/drivers/gpu/drm/drm_internal.h
> @@ -37,6 +37,9 @@ void drm_pci_agp_destroy(struct drm_device *dev);
>  int drm_pci_set_busid(struct drm_device *dev, struct drm_master *master);
>  
>  /* drm_prime.c */
> +int drm_prime_handle_to_fd(struct drm_device *dev,
> +			   struct drm_prime_handle *args,
> +			   struct drm_file *file_priv);
>  int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
>  				 struct drm_file *file_priv);
>  int drm_prime_fd_to_handle_ioctl(struct drm_device *dev, void *data,
> @@ -59,6 +62,8 @@ int drm_gem_name_info(struct seq_file *m, void *data);
>  /* drm_vblank.c */
>  void drm_vblank_disable_and_save(struct drm_device *dev, unsigned int pipe);
>  void drm_vblank_cleanup(struct drm_device *dev);
> +int drm_wait_vblank(struct drm_device *dev, union drm_wait_vblank *vblwait,
> +		    struct drm_file *file_priv);
>  
>  /* IOCTLS */
>  int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
> diff --git a/drivers/gpu/drm/drm_ioc32.c b/drivers/gpu/drm/drm_ioc32.c
> index f8e96e648acf..576d00b7dad5 100644
> --- a/drivers/gpu/drm/drm_ioc32.c
> +++ b/drivers/gpu/drm/drm_ioc32.c
> @@ -884,7 +884,7 @@ static int compat_drm_mode_addfb2(struct file *file, unsigned int cmd,
>  			   sizeof(req64.modifier)))
>  		return -EFAULT;
>  
> -	err = drm_ioctl_kernel(file, drm_mode_addfb2, &req64,
> +	err = drm_ioctl_kernel(file, drm_mode_addfb2_ioctl, &req64,
>  				DRM_CONTROL_ALLOW|DRM_UNLOCKED);
>  	if (err)
>  		return err;
> diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
> index 346b8060df7c..726fbdb8a4b0 100644
> --- a/drivers/gpu/drm/drm_ioctl.c
> +++ b/drivers/gpu/drm/drm_ioctl.c
> @@ -619,14 +619,14 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
>  	DRM_IOCTL_DEF(DRM_IOCTL_PRIME_FD_TO_HANDLE, drm_prime_fd_to_handle_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW),
>  
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANERESOURCES, drm_mode_getplane_res, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> -	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> +	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANE, drm_mode_getplane, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPLANE, drm_mode_setplane, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETGAMMA, drm_mode_gamma_get_ioctl, DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETGAMMA, drm_mode_gamma_set_ioctl, DRM_MASTER|DRM_UNLOCKED),
> -	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETENCODER, drm_mode_getencoder, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> +	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETENCODER, drm_mode_getencoder_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATTACHMODE, drm_noop, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_DETACHMODE, drm_noop, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> @@ -635,8 +635,8 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPBLOB, drm_mode_getblob_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> -	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> -	DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> +	DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> +	DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_DIRTYFB, drm_mode_dirtyfb_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
>  	DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
> diff --git a/drivers/gpu/drm/drm_mode_object.c b/drivers/gpu/drm/drm_mode_object.c
> index ce4d2fb32810..2bf7a9e8ac08 100644
> --- a/drivers/gpu/drm/drm_mode_object.c
> +++ b/drivers/gpu/drm/drm_mode_object.c
> @@ -496,10 +496,10 @@ static int set_property_atomic(struct drm_mode_object *obj,
>  	return ret;
>  }
>  
> -int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
> -				    struct drm_file *file_priv)
> +int drm_mode_obj_set_property(struct drm_device *dev,
> +			      struct drm_mode_obj_set_property *arg,
> +			      struct drm_file *file_priv)
>  {
> -	struct drm_mode_obj_set_property *arg = data;
>  	struct drm_mode_object *arg_obj;
>  	struct drm_property *property;
>  	int ret = -EINVAL;
> @@ -527,3 +527,9 @@ int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
>  	drm_mode_object_put(arg_obj);
>  	return ret;
>  }
> +
> +int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
> +				    struct drm_file *file_priv)
> +{
> +	return drm_mode_obj_set_property(dev, data, file_priv);
> +}
> diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
> index 22b54663b6e7..b1f55556e196 100644
> --- a/drivers/gpu/drm/drm_plane.c
> +++ b/drivers/gpu/drm/drm_plane.c
> @@ -907,10 +907,10 @@ int drm_mode_cursor2_ioctl(struct drm_device *dev,
>  	return drm_mode_cursor_common(dev, req, file_priv);
>  }
>  
> -int drm_mode_page_flip_ioctl(struct drm_device *dev,
> -			     void *data, struct drm_file *file_priv)
> +int drm_mode_page_flip(struct drm_device *dev,
> +		       struct drm_mode_crtc_page_flip_target *page_flip,
> +		       struct drm_file *file_priv)
>  {
> -	struct drm_mode_crtc_page_flip_target *page_flip = data;
>  	struct drm_crtc *crtc;
>  	struct drm_framebuffer *fb = NULL;
>  	struct drm_pending_vblank_event *e = NULL;
> @@ -1082,3 +1082,9 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
>  
>  	return ret;
>  }
> +
> +int drm_mode_page_flip_ioctl(struct drm_device *dev,
> +			     void *data, struct drm_file *file_priv)
> +{
> +	return drm_mode_page_flip(dev, data, file_priv);
> +}
> diff --git a/drivers/gpu/drm/drm_prime.c b/drivers/gpu/drm/drm_prime.c
> index e82a976f0fba..eaf8392ef815 100644
> --- a/drivers/gpu/drm/drm_prime.c
> +++ b/drivers/gpu/drm/drm_prime.c
> @@ -853,11 +853,10 @@ int drm_gem_prime_fd_to_handle(struct drm_device *dev,
>  }
>  EXPORT_SYMBOL(drm_gem_prime_fd_to_handle);
>  
> -int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
> -				 struct drm_file *file_priv)
> +int drm_prime_handle_to_fd(struct drm_device *dev,
> +			   struct drm_prime_handle *args,
> +			   struct drm_file *file_priv)
>  {
> -	struct drm_prime_handle *args = data;
> -
>  	if (!drm_core_check_feature(dev, DRIVER_PRIME))
>  		return -EINVAL;
>  
> @@ -872,6 +871,12 @@ int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
>  			args->handle, args->flags, &args->fd);
>  }
>  
> +int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
> +				 struct drm_file *file_priv)
> +{
> +	return drm_prime_handle_to_fd(dev, data, file_priv);
> +}
> +
>  int drm_prime_fd_to_handle_ioctl(struct drm_device *dev, void *data,
>  				 struct drm_file *file_priv)
>  {
> diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c
> index 32d9bcf5be7f..ef41508bc539 100644
> --- a/drivers/gpu/drm/drm_vblank.c
> +++ b/drivers/gpu/drm/drm_vblank.c
> @@ -1445,12 +1445,11 @@ static void drm_wait_vblank_reply(struct drm_device *dev, unsigned int pipe,
>  	reply->tval_usec = ts.tv_nsec / 1000;
>  }
>  
> -int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
> -			  struct drm_file *file_priv)
> +int drm_wait_vblank(struct drm_device *dev, union drm_wait_vblank *vblwait,
> +		    struct drm_file *file_priv)
>  {
>  	struct drm_crtc *crtc;
>  	struct drm_vblank_crtc *vblank;
> -	union drm_wait_vblank *vblwait = data;
>  	int ret;
>  	u64 req_seq, seq;
>  	unsigned int pipe_index;
> @@ -1567,6 +1566,12 @@ int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
>  	return ret;
>  }
>  
> +int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
> +			  struct drm_file *file_priv)
> +{
> +	return drm_wait_vblank_ioctl(dev, data, file_priv);
> +}
> +
>  static void drm_handle_vblank_events(struct drm_device *dev, unsigned int pipe)
>  {
>  	struct drm_pending_vblank_event *e, *t;
> -- 
> 2.15.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

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

* Re: [RFC v3 07/12] drm/modes: Add drm_umode_equal()
  2018-02-22 20:06 ` [RFC v3 07/12] drm/modes: Add drm_umode_equal() Noralf Trønnes
@ 2018-03-06  8:42   ` Daniel Vetter
  0 siblings, 0 replies; 26+ messages in thread
From: Daniel Vetter @ 2018-03-06  8:42 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart

On Thu, Feb 22, 2018 at 09:06:48PM +0100, Noralf Trønnes wrote:
> Add a way to check if userspace modes are equal. Useful for in-kernel
> clients. Also export drm_mode_convert_umode().
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Assuming we don't use the KMS ioctls for in-kernel clients I guess we
don't need this here either?
-Daniel

> ---
>  drivers/gpu/drm/drm_modes.c | 50 +++++++++++++++++++++++++++++++++++++++++++++
>  include/drm/drm_modes.h     |  2 ++
>  2 files changed, 52 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
> index 5a8033fda4e3..0e39164f15aa 100644
> --- a/drivers/gpu/drm/drm_modes.c
> +++ b/drivers/gpu/drm/drm_modes.c
> @@ -1631,6 +1631,56 @@ int drm_mode_convert_umode(struct drm_device *dev,
>  out:
>  	return ret;
>  }
> +EXPORT_SYMBOL(drm_mode_convert_umode);
> +
> +/**
> + * drm_umode_equal - test modeinfo modes for equality
> + * @mode1: first mode
> + * @mode2: second mode
> + *
> + * Check to see if @mode1 and @mode2 are equivalent.
> + *
> + * Returns:
> + * True if the modes are equal, false otherwise.
> + */
> +bool drm_umode_equal(const struct drm_mode_modeinfo *mode1,
> +		     const struct drm_mode_modeinfo *mode2)
> +{
> +	if (!mode1 && !mode2)
> +		return true;
> +
> +	if (!mode1 || !mode2)
> +		return false;
> +
> +	/* do clock check convert to PICOS so fb modes get matched the same */
> +	if (mode1->clock && mode2->clock) {
> +		if (KHZ2PICOS(mode1->clock) != KHZ2PICOS(mode2->clock))
> +			return false;
> +	} else if (mode1->clock != mode2->clock) {
> +		return false;
> +	}
> +
> +	if ((mode1->flags & DRM_MODE_FLAG_3D_MASK) !=
> +	    (mode2->flags & DRM_MODE_FLAG_3D_MASK))
> +		return false;
> +
> +	if (mode1->hdisplay == mode2->hdisplay &&
> +	    mode1->hsync_start == mode2->hsync_start &&
> +	    mode1->hsync_end == mode2->hsync_end &&
> +	    mode1->htotal == mode2->htotal &&
> +	    mode1->hskew == mode2->hskew &&
> +	    mode1->vdisplay == mode2->vdisplay &&
> +	    mode1->vsync_start == mode2->vsync_start &&
> +	    mode1->vsync_end == mode2->vsync_end &&
> +	    mode1->vtotal == mode2->vtotal &&
> +	    mode1->vscan == mode2->vscan &&
> +	    (mode1->flags & ~DRM_MODE_FLAG_3D_MASK) ==
> +	     (mode2->flags & ~DRM_MODE_FLAG_3D_MASK))
> +		return true;
> +
> +	return false;
> +}
> +EXPORT_SYMBOL(drm_umode_equal);
>  
>  /**
>   * drm_mode_is_420_only - if a given videomode can be only supported in YCBCR420
> diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h
> index 0d310beae6af..05e73ca4f2ae 100644
> --- a/include/drm/drm_modes.h
> +++ b/include/drm/drm_modes.h
> @@ -447,6 +447,8 @@ void drm_mode_convert_to_umode(struct drm_mode_modeinfo *out,
>  int drm_mode_convert_umode(struct drm_device *dev,
>  			   struct drm_display_mode *out,
>  			   const struct drm_mode_modeinfo *in);
> +bool drm_umode_equal(const struct drm_mode_modeinfo *mode1,
> +		     const struct drm_mode_modeinfo *mode2);
>  void drm_mode_probed_add(struct drm_connector *connector, struct drm_display_mode *mode);
>  void drm_mode_debug_printmodeline(const struct drm_display_mode *mode);
>  bool drm_mode_is_420_only(const struct drm_display_info *display,
> -- 
> 2.15.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v3 08/12] drm/framebuffer: Add drm_mode_can_dirtyfb()
  2018-02-22 20:06 ` [RFC v3 08/12] drm/framebuffer: Add drm_mode_can_dirtyfb() Noralf Trønnes
@ 2018-03-06  8:45   ` Daniel Vetter
  0 siblings, 0 replies; 26+ messages in thread
From: Daniel Vetter @ 2018-03-06  8:45 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart

On Thu, Feb 22, 2018 at 09:06:49PM +0100, Noralf Trønnes wrote:
> Add a function so the generic fbdev client can check if the framebuffer
> does flushing. This is needed to set up deferred I/O.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Again I guess not needed if we use drm_framebuffer * internally for
in-kernel clients.

For a high-level design I think the only place where we have to use the
abstract (drm_file, id) pair is for backing storage buffers, because not
every driver is using drm_gem_object for that. But otherwise I don't think
there's a need, and not using the abstract IDs makes for more cumbersome
code I think. Real userspace compositors also recreate their own mirroring
objects as the first thing, since the IDs are all a bit unwiedling to
manage.
-Daniel

> ---
>  drivers/gpu/drm/drm_framebuffer.c | 31 +++++++++++++++++++++++++++++++
>  include/drm/drm_framebuffer.h     |  2 ++
>  2 files changed, 33 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c
> index ad8f7d308656..a659cff45844 100644
> --- a/drivers/gpu/drm/drm_framebuffer.c
> +++ b/drivers/gpu/drm/drm_framebuffer.c
> @@ -600,6 +600,37 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
>  	return drm_mode_dirtyfb(dev, data, file_priv, true);
>  }
>  
> +/**
> + * drm_mode_can_dirtyfb - check if the FB does flushing
> + * @dev: drm device
> + * @fb_id: Framebuffer id
> + * @file_priv: drm file
> + *
> + * Returns:
> + * True if the framebuffer does flushing, false otherwise.
> + */
> +bool drm_mode_can_dirtyfb(struct drm_device *dev, u32 fb_id,
> +			  struct drm_file *file_priv)
> +{
> +	struct drm_framebuffer *fb;
> +	bool ret = false;
> +
> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> +		return false;
> +
> +	fb = drm_framebuffer_lookup(dev, file_priv, fb_id);
> +	if (!fb)
> +		return false;
> +
> +	if (fb->funcs->dirty)
> +		ret = true;
> +
> +	drm_framebuffer_put(fb);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_mode_can_dirtyfb);
> +
>  /**
>   * drm_fb_release - remove and free the FBs on this file
>   * @priv: drm file for the ioctl
> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h
> index c50502c656e5..05d170f4e215 100644
> --- a/include/drm/drm_framebuffer.h
> +++ b/include/drm/drm_framebuffer.h
> @@ -216,6 +216,8 @@ struct drm_framebuffer *drm_framebuffer_lookup(struct drm_device *dev,
>  void drm_framebuffer_remove(struct drm_framebuffer *fb);
>  void drm_framebuffer_cleanup(struct drm_framebuffer *fb);
>  void drm_framebuffer_unregister_private(struct drm_framebuffer *fb);
> +bool drm_mode_can_dirtyfb(struct drm_device *dev, u32 fb_id,
> +			  struct drm_file *file_priv);
>  
>  /**
>   * drm_framebuffer_get - acquire a framebuffer reference
> -- 
> 2.15.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v3 09/12] drm: Add API for in-kernel clients
  2018-02-22 20:06 ` [RFC v3 09/12] drm: Add API for in-kernel clients Noralf Trønnes
@ 2018-03-06  8:56   ` Daniel Vetter
  2018-03-08 17:12     ` Noralf Trønnes
  0 siblings, 1 reply; 26+ messages in thread
From: Daniel Vetter @ 2018-03-06  8:56 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart

On Thu, Feb 22, 2018 at 09:06:50PM +0100, Noralf Trønnes wrote:
> This adds an API for writing in-kernel clients.
> 
> TODO:
> - Flesh out and complete documentation.
> - Cloned displays is not tested.
> - Complete tiled display support and test it.
> - Test plug/unplug different monitors.
> - A runtime knob to prevent clients from attaching for debugging purposes.
> - Maybe a way to unbind individual client instances.
> - Maybe take the sysrq support in drm_fb_helper and move it here somehow.
> - Add suspend/resume callbacks.
>   Does anyone know why fbdev requires suspend/resume?
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

The core client api I like. Some of the opens I'm seeing:

- If we go with using the internal kms api directly instead of IOCTL
  wrappers then a huge pile of the functions you have here aren't needed
  (e.g. all the event stuff we can just directly use vblank events instead
  of all the wrapping). I'm leaning ever more into that direction, since
  much less code to add.

- The register/unregister model needs more thought. Allowing both clients
  to register whenever they want to, and drm_device instances to come and
  go is what fbcon has done, and the resulting locking is a horror show.

  I think if we require that all in-kernel drm_clients are registers when
  loading drm.ko (and enabled/disabled only per module options and
  Kconfig), then we can throw out all the locking. That avoids a lot of
  the headaches.

  2nd, if the list of clients is static over the lifetime of drm.ko, we
  also don't need to iterate existing drivers. Which avoids me having to
  review the iterator patch (that's the other aspect where fbcon totally
  falls over and essentially just ignores a bunch of races).

> ---
>  drivers/gpu/drm/Kconfig             |    2 +
>  drivers/gpu/drm/Makefile            |    3 +-
>  drivers/gpu/drm/client/Kconfig      |    4 +
>  drivers/gpu/drm/client/Makefile     |    1 +
>  drivers/gpu/drm/client/drm_client.c | 1612 +++++++++++++++++++++++++++++++++++

I'd move this into main drm/ directory, it's fairly core stuff.

>  drivers/gpu/drm/drm_drv.c           |    6 +
>  drivers/gpu/drm/drm_file.c          |    3 +
>  drivers/gpu/drm/drm_probe_helper.c  |    3 +
>  include/drm/drm_client.h            |  192 +++++
>  include/drm/drm_device.h            |    1 +
>  include/drm/drm_file.h              |    7 +
>  11 files changed, 1833 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/client/Kconfig
>  create mode 100644 drivers/gpu/drm/client/Makefile
>  create mode 100644 drivers/gpu/drm/client/drm_client.c
>  create mode 100644 include/drm/drm_client.h
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index deeefa7a1773..d4ae15f9ee9f 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -154,6 +154,8 @@ config DRM_SCHED
>  	tristate
>  	depends on DRM
>  
> +source "drivers/gpu/drm/client/Kconfig"
> +
>  source "drivers/gpu/drm/i2c/Kconfig"
>  
>  source "drivers/gpu/drm/arm/Kconfig"
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 50093ff4479b..8e06dc7eeca1 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
>  		drm_encoder.o drm_mode_object.o drm_property.o \
>  		drm_plane.o drm_color_mgmt.o drm_print.o \
>  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> -		drm_syncobj.o drm_lease.o
> +		drm_syncobj.o drm_lease.o client/drm_client.o
>  
>  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
>  drm-$(CONFIG_DRM_VM) += drm_vm.o
> @@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
>  obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
>  obj-$(CONFIG_DRM_PL111) += pl111/
>  obj-$(CONFIG_DRM_TVE200) += tve200/
> +obj-y			+= client/
> diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
> new file mode 100644
> index 000000000000..4bb8e4655ff7
> --- /dev/null
> +++ b/drivers/gpu/drm/client/Kconfig
> @@ -0,0 +1,4 @@
> +menu "DRM Clients"
> +	depends on DRM
> +
> +endmenu
> diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
> new file mode 100644
> index 000000000000..f66554cd5c45
> --- /dev/null
> +++ b/drivers/gpu/drm/client/Makefile
> @@ -0,0 +1 @@
> +# SPDX-License-Identifier: GPL-2.0
> diff --git a/drivers/gpu/drm/client/drm_client.c b/drivers/gpu/drm/client/drm_client.c
> new file mode 100644
> index 000000000000..a633bf747316
> --- /dev/null
> +++ b/drivers/gpu/drm/client/drm_client.c
> @@ -0,0 +1,1612 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright 2018 Noralf Trønnes
> +
> +#include <linux/dma-buf.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +
> +#include <drm/drm_client.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_file.h>
> +#include <drm/drm_ioctl.h>
> +#include <drm/drmP.h>
> +
> +#include "drm_crtc_internal.h"
> +#include "drm_internal.h"
> +
> +struct drm_client_funcs_entry {
> +	struct list_head list;
> +	const struct drm_client_funcs *funcs;
> +};
> +
> +static LIST_HEAD(drm_client_list);

I think the client list itself should be on the drm_device, not in one
global list that mixes up all the clients of all the drm_devices.

I'll skip reviewing details since we have a bunch of high-level questions
to figure out first.
-Daniel

> +static LIST_HEAD(drm_client_funcs_list);
> +static DEFINE_MUTEX(drm_client_list_lock);
> +
> +static void drm_client_new(struct drm_device *dev,
> +			   const struct drm_client_funcs *funcs)
> +{
> +	struct drm_client_dev *client;
> +	int ret;
> +
> +	lockdep_assert_held(&drm_client_list_lock);
> +
> +	client = kzalloc(sizeof(*client), GFP_KERNEL);
> +	if (!client)
> +		return;
> +
> +	mutex_init(&client->lock);
> +	client->dev = dev;
> +	client->funcs = funcs;
> +
> +	ret = funcs->new(client);
> +	DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", funcs->name, ret);
> +	if (ret) {
> +		drm_client_free(client);
> +		return;
> +	}
> +
> +	list_add(&client->list, &drm_client_list);
> +}
> +
> +/**
> + * drm_client_free - Free DRM client resources
> + * @client: DRM client
> + *
> + * This is called automatically on client removal unless the client returns
> + * non-zero in the &drm_client_funcs->remove callback. The fbdev client does
> + * this when it can't close &drm_file because userspace has an open fd.
> + */
> +void drm_client_free(struct drm_client_dev *client)
> +{
> +	DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
> +	if (WARN_ON(client->file)) {
> +		client->file_ref_count = 1;
> +		drm_client_put_file(client);
> +	}
> +	mutex_destroy(&client->lock);
> +	kfree(client->crtcs);
> +	kfree(client);
> +}
> +EXPORT_SYMBOL(drm_client_free);
> +
> +static void drm_client_remove(struct drm_client_dev *client)
> +{
> +	lockdep_assert_held(&drm_client_list_lock);
> +
> +	list_del(&client->list);
> +
> +	if (!client->funcs->remove || !client->funcs->remove(client))
> +		drm_client_free(client);
> +}
> +
> +/**
> + * drm_client_register - Register a DRM client
> + * @funcs: Client callbacks
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int drm_client_register(const struct drm_client_funcs *funcs)
> +{
> +	struct drm_client_funcs_entry *funcs_entry;
> +	struct drm_device_list_iter iter;
> +	struct drm_device *dev;
> +
> +	funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
> +	if (!funcs_entry)
> +		return -ENOMEM;
> +
> +	funcs_entry->funcs = funcs;
> +
> +	mutex_lock(&drm_global_mutex);
> +	mutex_lock(&drm_client_list_lock);
> +
> +	drm_device_list_iter_begin(&iter);
> +	drm_for_each_device_iter(dev, &iter)
> +		if (drm_core_check_feature(dev, DRIVER_MODESET))
> +			drm_client_new(dev, funcs);
> +	drm_device_list_iter_end(&iter);
> +
> +	list_add(&funcs_entry->list, &drm_client_funcs_list);
> +
> +	mutex_unlock(&drm_client_list_lock);
> +	mutex_unlock(&drm_global_mutex);
> +
> +	DRM_DEBUG_KMS("%s\n", funcs->name);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_client_register);
> +
> +/**
> + * drm_client_unregister - Unregister a DRM client
> + * @funcs: Client callbacks
> + */
> +void drm_client_unregister(const struct drm_client_funcs *funcs)
> +{
> +	struct drm_client_funcs_entry *funcs_entry;
> +	struct drm_client_dev *client, *tmp;
> +
> +	mutex_lock(&drm_client_list_lock);
> +
> +	list_for_each_entry_safe(client, tmp, &drm_client_list, list) {
> +		if (client->funcs == funcs)
> +			drm_client_remove(client);
> +	}
> +
> +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list) {
> +		if (funcs_entry->funcs == funcs) {
> +			list_del(&funcs_entry->list);
> +			kfree(funcs_entry);
> +			break;
> +		}
> +	}
> +
> +	mutex_unlock(&drm_client_list_lock);
> +
> +	DRM_DEBUG_KMS("%s\n", funcs->name);
> +}
> +EXPORT_SYMBOL(drm_client_unregister);
> +
> +void drm_client_dev_register(struct drm_device *dev)
> +{
> +	struct drm_client_funcs_entry *funcs_entry;
> +
> +	/*
> +	 * Minors are created at the beginning of drm_dev_register(), but can
> +	 * be removed again if the function fails. Since we iterate DRM devices
> +	 * by walking DRM minors, we need to stay under this lock.
> +	 */
> +	lockdep_assert_held(&drm_global_mutex);
> +
> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> +		return;
> +
> +	mutex_lock(&drm_client_list_lock);
> +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list)
> +		drm_client_new(dev, funcs_entry->funcs);
> +	mutex_unlock(&drm_client_list_lock);
> +}
> +
> +void drm_client_dev_unregister(struct drm_device *dev)
> +{
> +	struct drm_client_dev *client, *tmp;
> +
> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> +		return;
> +
> +	mutex_lock(&drm_client_list_lock);
> +	list_for_each_entry_safe(client, tmp, &drm_client_list, list)
> +		if (client->dev == dev)
> +			drm_client_remove(client);
> +	mutex_unlock(&drm_client_list_lock);
> +}
> +
> +void drm_client_dev_hotplug(struct drm_device *dev)
> +{
> +	struct drm_client_dev *client;
> +	int ret;
> +
> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> +		return;
> +
> +	mutex_lock(&drm_client_list_lock);
> +	list_for_each_entry(client, &drm_client_list, list)
> +		if (client->dev == dev && client->funcs->hotplug) {
> +			ret = client->funcs->hotplug(client);
> +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
> +					  client->funcs->name, ret);
> +		}
> +	mutex_unlock(&drm_client_list_lock);
> +}
> +
> +void drm_client_dev_lastclose(struct drm_device *dev)
> +{
> +	struct drm_client_dev *client;
> +	int ret;
> +
> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> +		return;
> +
> +	mutex_lock(&drm_client_list_lock);
> +	list_for_each_entry(client, &drm_client_list, list)
> +		if (client->dev == dev && client->funcs->lastclose) {
> +			ret = client->funcs->lastclose(client);
> +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
> +					  client->funcs->name, ret);
> +		}
> +	mutex_unlock(&drm_client_list_lock);
> +}
> +
> +/* Get static info */
> +static int drm_client_init(struct drm_client_dev *client, struct drm_file *file)
> +{
> +	struct drm_mode_card_res card_res = {};
> +	struct drm_device *dev = client->dev;
> +	u32 *crtcs;
> +	int ret;
> +
> +	ret = drm_mode_getresources(dev, &card_res, file, false);
> +	if (ret)
> +		return ret;
> +	if (!card_res.count_crtcs)
> +		return -ENOENT;
> +
> +	crtcs = kmalloc_array(card_res.count_crtcs, sizeof(*crtcs), GFP_KERNEL);
> +	if (!crtcs)
> +		return -ENOMEM;
> +
> +	card_res.count_fbs = 0;
> +	card_res.count_connectors = 0;
> +	card_res.count_encoders = 0;
> +	card_res.crtc_id_ptr = (unsigned long)crtcs;
> +
> +	ret = drm_mode_getresources(dev, &card_res, file, false);
> +	if (ret) {
> +		kfree(crtcs);
> +		return ret;
> +	}
> +
> +	client->crtcs = crtcs;
> +	client->num_crtcs = card_res.count_crtcs;
> +	client->min_width = card_res.min_width;
> +	client->max_width = card_res.max_width;
> +	client->min_height = card_res.min_height;
> +	client->max_height = card_res.max_height;
> +
> +	return 0;
> +}
> +
> +/**
> + * drm_client_get_file - Get a DRM file
> + * @client: DRM client
> + *
> + * This function makes sure the client has a &drm_file available. The client
> + * doesn't normally need to call this, since all client functions that depends
> + * on a DRM file will call it. A matching call to drm_client_put_file() is
> + * necessary.
> + *
> + * The reason for not opening a DRM file when a @client is created is because
> + * we have to take a ref on the driver module due to &drm_driver->postclose
> + * being called in drm_file_free(). Having a DRM file open for the lifetime of
> + * the client instance would block driver module unload.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int drm_client_get_file(struct drm_client_dev *client)
> +{
> +	struct drm_device *dev = client->dev;
> +	struct drm_file *file;
> +	int ret = 0;
> +
> +	mutex_lock(&client->lock);
> +
> +	if (client->file_ref_count++) {
> +		mutex_unlock(&client->lock);
> +		return 0;
> +	}
> +
> +	if (!try_module_get(dev->driver->fops->owner)) {
> +		ret = -ENODEV;
> +		goto err_unlock;
> +	}
> +
> +	drm_dev_get(dev);
> +
> +	file = drm_file_alloc(dev->primary);
> +	if (IS_ERR(file)) {
> +		ret = PTR_ERR(file);
> +		goto err_put;
> +	}
> +
> +	if (!client->crtcs) {
> +		ret = drm_client_init(client, file);
> +		if (ret)
> +			goto err_free;
> +	}
> +
> +	mutex_lock(&dev->filelist_mutex);
> +	list_add(&file->lhead, &dev->filelist_internal);
> +	mutex_unlock(&dev->filelist_mutex);
> +
> +	client->file = file;
> +
> +	mutex_unlock(&client->lock);
> +
> +	return 0;
> +
> +err_free:
> +	drm_file_free(file);
> +err_put:
> +	drm_dev_put(dev);
> +	module_put(dev->driver->fops->owner);
> +err_unlock:
> +	client->file_ref_count = 0;
> +	mutex_unlock(&client->lock);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_get_file);
> +
> +void drm_client_put_file(struct drm_client_dev *client)
> +{
> +	struct drm_device *dev = client->dev;
> +
> +	if (!client)
> +		return;
> +
> +	mutex_lock(&client->lock);
> +
> +	if (WARN_ON(!client->file_ref_count))
> +		goto out_unlock;
> +
> +	if (--client->file_ref_count)
> +		goto out_unlock;
> +
> +	mutex_lock(&dev->filelist_mutex);
> +	list_del(&client->file->lhead);
> +	mutex_unlock(&dev->filelist_mutex);
> +
> +	drm_file_free(client->file);
> +	client->file = NULL;
> +	drm_dev_put(dev);
> +	module_put(dev->driver->fops->owner);
> +out_unlock:
> +	mutex_unlock(&client->lock);
> +}
> +EXPORT_SYMBOL(drm_client_put_file);
> +
> +static struct drm_pending_event *
> +drm_client_read_get_pending_event(struct drm_device *dev, struct drm_file *file)
> +{
> +	struct drm_pending_event *e = NULL;
> +	int ret;
> +
> +	ret = mutex_lock_interruptible(&file->event_read_lock);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	spin_lock_irq(&dev->event_lock);
> +	if (!list_empty(&file->event_list)) {
> +		e = list_first_entry(&file->event_list,
> +				     struct drm_pending_event, link);
> +		file->event_space += e->event->length;
> +		list_del(&e->link);
> +	}
> +	spin_unlock_irq(&dev->event_lock);
> +
> +	mutex_unlock(&file->event_read_lock);
> +
> +	return e;
> +}
> +
> +struct drm_event *
> +drm_client_read_event(struct drm_client_dev *client, bool block)
> +{
> +	struct drm_file *file = client->file;
> +	struct drm_device *dev = client->dev;
> +	struct drm_pending_event *e;
> +	struct drm_event *event;
> +	int ret;
> +
> +	/* Allocate so it fits all events, there's a sanity check later */
> +	event = kzalloc(128, GFP_KERNEL);
> +	if (!event)
> +		return ERR_PTR(-ENOMEM);
> +
> +	e = drm_client_read_get_pending_event(dev, file);
> +	if (IS_ERR(e)) {
> +		ret = PTR_ERR(e);
> +		goto err_free;
> +	}
> +
> +	if (!e && !block) {
> +		ret = 0;
> +		goto err_free;
> +	}
> +
> +	ret = wait_event_interruptible_timeout(file->event_wait,
> +					       !list_empty(&file->event_list),
> +					       5 * HZ);
> +	if (!ret)
> +		ret = -ETIMEDOUT;
> +	if (ret < 0)
> +		goto err_free;
> +
> +	e = drm_client_read_get_pending_event(dev, file);
> +	if (IS_ERR_OR_NULL(e)) {
> +		ret = PTR_ERR_OR_ZERO(e);
> +		goto err_free;
> +	}
> +
> +	if (WARN_ON(e->event->length > 128)) {
> +		/* Increase buffer if this happens */
> +		ret = -ENOMEM;
> +		goto err_free;
> +	}
> +
> +	memcpy(event, e->event, e->event->length);
> +	kfree(e);
> +
> +	return event;
> +
> +err_free:
> +	kfree(event);
> +
> +	return ret ? ERR_PTR(ret) : NULL;
> +}
> +EXPORT_SYMBOL(drm_client_read_event);
> +
> +static void drm_client_connector_free(struct drm_client_connector *connector)
> +{
> +	if (!connector)
> +		return;
> +	kfree(connector->modes);
> +	kfree(connector);
> +}
> +
> +static struct drm_client_connector *
> +drm_client_get_connector(struct drm_client_dev *client, unsigned int id)
> +{
> +	struct drm_mode_get_connector req = {
> +		.connector_id = id,
> +	};
> +	struct drm_client_connector *connector;
> +	struct drm_mode_modeinfo *modes = NULL;
> +	struct drm_device *dev = client->dev;
> +	struct drm_connector *conn;
> +	bool non_desktop;
> +	int ret;
> +
> +	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
> +	if (!connector)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ret = drm_mode_getconnector(dev, &req, client->file, false);
> +	if (ret)
> +		goto err_free;
> +
> +	connector->conn_id = id;
> +	connector->status = req.connection;
> +
> +	conn = drm_connector_lookup(dev, client->file, id);
> +	if (!conn) {
> +		ret = -ENOENT;
> +		goto err_free;
> +	}
> +
> +	non_desktop = conn->display_info.non_desktop;
> +
> +	connector->has_tile = conn->has_tile;
> +	connector->tile_h_loc = conn->tile_h_loc;
> +	connector->tile_v_loc = conn->tile_v_loc;
> +	if (conn->tile_group)
> +		connector->tile_group = conn->tile_group->id;
> +
> +	drm_connector_put(conn);
> +
> +	if (non_desktop) {
> +		kfree(connector);
> +		return NULL;
> +	}
> +
> +	if (!req.count_modes)
> +		return connector;
> +
> +	modes = kcalloc(req.count_modes, sizeof(*modes), GFP_KERNEL);
> +	if (!modes) {
> +		ret = -ENOMEM;
> +		goto err_free;
> +	}
> +
> +	connector->modes = modes;
> +	connector->num_modes = req.count_modes;
> +
> +	req.count_props = 0;
> +	req.count_encoders = 0;
> +	req.modes_ptr = (unsigned long)modes;
> +
> +	ret = drm_mode_getconnector(dev, &req, client->file, false);
> +	if (ret)
> +		goto err_free;
> +
> +	return connector;
> +
> +err_free:
> +	kfree(modes);
> +	kfree(connector);
> +
> +	return ERR_PTR(ret);
> +}
> +
> +static int drm_client_get_connectors(struct drm_client_dev *client,
> +				     struct drm_client_connector ***connectors)
> +{
> +	struct drm_mode_card_res card_res = {};
> +	struct drm_device *dev = client->dev;
> +	int ret, num_connectors;
> +	u32 *connector_ids;
> +	unsigned int i;
> +
> +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
> +	if (ret)
> +		return ret;
> +	if (!card_res.count_connectors)
> +		return 0;
> +
> +	num_connectors = card_res.count_connectors;
> +	connector_ids = kcalloc(num_connectors,
> +				sizeof(*connector_ids), GFP_KERNEL);
> +	if (!connector_ids)
> +		return -ENOMEM;
> +
> +	card_res.count_fbs = 0;
> +	card_res.count_crtcs = 0;
> +	card_res.count_encoders = 0;
> +	card_res.connector_id_ptr = (unsigned long)connector_ids;
> +
> +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
> +	if (ret)
> +		goto err_free;
> +
> +	*connectors = kcalloc(num_connectors, sizeof(**connectors), GFP_KERNEL);
> +	if (!(*connectors)) {
> +		ret = -ENOMEM;
> +		goto err_free;
> +	}
> +
> +	for (i = 0; i < num_connectors; i++) {
> +		struct drm_client_connector *connector;
> +
> +		connector = drm_client_get_connector(client, connector_ids[i]);
> +		if (IS_ERR(connector)) {
> +			ret = PTR_ERR(connector);
> +			goto err_free;
> +		}
> +		if (connector)
> +			(*connectors)[i] = connector;
> +		else
> +			num_connectors--;
> +	}
> +
> +	if (!num_connectors) {
> +		ret = 0;
> +		goto err_free;
> +	}
> +
> +	return num_connectors;
> +
> +err_free:
> +	if (connectors)
> +		for (i = 0; i < num_connectors; i++)
> +			drm_client_connector_free((*connectors)[i]);
> +
> +	kfree(connectors);
> +	kfree(connector_ids);
> +
> +	return ret;
> +}
> +
> +static bool
> +drm_client_connector_is_enabled(struct drm_client_connector *connector,
> +				bool strict)
> +{
> +	if (strict)
> +		return connector->status == connector_status_connected;
> +	else
> +		return connector->status != connector_status_disconnected;
> +}
> +
> +struct drm_mode_modeinfo *
> +drm_client_display_first_mode(struct drm_client_display *display)
> +{
> +	if (!display->num_modes)
> +		return NULL;
> +	return display->modes;
> +}
> +EXPORT_SYMBOL(drm_client_display_first_mode);
> +
> +struct drm_mode_modeinfo *
> +drm_client_display_next_mode(struct drm_client_display *display,
> +			     struct drm_mode_modeinfo *mode)
> +{
> +	struct drm_mode_modeinfo *modes = display->modes;
> +
> +	if (++mode < &modes[display->num_modes])
> +		return mode;
> +
> +	return NULL;
> +}
> +EXPORT_SYMBOL(drm_client_display_next_mode);
> +
> +static void
> +drm_client_display_fill_tile_modes(struct drm_client_display *display,
> +				   struct drm_mode_modeinfo *tile_modes)
> +{
> +	unsigned int i, j, num_modes = display->connectors[0]->num_modes;
> +	struct drm_mode_modeinfo *tile_mode, *conn_mode;
> +
> +	if (!num_modes) {
> +		kfree(tile_modes);
> +		kfree(display->modes);
> +		display->modes = NULL;
> +		display->num_modes = 0;
> +		return;
> +	}
> +
> +	for (i = 0; i < num_modes; i++) {
> +		tile_mode = &tile_modes[i];
> +
> +		conn_mode = &display->connectors[0]->modes[i];
> +		tile_mode->clock = conn_mode->clock;
> +		tile_mode->vscan = conn_mode->vscan;
> +		tile_mode->vrefresh = conn_mode->vrefresh;
> +		tile_mode->flags = conn_mode->flags;
> +		tile_mode->type = conn_mode->type;
> +
> +		for (j = 0; j < display->num_connectors; j++) {
> +			conn_mode = &display->connectors[j]->modes[i];
> +
> +			if (!display->connectors[j]->tile_h_loc) {
> +				tile_mode->hdisplay += conn_mode->hdisplay;
> +				tile_mode->hsync_start += conn_mode->hsync_start;
> +				tile_mode->hsync_end += conn_mode->hsync_end;
> +				tile_mode->htotal += conn_mode->htotal;
> +			}
> +
> +			if (!display->connectors[j]->tile_v_loc) {
> +				tile_mode->vdisplay += conn_mode->vdisplay;
> +				tile_mode->vsync_start += conn_mode->vsync_start;
> +				tile_mode->vsync_end += conn_mode->vsync_end;
> +				tile_mode->vtotal += conn_mode->vtotal;
> +			}
> +		}
> +	}
> +
> +	kfree(display->modes);
> +	display->modes = tile_modes;
> +	display->num_modes = num_modes;
> +}
> +
> +/**
> + * drm_client_display_update_modes - Fetch display modes
> + * @display: Client display
> + * @mode_changed: Optional pointer to boolen which return whether the modes
> + *                have changed or not.
> + *
> + * This function can be used in the client hotplug callback to check if the
> + * video modes have changed and get them up-to-date.
> + *
> + * Returns:
> + * Number of modes on success, negative error code on failure.
> + */
> +int drm_client_display_update_modes(struct drm_client_display *display,
> +				    bool *mode_changed)
> +{
> +	unsigned int num_connectors = display->num_connectors;
> +	struct drm_client_dev *client = display->client;
> +	struct drm_mode_modeinfo *display_tile_modes;
> +	struct drm_client_connector **connectors;
> +	unsigned int i, num_modes = 0;
> +	bool dummy_changed = false;
> +	int ret;
> +
> +	if (mode_changed)
> +		*mode_changed = false;
> +	else
> +		mode_changed = &dummy_changed;
> +
> +	if (display->cloned)
> +		return 2;
> +
> +	ret = drm_client_get_file(client);
> +	if (ret)
> +		return ret;
> +
> +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
> +	if (!connectors) {
> +		ret = -ENOMEM;
> +		goto out_put_file;
> +	}
> +
> +	/* Get a new set for comparison */
> +	for (i = 0; i < num_connectors; i++) {
> +		connectors[i] = drm_client_get_connector(client, display->connectors[i]->conn_id);
> +		if (IS_ERR_OR_NULL(connectors[i])) {
> +			ret = PTR_ERR_OR_ZERO(connectors[i]);
> +			if (!ret)
> +				ret = -ENOENT;
> +			goto out_cleanup;
> +		}
> +	}
> +
> +	/* All connectors should have the same number of modes */
> +	num_modes = connectors[0]->num_modes;
> +	for (i = 0; i < num_connectors; i++) {
> +		if (num_modes != connectors[i]->num_modes) {
> +			ret = -EINVAL;
> +			goto out_cleanup;
> +		}
> +	}
> +
> +	if (num_connectors > 1) {
> +		display_tile_modes = kcalloc(num_modes, sizeof(*display_tile_modes), GFP_KERNEL);
> +		if (!display_tile_modes) {
> +			ret = -ENOMEM;
> +			goto out_cleanup;
> +		}
> +	}
> +
> +	mutex_lock(&display->modes_lock);
> +
> +	for (i = 0; i < num_connectors; i++) {
> +		display->connectors[i]->status = connectors[i]->status;
> +		if (display->connectors[i]->num_modes != connectors[i]->num_modes) {
> +			display->connectors[i]->num_modes = connectors[i]->num_modes;
> +			kfree(display->connectors[i]->modes);
> +			display->connectors[i]->modes = connectors[i]->modes;
> +			connectors[i]->modes = NULL;
> +			*mode_changed = true;
> +		}
> +	}
> +
> +	if (num_connectors > 1)
> +		drm_client_display_fill_tile_modes(display, display_tile_modes);
> +	else
> +		display->modes = display->connectors[0]->modes;
> +
> +	mutex_unlock(&display->modes_lock);
> +
> +out_cleanup:
> +	for (i = 0; i < num_connectors; i++)
> +		drm_client_connector_free(connectors[i]);
> +	kfree(connectors);
> +out_put_file:
> +	drm_client_put_file(client);
> +
> +	return ret ? ret : num_modes;
> +}
> +EXPORT_SYMBOL(drm_client_display_update_modes);
> +
> +void drm_client_display_free(struct drm_client_display *display)
> +{
> +	unsigned int i;
> +
> +	if (!display)
> +		return;
> +
> +	/* tile modes? */
> +	if (display->modes != display->connectors[0]->modes)
> +		kfree(display->modes);
> +
> +	for (i = 0; i < display->num_connectors; i++)
> +		drm_client_connector_free(display->connectors[i]);
> +
> +	kfree(display->connectors);
> +	mutex_destroy(&display->modes_lock);
> +	kfree(display);
> +}
> +EXPORT_SYMBOL(drm_client_display_free);
> +
> +static struct drm_client_display *
> +drm_client_display_alloc(struct drm_client_dev *client,
> +			 unsigned int num_connectors)
> +{
> +	struct drm_client_display *display;
> +	struct drm_client_connector **connectors;
> +
> +	display = kzalloc(sizeof(*display), GFP_KERNEL);
> +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
> +	if (!display || !connectors) {
> +		kfree(display);
> +		kfree(connectors);
> +		return NULL;
> +	}
> +
> +	mutex_init(&display->modes_lock);
> +	display->client = client;
> +	display->connectors = connectors;
> +	display->num_connectors = num_connectors;
> +
> +	return display;
> +}
> +
> +/* Logic is from drm_fb_helper */
> +static struct drm_client_display *
> +drm_client_connector_pick_cloned(struct drm_client_dev *client,
> +				 struct drm_client_connector **connectors,
> +				 unsigned int num_connectors)
> +{
> +	struct drm_mode_modeinfo modes[2], udmt_mode, *mode, *tmp;
> +	struct drm_display_mode *dmt_display_mode = NULL;
> +	unsigned int i, j, conns[2], num_conns = 0;
> +	struct drm_client_connector *connector;
> +	struct drm_device *dev = client->dev;
> +	struct drm_client_display *display;
> +
> +	/* only contemplate cloning in the single crtc case */
> +	if (dev->mode_config.num_crtc > 1)
> +		return NULL;
> +retry:
> +	for (i = 0; i < num_connectors; i++) {
> +		connector = connectors[i];
> +		if (!connector || connector->has_tile || !connector->num_modes)
> +			continue;
> +
> +		for (j = 0; j < connector->num_modes; j++) {
> +			mode = &connector->modes[j];
> +			if (dmt_display_mode) {
> +				if (drm_umode_equal(&udmt_mode, mode)) {
> +					conns[num_conns] = i;
> +					modes[num_conns++] = *mode;
> +					break;
> +				}
> +			} else {
> +				if (mode->type & DRM_MODE_TYPE_USERDEF) {
> +					conns[num_conns] = i;
> +					modes[num_conns++] = *mode;
> +					break;
> +				}
> +			}
> +		}
> +		if (num_conns == 2)
> +			break;
> +	}
> +
> +	if (num_conns == 2)
> +		goto found;
> +
> +	if (dmt_display_mode)
> +		return NULL;
> +
> +	dmt_display_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
> +	drm_mode_convert_to_umode(&udmt_mode, dmt_display_mode);
> +	drm_mode_destroy(dev, dmt_display_mode);
> +
> +	goto retry;
> +
> +found:
> +	tmp = kcalloc(2, sizeof(*tmp), GFP_KERNEL);
> +	if (!tmp)
> +		return ERR_PTR(-ENOMEM);
> +
> +	display = drm_client_display_alloc(client, 2);
> +	if (!display) {
> +		kfree(tmp);
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
> +	for (i = 0; i < 2; i++) {
> +		connector = connectors[conns[i]];
> +		display->connectors[i] = connector;
> +		connectors[conns[i]] = NULL;
> +		kfree(connector->modes);
> +		tmp[i] = modes[i];
> +		connector->modes = &tmp[i];
> +		connector->num_modes = 1;
> +	}
> +
> +	display->cloned = true;
> +	display->modes = &tmp[0];
> +	display->num_modes = 1;
> +
> +	return display;
> +}
> +
> +static struct drm_client_display *
> +drm_client_connector_pick_tile(struct drm_client_dev *client,
> +			       struct drm_client_connector **connectors,
> +			       unsigned int num_connectors)
> +{
> +	unsigned int i, num_conns, num_modes, tile_group = 0;
> +	struct drm_mode_modeinfo *tile_modes = NULL;
> +	struct drm_client_connector *connector;
> +	struct drm_client_display *display;
> +	u16 conns[32];
> +
> +	for (i = 0; i < num_connectors; i++) {
> +		connector = connectors[i];
> +		if (!connector || !connector->tile_group)
> +			continue;
> +
> +		if (!tile_group) {
> +			tile_group = connector->tile_group;
> +			num_modes = connector->num_modes;
> +		}
> +
> +		if (connector->tile_group != tile_group)
> +			continue;
> +
> +		if (num_modes != connector->num_modes) {
> +			DRM_ERROR("Tile connectors must have the same number of modes\n");
> +			return ERR_PTR(-EINVAL);
> +		}
> +
> +		conns[num_conns++] = i;
> +		if (WARN_ON(num_conns == 33))
> +			return ERR_PTR(-EINVAL);
> +	}
> +
> +	if (!num_conns)
> +		return NULL;
> +
> +	if (num_modes) {
> +		tile_modes = kcalloc(num_modes, sizeof(*tile_modes), GFP_KERNEL);
> +		if (!tile_modes)
> +			return ERR_PTR(-ENOMEM);
> +	}
> +
> +	display = drm_client_display_alloc(client, num_conns);
> +	if (!display) {
> +		kfree(tile_modes);
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
> +	if (num_modes)
> +		drm_client_display_fill_tile_modes(display, tile_modes);
> +
> +	return display;
> +}
> +
> +static struct drm_client_display *
> +drm_client_connector_pick_not_tile(struct drm_client_dev *client,
> +				   struct drm_client_connector **connectors,
> +				   unsigned int num_connectors)
> +{
> +	struct drm_client_display *display;
> +	unsigned int i;
> +
> +	for (i = 0; i < num_connectors; i++) {
> +		if (!connectors[i] || connectors[i]->has_tile)
> +			continue;
> +		break;
> +	}
> +
> +	if (i == num_connectors)
> +		return NULL;
> +
> +	display = drm_client_display_alloc(client, 1);
> +	if (!display)
> +		return ERR_PTR(-ENOMEM);
> +
> +	display->connectors[0] = connectors[i];
> +	connectors[i] = NULL;
> +	display->modes = display->connectors[0]->modes;
> +	display->num_modes = display->connectors[0]->num_modes;
> +
> +	return display;
> +}
> +
> +/* Get connectors and bundle them up into displays */
> +static int drm_client_get_displays(struct drm_client_dev *client,
> +				   struct drm_client_display ***displays)
> +{
> +	int ret, num_connectors, num_displays = 0;
> +	struct drm_client_connector **connectors;
> +	struct drm_client_display *display;
> +	unsigned int i;
> +
> +	ret = drm_client_get_file(client);
> +	if (ret)
> +		return ret;
> +
> +	num_connectors = drm_client_get_connectors(client, &connectors);
> +	if (num_connectors <= 0) {
> +		ret = num_connectors;
> +		goto err_put_file;
> +	}
> +
> +	*displays = kcalloc(num_connectors, sizeof(*displays), GFP_KERNEL);
> +	if (!(*displays)) {
> +		ret = -ENOMEM;
> +		goto err_free;
> +	}
> +
> +	display = drm_client_connector_pick_cloned(client, connectors,
> +						   num_connectors);
> +	if (IS_ERR(display)) {
> +		ret = PTR_ERR(display);
> +		goto err_free;
> +	}
> +	if (display)
> +		(*displays)[num_displays++] = display;
> +
> +	for (i = 0; i < num_connectors; i++) {
> +		display = drm_client_connector_pick_tile(client, connectors,
> +							 num_connectors);
> +		if (IS_ERR(display)) {
> +			ret = PTR_ERR(display);
> +			goto err_free;
> +		}
> +		if (!display)
> +			break;
> +		(*displays)[num_displays++] = display;
> +	}
> +
> +	for (i = 0; i < num_connectors; i++) {
> +		display = drm_client_connector_pick_not_tile(client, connectors,
> +							     num_connectors);
> +		if (IS_ERR(display)) {
> +			ret = PTR_ERR(display);
> +			goto err_free;
> +		}
> +		if (!display)
> +			break;
> +		(*displays)[num_displays++] = display;
> +	}
> +
> +	for (i = 0; i < num_connectors; i++) {
> +		if (connectors[i]) {
> +			DRM_INFO("Connector %u fell through the cracks.\n",
> +				 connectors[i]->conn_id);
> +			drm_client_connector_free(connectors[i]);
> +		}
> +	}
> +
> +	drm_client_put_file(client);
> +	kfree(connectors);
> +
> +	return num_displays;
> +
> +err_free:
> +	for (i = 0; i < num_displays; i++)
> +		drm_client_display_free((*displays)[i]);
> +	kfree(*displays);
> +	*displays = NULL;
> +	for (i = 0; i < num_connectors; i++)
> +		drm_client_connector_free(connectors[i]);
> +	kfree(connectors);
> +err_put_file:
> +	drm_client_put_file(client);
> +
> +	return ret;
> +}
> +
> +static bool
> +drm_client_display_is_enabled(struct drm_client_display *display, bool strict)
> +{
> +	unsigned int i;
> +
> +	if (!display->num_modes)
> +		return false;
> +
> +	for (i = 0; i < display->num_connectors; i++)
> +		if (!drm_client_connector_is_enabled(display->connectors[i], strict))
> +			return false;
> +
> +	return true;
> +}
> +
> +/**
> + * drm_client_display_get_first_enabled - Get first enabled display
> + * @client: DRM client
> + * @strict: If true the connector(s) have to be connected, if false they can
> + *          also have unknown status.
> + *
> + * This function gets all connectors and bundles them into displays
> + * (tiled/cloned). It then picks the first one with connectors that is enabled
> + * according to @strict.
> + *
> + * Returns:
> + * Pointer to a client display if such a display was found, NULL if not found
> + * or an error pointer on failure.
> + */
> +struct drm_client_display *
> +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict)
> +{
> +	struct drm_client_display **displays, *display = NULL;
> +	int num_displays;
> +	unsigned int i;
> +
> +	num_displays = drm_client_get_displays(client, &displays);
> +	if (num_displays < 0)
> +		return ERR_PTR(num_displays);
> +	if (!num_displays)
> +		return NULL;
> +
> +	for (i = 0; i < num_displays; i++) {
> +		if (!display &&
> +		    drm_client_display_is_enabled(displays[i], strict)) {
> +			display = displays[i];
> +			continue;
> +		}
> +		drm_client_display_free(displays[i]);
> +	}
> +
> +	kfree(displays);
> +
> +	return display;
> +}
> +EXPORT_SYMBOL(drm_client_display_get_first_enabled);
> +
> +unsigned int
> +drm_client_display_preferred_depth(struct drm_client_display *display)
> +{
> +	struct drm_connector *conn;
> +	unsigned int ret;
> +
> +	conn = drm_connector_lookup(display->client->dev, NULL,
> +				    display->connectors[0]->conn_id);
> +	if (!conn)
> +		return 0;
> +
> +	if (conn->cmdline_mode.bpp_specified)
> +		ret = conn->cmdline_mode.bpp;
> +	else
> +		ret = display->client->dev->mode_config.preferred_depth;
> +
> +	drm_connector_put(conn);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_display_preferred_depth);
> +
> +int drm_client_display_dpms(struct drm_client_display *display, int mode)
> +{
> +	struct drm_mode_obj_set_property prop;
> +
> +	prop.value = mode;
> +	prop.prop_id = display->client->dev->mode_config.dpms_property->base.id;
> +	prop.obj_id = display->connectors[0]->conn_id;
> +	prop.obj_type = DRM_MODE_OBJECT_CONNECTOR;
> +
> +	return drm_mode_obj_set_property(display->client->dev, &prop,
> +					 display->client->file);
> +}
> +EXPORT_SYMBOL(drm_client_display_dpms);
> +
> +int drm_client_display_wait_vblank(struct drm_client_display *display)
> +{
> +	struct drm_crtc *crtc;
> +	union drm_wait_vblank vblank_req = {
> +		.request = {
> +			.type = _DRM_VBLANK_RELATIVE,
> +			.sequence = 1,
> +		},
> +	};
> +
> +	crtc = drm_crtc_find(display->client->dev, display->client->file,
> +			     display->connectors[0]->crtc_id);
> +	if (!crtc)
> +		return -ENOENT;
> +
> +	vblank_req.request.type |= drm_crtc_index(crtc) << _DRM_VBLANK_HIGH_CRTC_SHIFT;
> +
> +	return drm_wait_vblank(display->client->dev, &vblank_req,
> +			       display->client->file);
> +}
> +EXPORT_SYMBOL(drm_client_display_wait_vblank);
> +
> +static int drm_client_get_crtc_index(struct drm_client_dev *client, u32 id)
> +{
> +	int i;
> +
> +	for (i = 0; i < client->num_crtcs; i++)
> +		if (client->crtcs[i] == id)
> +			return i;
> +
> +	return -ENOENT;
> +}
> +
> +static int drm_client_display_find_crtcs(struct drm_client_display *display)
> +{
> +	struct drm_client_dev *client = display->client;
> +	struct drm_device *dev = client->dev;
> +	struct drm_file *file = client->file;
> +	u32 encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
> +	unsigned int i, j, available_crtcs = ~0;
> +	struct drm_mode_get_connector conn_req;
> +	struct drm_mode_get_encoder enc_req;
> +	int ret;
> +
> +	/* Already done? */
> +	if (display->connectors[0]->crtc_id)
> +		return 0;
> +
> +	for (i = 0; i < display->num_connectors; i++) {
> +		u32 active_crtcs = 0, crtcs_for_connector = 0;
> +		int crtc_idx;
> +
> +		memset(&conn_req, 0, sizeof(conn_req));
> +		conn_req.connector_id = display->connectors[i]->conn_id;
> +		conn_req.encoders_ptr = (unsigned long)(encoder_ids);
> +		conn_req.count_encoders = DRM_CONNECTOR_MAX_ENCODER;
> +		ret = drm_mode_getconnector(dev, &conn_req, file, false);
> +		if (ret)
> +			return ret;
> +
> +		if (conn_req.encoder_id) {
> +			memset(&enc_req, 0, sizeof(enc_req));
> +			enc_req.encoder_id = conn_req.encoder_id;
> +			ret = drm_mode_getencoder(dev, &enc_req, file);
> +			if (ret)
> +				return ret;
> +			crtcs_for_connector |= enc_req.possible_crtcs;
> +			if (crtcs_for_connector & available_crtcs)
> +				goto found;
> +		}
> +
> +		for (j = 0; j < conn_req.count_encoders; j++) {
> +			memset(&enc_req, 0, sizeof(enc_req));
> +			enc_req.encoder_id = encoder_ids[j];
> +			ret = drm_mode_getencoder(dev, &enc_req, file);
> +			if (ret)
> +				return ret;
> +
> +			crtcs_for_connector |= enc_req.possible_crtcs;
> +
> +			if (enc_req.crtc_id) {
> +				crtc_idx = drm_client_get_crtc_index(client, enc_req.crtc_id);
> +				if (crtc_idx >= 0)
> +					active_crtcs |= 1 << crtc_idx;
> +			}
> +		}
> +
> +found:
> +		crtcs_for_connector &= available_crtcs;
> +		active_crtcs &= available_crtcs;
> +
> +		if (!crtcs_for_connector)
> +			return -ENOENT;
> +
> +		if (active_crtcs)
> +			crtc_idx = ffs(active_crtcs) - 1;
> +		else
> +			crtc_idx = ffs(crtcs_for_connector) - 1;
> +
> +		if (crtc_idx >= client->num_crtcs)
> +			return -EINVAL;
> +
> +		display->connectors[i]->crtc_id = client->crtcs[crtc_idx];
> +		available_crtcs &= ~BIT(crtc_idx);
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * drm_client_display_commit_mode - Commit a mode to the crtc(s)
> + * @display: Client display
> + * @fb_id: Framebuffer id
> + * @mode: Video mode
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int drm_client_display_commit_mode(struct drm_client_display *display,
> +				   u32 fb_id, struct drm_mode_modeinfo *mode)
> +{
> +	struct drm_client_dev *client = display->client;
> +	struct drm_device *dev = client->dev;
> +	unsigned int num_crtcs = client->num_crtcs;
> +	struct drm_file *file = client->file;
> +	unsigned int *xoffsets = NULL, *yoffsets = NULL;
> +	struct drm_mode_crtc *crtc_reqs, *req;
> +	u32 cloned_conn_ids[2];
> +	unsigned int i;
> +	int idx, ret;
> +
> +	ret = drm_client_display_find_crtcs(display);
> +	if (ret)
> +		return ret;
> +
> +	crtc_reqs = kcalloc(num_crtcs, sizeof(*crtc_reqs), GFP_KERNEL);
> +	if (!crtc_reqs)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < num_crtcs; i++)
> +		crtc_reqs[i].crtc_id = client->crtcs[i];
> +
> +	if (drm_client_display_is_tiled(display)) {
> +		/* TODO calculate tile crtc offsets */
> +	}
> +
> +	for (i = 0; i < display->num_connectors; i++) {
> +		idx = drm_client_get_crtc_index(client, display->connectors[i]->crtc_id);
> +		if (idx < 0)
> +			return -ENOENT;
> +
> +		req = &crtc_reqs[idx];
> +
> +		req->fb_id = fb_id;
> +		if (xoffsets) {
> +			req->x = xoffsets[i];
> +			req->y = yoffsets[i];
> +		}
> +		req->mode_valid = 1;
> +		req->mode = *mode;
> +
> +		if (display->cloned) {
> +			cloned_conn_ids[0] = display->connectors[0]->conn_id;
> +			cloned_conn_ids[1] = display->connectors[1]->conn_id;
> +			req->set_connectors_ptr = (unsigned long)(cloned_conn_ids);
> +			req->count_connectors = 2;
> +			break;
> +		}
> +
> +		req->set_connectors_ptr = (unsigned long)(&display->connectors[i]->conn_id);
> +		req->count_connectors = 1;
> +	}
> +
> +	for (i = 0; i < num_crtcs; i++) {
> +		ret = drm_mode_setcrtc(dev, &crtc_reqs[i], file, false);
> +		if (ret)
> +			break;
> +	}
> +
> +	kfree(xoffsets);
> +	kfree(yoffsets);
> +	kfree(crtc_reqs);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_display_commit_mode);
> +
> +unsigned int drm_client_display_current_fb(struct drm_client_display *display)
> +{
> +	struct drm_client_dev *client = display->client;
> +	int ret;
> +	struct drm_mode_crtc crtc_req = {
> +		.crtc_id = display->connectors[0]->crtc_id,
> +	};
> +
> +	ret = drm_mode_getcrtc(client->dev, &crtc_req, client->file);
> +	if (ret)
> +		return 0;
> +
> +	return crtc_req.fb_id;
> +}
> +EXPORT_SYMBOL(drm_client_display_current_fb);
> +
> +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
> +			     struct drm_clip_rect *clips, unsigned int num_clips)
> +{
> +	struct drm_client_dev *client = display->client;
> +	struct drm_mode_fb_dirty_cmd dirty_req = {
> +		.fb_id = fb_id,
> +		.clips_ptr = (unsigned long)clips,
> +		.num_clips = num_clips,
> +	};
> +	int ret;
> +
> +	if (display->no_flushing)
> +		return 0;
> +
> +	ret = drm_mode_dirtyfb(client->dev, &dirty_req, client->file, false);
> +	if (ret == -ENOSYS) {
> +		ret = 0;
> +		display->no_flushing = true;
> +	}
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_display_flush);
> +
> +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
> +				 bool event)
> +{
> +	struct drm_client_dev *client = display->client;
> +	struct drm_mode_crtc_page_flip_target page_flip_req = {
> +		.crtc_id = display->connectors[0]->crtc_id,
> +		.fb_id = fb_id,
> +	};
> +
> +	if (event)
> +		page_flip_req.flags = DRM_MODE_PAGE_FLIP_EVENT;
> +
> +	return drm_mode_page_flip(client->dev, &page_flip_req, client->file);
> +	/*
> +	 * TODO:
> +	 * Where do we flush on page flip? Should the driver handle that?
> +	 */
> +}
> +EXPORT_SYMBOL(drm_client_display_page_flip);
> +
> +/**
> + * drm_client_framebuffer_create - Create a client framebuffer
> + * @client: DRM client
> + * @mode: Display mode to create a buffer for
> + * @format: Buffer format
> + *
> + * This function creates a &drm_client_buffer which consists of a
> + * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf
> + * exported to aquire a virtual address which is stored in
> + * &drm_client_buffer->vaddr.
> + * Call drm_client_framebuffer_delete() to free the buffer.
> + *
> + * Returns:
> + * Pointer to a client buffer or an error pointer on failure.
> + */
> +struct drm_client_buffer *
> +drm_client_framebuffer_create(struct drm_client_dev *client,
> +			      struct drm_mode_modeinfo *mode, u32 format)
> +{
> +	struct drm_client_buffer *buffer;
> +	int ret;
> +
> +	buffer = drm_client_buffer_create(client, mode->hdisplay,
> +					  mode->vdisplay, format);
> +	if (IS_ERR(buffer))
> +		return buffer;
> +
> +	ret = drm_client_buffer_addfb(buffer, mode);
> +	if (ret) {
> +		drm_client_buffer_delete(buffer);
> +		return ERR_PTR(ret);
> +	}
> +
> +	return buffer;
> +}
> +EXPORT_SYMBOL(drm_client_framebuffer_create);
> +
> +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
> +{
> +	drm_client_buffer_rmfb(buffer);
> +	drm_client_buffer_delete(buffer);
> +}
> +EXPORT_SYMBOL(drm_client_framebuffer_delete);
> +
> +struct drm_client_buffer *
> +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
> +			 u32 format)
> +{
> +	struct drm_mode_create_dumb dumb_args = { 0 };
> +	struct drm_prime_handle prime_args = { 0 };
> +	struct drm_client_buffer *buffer;
> +	struct dma_buf *dma_buf;
> +	void *vaddr;
> +	int ret;
> +
> +	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
> +	if (!buffer)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ret = drm_client_get_file(client);
> +	if (ret)
> +		goto err_free;
> +
> +	buffer->client = client;
> +	buffer->width = width;
> +	buffer->height = height;
> +	buffer->format = format;
> +
> +	dumb_args.width = buffer->width;
> +	dumb_args.height = buffer->height;
> +	dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
> +	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
> +	if (ret)
> +		goto err_put_file;
> +
> +	buffer->handle = dumb_args.handle;
> +	buffer->pitch = dumb_args.pitch;
> +	buffer->size = dumb_args.size;
> +
> +	prime_args.handle = dumb_args.handle;
> +	ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file);
> +	if (ret)
> +		goto err_delete;
> +
> +	dma_buf = dma_buf_get(prime_args.fd);
> +	if (IS_ERR(dma_buf)) {
> +		ret = PTR_ERR(dma_buf);
> +		goto err_delete;
> +	}
> +
> +	buffer->dma_buf = dma_buf;
> +
> +	vaddr = dma_buf_vmap(dma_buf);
> +	if (!vaddr) {
> +		ret = -ENOMEM;
> +		goto err_delete;
> +	}
> +
> +	buffer->vaddr = vaddr;
> +
> +	return buffer;
> +
> +err_delete:
> +	drm_client_buffer_delete(buffer);
> +err_put_file:
> +	drm_client_put_file(client);
> +err_free:
> +	kfree(buffer);
> +
> +	return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL(drm_client_buffer_create);
> +
> +void drm_client_buffer_delete(struct drm_client_buffer *buffer)
> +{
> +	if (!buffer)
> +		return;
> +
> +	if (buffer->vaddr)
> +		dma_buf_vunmap(buffer->dma_buf, buffer->vaddr);
> +
> +	if (buffer->dma_buf)
> +		dma_buf_put(buffer->dma_buf);
> +
> +	drm_mode_destroy_dumb(buffer->client->dev, buffer->handle,
> +			      buffer->client->file);
> +	drm_client_put_file(buffer->client);
> +	kfree(buffer);
> +}
> +EXPORT_SYMBOL(drm_client_buffer_delete);
> +
> +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
> +			    struct drm_mode_modeinfo *mode)
> +{
> +	struct drm_client_dev *client = buffer->client;
> +	struct drm_mode_fb_cmd2 fb_req = { };
> +	unsigned int num_fbs, *fb_ids;
> +	int i, ret;
> +
> +	if (buffer->num_fbs)
> +		return -EINVAL;
> +
> +	if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
> +		return -EINVAL;
> +
> +	num_fbs = buffer->height / mode->vdisplay;
> +	fb_ids = kcalloc(num_fbs, sizeof(*fb_ids), GFP_KERNEL);
> +	if (!fb_ids)
> +		return -ENOMEM;
> +
> +	fb_req.width = mode->hdisplay;
> +	fb_req.height = mode->vdisplay;
> +	fb_req.pixel_format = buffer->format;
> +	fb_req.handles[0] = buffer->handle;
> +	fb_req.pitches[0] = buffer->pitch;
> +
> +	for (i = 0; i < num_fbs; i++) {
> +		fb_req.offsets[0] = i * mode->vdisplay * buffer->pitch;
> +		ret = drm_mode_addfb2(client->dev, &fb_req, client->file,
> +				      client->funcs->name);
> +		if (ret)
> +			goto err_remove;
> +		fb_ids[i] = fb_req.fb_id;
> +	}
> +
> +	buffer->fb_ids = fb_ids;
> +	buffer->num_fbs = num_fbs;
> +
> +	return 0;
> +
> +err_remove:
> +	for (i--; i >= 0; i--)
> +		drm_mode_rmfb(client->dev, fb_ids[i], client->file);
> +	kfree(fb_ids);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_buffer_addfb);
> +
> +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	if (!buffer || !buffer->num_fbs)
> +		return 0;
> +
> +	for (i = 0; i < buffer->num_fbs; i++) {
> +		ret = drm_mode_rmfb(buffer->client->dev, buffer->fb_ids[i],
> +				    buffer->client->file);
> +		if (ret)
> +			DRM_DEV_ERROR(buffer->client->dev->dev,
> +				      "Error removing FB:%u (%d)\n",
> +				      buffer->fb_ids[i], ret);
> +	}
> +
> +	kfree(buffer->fb_ids);
> +	buffer->fb_ids = NULL;
> +	buffer->num_fbs = 0;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_client_buffer_rmfb);
> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
> index f869de185986..db161337d87c 100644
> --- a/drivers/gpu/drm/drm_drv.c
> +++ b/drivers/gpu/drm/drm_drv.c
> @@ -33,6 +33,7 @@
>  #include <linux/mount.h>
>  #include <linux/slab.h>
>  
> +#include <drm/drm_client.h>
>  #include <drm/drm_drv.h>
>  #include <drm/drmP.h>
>  
> @@ -463,6 +464,7 @@ int drm_dev_init(struct drm_device *dev,
>  	dev->driver = driver;
>  
>  	INIT_LIST_HEAD(&dev->filelist);
> +	INIT_LIST_HEAD(&dev->filelist_internal);
>  	INIT_LIST_HEAD(&dev->ctxlist);
>  	INIT_LIST_HEAD(&dev->vmalist);
>  	INIT_LIST_HEAD(&dev->maplist);
> @@ -787,6 +789,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
>  		 dev->dev ? dev_name(dev->dev) : "virtual device",
>  		 dev->primary->index);
>  
> +	drm_client_dev_register(dev);
> +
>  	goto out_unlock;
>  
>  err_minors:
> @@ -839,6 +843,8 @@ void drm_dev_unregister(struct drm_device *dev)
>  	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
>  	drm_minor_unregister(dev, DRM_MINOR_RENDER);
>  	drm_minor_unregister(dev, DRM_MINOR_CONTROL);
> +
> +	drm_client_dev_unregister(dev);
>  }
>  EXPORT_SYMBOL(drm_dev_unregister);
>  
> diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
> index 55505378df47..bcc688e58776 100644
> --- a/drivers/gpu/drm/drm_file.c
> +++ b/drivers/gpu/drm/drm_file.c
> @@ -35,6 +35,7 @@
>  #include <linux/slab.h>
>  #include <linux/module.h>
>  
> +#include <drm/drm_client.h>
>  #include <drm/drm_file.h>
>  #include <drm/drmP.h>
>  
> @@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
>  
>  	if (drm_core_check_feature(dev, DRIVER_LEGACY))
>  		drm_legacy_dev_reinit(dev);
> +
> +	drm_client_dev_lastclose(dev);
>  }
>  
>  /**
> diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c
> index 2d1643bdae78..5d2a6c6717f5 100644
> --- a/drivers/gpu/drm/drm_probe_helper.c
> +++ b/drivers/gpu/drm/drm_probe_helper.c
> @@ -33,6 +33,7 @@
>  #include <linux/moduleparam.h>
>  
>  #include <drm/drmP.h>
> +#include <drm/drm_client.h>
>  #include <drm/drm_crtc.h>
>  #include <drm/drm_fourcc.h>
>  #include <drm/drm_crtc_helper.h>
> @@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
>  	drm_sysfs_hotplug_event(dev);
>  	if (dev->mode_config.funcs->output_poll_changed)
>  		dev->mode_config.funcs->output_poll_changed(dev);
> +
> +	drm_client_dev_hotplug(dev);
>  }
>  EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
>  
> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> new file mode 100644
> index 000000000000..88f6f87919c5
> --- /dev/null
> +++ b/include/drm/drm_client.h
> @@ -0,0 +1,192 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#include <linux/mutex.h>
> +
> +struct dma_buf;
> +struct drm_clip_rect;
> +struct drm_device;
> +struct drm_file;
> +struct drm_mode_modeinfo;
> +
> +struct drm_client_dev;
> +
> +/**
> + * struct drm_client_funcs - DRM client  callbacks
> + */
> +struct drm_client_funcs {
> +	/**
> +	 * @name:
> +	 *
> +	 * Name of the client.
> +	 */
> +	const char *name;
> +
> +	/**
> +	 * @new:
> +	 *
> +	 * Called when a client or a &drm_device is registered.
> +	 * If the callback returns anything but zero, then this client instance
> +	 * is dropped.
> +	 *
> +	 * This callback is mandatory.
> +	 */
> +	int (*new)(struct drm_client_dev *client);
> +
> +	/**
> +	 * @remove:
> +	 *
> +	 * Called when a &drm_device is unregistered or the client is
> +	 * unregistered. If zero is returned drm_client_free() is called
> +	 * automatically. If the client can't drop it's resources it should
> +	 * return non-zero and call drm_client_free() later.
> +	 *
> +	 * This callback is optional.
> +	 */
> +	int (*remove)(struct drm_client_dev *client);
> +
> +	/**
> +	 * @lastclose:
> +	 *
> +	 * Called on drm_lastclose(). The first client instance in the list
> +	 * that returns zero gets the privilege to restore and no more clients
> +	 * are called.
> +	 *
> +	 * This callback is optional.
> +	 */
> +	int (*lastclose)(struct drm_client_dev *client);
> +
> +	/**
> +	 * @hotplug:
> +	 *
> +	 * Called on drm_kms_helper_hotplug_event().
> +	 *
> +	 * This callback is optional.
> +	 */
> +	int (*hotplug)(struct drm_client_dev *client);
> +
> +// TODO
> +//	void (*suspend)(struct drm_client_dev *client);
> +//	void (*resume)(struct drm_client_dev *client);
> +};
> +
> +/**
> + * struct drm_client_dev - DRM client instance
> + */
> +struct drm_client_dev {
> +	struct list_head list;
> +	struct drm_device *dev;
> +	const struct drm_client_funcs *funcs;
> +	struct mutex lock;
> +	struct drm_file *file;
> +	unsigned int file_ref_count;
> +	u32 *crtcs;
> +	unsigned int num_crtcs;
> +	u32 min_width;
> +	u32 max_width;
> +	u32 min_height;
> +	u32 max_height;
> +	void *private;
> +};
> +
> +void drm_client_free(struct drm_client_dev *client);
> +int drm_client_register(const struct drm_client_funcs *funcs);
> +void drm_client_unregister(const struct drm_client_funcs *funcs);
> +
> +void drm_client_dev_register(struct drm_device *dev);
> +void drm_client_dev_unregister(struct drm_device *dev);
> +void drm_client_dev_hotplug(struct drm_device *dev);
> +void drm_client_dev_lastclose(struct drm_device *dev);
> +
> +int drm_client_get_file(struct drm_client_dev *client);
> +void drm_client_put_file(struct drm_client_dev *client);
> +struct drm_event *
> +drm_client_read_event(struct drm_client_dev *client, bool block);
> +
> +struct drm_client_connector {
> +	unsigned int conn_id;
> +	unsigned int status;
> +	unsigned int crtc_id;
> +	struct drm_mode_modeinfo *modes;
> +	unsigned int num_modes;
> +	bool has_tile;
> +	int tile_group;
> +	u8 tile_h_loc, tile_v_loc;
> +};
> +
> +struct drm_client_display {
> +	struct drm_client_dev *client;
> +
> +	struct drm_client_connector **connectors;
> +	unsigned int num_connectors;
> +
> +	struct mutex modes_lock;
> +	struct drm_mode_modeinfo *modes;
> +	unsigned int num_modes;
> +
> +	bool cloned;
> +	bool no_flushing;
> +};
> +
> +void drm_client_display_free(struct drm_client_display *display);
> +struct drm_client_display *
> +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict);
> +
> +int drm_client_display_update_modes(struct drm_client_display *display,
> +				    bool *mode_changed);
> +
> +static inline bool
> +drm_client_display_is_tiled(struct drm_client_display *display)
> +{
> +	return !display->cloned && display->num_connectors > 1;
> +}
> +
> +int drm_client_display_dpms(struct drm_client_display *display, int mode);
> +int drm_client_display_wait_vblank(struct drm_client_display *display);
> +
> +struct drm_mode_modeinfo *
> +drm_client_display_first_mode(struct drm_client_display *display);
> +struct drm_mode_modeinfo *
> +drm_client_display_next_mode(struct drm_client_display *display,
> +			     struct drm_mode_modeinfo *mode);
> +
> +#define drm_client_display_for_each_mode(display, mode) \
> +	for (mode = drm_client_display_first_mode(display); mode; \
> +	     mode = drm_client_display_next_mode(display, mode))
> +
> +unsigned int
> +drm_client_display_preferred_depth(struct drm_client_display *display);
> +
> +int drm_client_display_commit_mode(struct drm_client_display *display,
> +				   u32 fb_id, struct drm_mode_modeinfo *mode);
> +unsigned int drm_client_display_current_fb(struct drm_client_display *display);
> +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
> +			     struct drm_clip_rect *clips, unsigned int num_clips);
> +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
> +				 bool event);
> +
> +struct drm_client_buffer {
> +	struct drm_client_dev *client;
> +	u32 width;
> +	u32 height;
> +	u32 format;
> +	u32 handle;
> +	u32 pitch;
> +	u64 size;
> +	struct dma_buf *dma_buf;
> +	void *vaddr;
> +
> +	unsigned int *fb_ids;
> +	unsigned int num_fbs;
> +};
> +
> +struct drm_client_buffer *
> +drm_client_framebuffer_create(struct drm_client_dev *client,
> +			      struct drm_mode_modeinfo *mode, u32 format);
> +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
> +struct drm_client_buffer *
> +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
> +			 u32 format);
> +void drm_client_buffer_delete(struct drm_client_buffer *buffer);
> +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
> +			    struct drm_mode_modeinfo *mode);
> +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer);
> diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
> index 7c4fa32f3fc6..32dfed3d5a86 100644
> --- a/include/drm/drm_device.h
> +++ b/include/drm/drm_device.h
> @@ -67,6 +67,7 @@ struct drm_device {
>  
>  	struct mutex filelist_mutex;
>  	struct list_head filelist;
> +	struct list_head filelist_internal;
>  
>  	/** \name Memory management */
>  	/*@{ */
> diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
> index 5176c3797680..39af8a4be7b3 100644
> --- a/include/drm/drm_file.h
> +++ b/include/drm/drm_file.h
> @@ -248,6 +248,13 @@ struct drm_file {
>  	 */
>  	void *driver_priv;
>  
> +	/**
> +	 * @user_priv:
> +	 *
> +	 * Optional pointer for user private data. Useful for in-kernel clients.
> +	 */
> +	void *user_priv;
> +
>  	/**
>  	 * @fbs:
>  	 *
> -- 
> 2.15.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v3 10/12] drm/client: Add fbdev emulation client
  2018-02-22 20:06 ` [RFC v3 10/12] drm/client: Add fbdev emulation client Noralf Trønnes
@ 2018-03-06  9:06   ` Daniel Vetter
  0 siblings, 0 replies; 26+ messages in thread
From: Daniel Vetter @ 2018-03-06  9:06 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart

On Thu, Feb 22, 2018 at 09:06:51PM +0100, Noralf Trønnes wrote:
> This adds generic fbdev emulation for drivers that support the
> dumb buffer API. No fbdev code is necessary in the driver.
> 
> Differences from drm_fb_helper:
> - The backing buffer is created when the first fd is opened.
> - Supports changing the mode from userspace.
> - Doesn't restore on lastclose if there is no fd/fbcon open.
> - Supports changing the buffer size (yres_virtual) from userspace before
>   the fd is opened (double/trippel/... buffering).
> - Panning is only supported as page flipping, so no partial offset.
> - Supports real page flipping with FBIO_WAITFORVSYNC waiting on the
>   actual flip.
> - Supports framebuffer flushing for fbcon on buffers that doesn't support
>   fbdev deferred I/O (shmem). mmap doesn't work but fbcon does.
> 
> TODO:
> - suspend/resume
> - sysrq
> - Look more into plane format selection/support.
> - Need a way for the driver to say that it wants generic fbdev emulation.
>   The client .new hook is run in drm_dev_register() which is before
>   drivers set up fbdev themselves. So the client can't look at
>   drm_device->fb_helper to find out.

For fbdev I think the better option (given where we are right now) is to
integrate the generic buffer allocation support into the existing fbdev
helpers, as a default fb_probe implementation that allocates a suitable
buffer.

This has a bunch of upsides:
- integrates with existing s/r support
- integrates with existing driver load sequence, avoiding bugs and stuff
- integrates with existing module options and similar to disable fbdev
  (although they're all now generic and should be using the one Kconfig
  option we have for that).

Furthermore this gives drivers more flexibility of still overwriting parts
of the fbdev helpers. This is useful for drivers with a dirtyfb hook:
Because of the rather nasty nature of the defio tracking required by fbdev
mmap semantics it's better to disable dirtyfb for fbdev emulation (if
possible by the hardware), even if that costs some performance/incrases
power consumption. This is e.g. what we do for i915 - we simply disable
manual upload when switching to fbdev.

Of course for your panels you always need manual upload, so not a concern.

Reusing the existing fbdev code also means better continuity and
bisectability, which is Always Good (tm) in kernel land :-)

The only downside is that there's a bit more layering and it will take us
a bit longer until we reach the perfectly clean world for most drivers,
but that's just the usual price to pay.

For the other/new clients (bootsplash) I think using your ->new hooks
makes perfect sense.

> - Do we need to support FB_VISUAL_PSEUDOCOLOR?
> 
> TROUBLE:
> - fbcon can't handle fb_open returning an error, it just heads on. This
>   results in a NULL deref in fbcon_init(). fbcon/vt is awful when it
>   comes to error handling. It doesn't look to be easily fixed, so I guess
>   a buffer has to be pre-allocated to ensure health and safety.

Yup, we need to stick to the exact same logic as the current fbdev helpers
of allocating at init time, and then reusing the same buffer for
everything. The mmap stuff should all still work.
-Daniel

> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  drivers/gpu/drm/client/Kconfig     |  16 +
>  drivers/gpu/drm/client/Makefile    |   2 +
>  drivers/gpu/drm/client/drm_fbdev.c | 997 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 1015 insertions(+)
>  create mode 100644 drivers/gpu/drm/client/drm_fbdev.c
> 
> diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
> index 4bb8e4655ff7..73902ab44c75 100644
> --- a/drivers/gpu/drm/client/Kconfig
> +++ b/drivers/gpu/drm/client/Kconfig
> @@ -1,4 +1,20 @@
>  menu "DRM Clients"
>  	depends on DRM
>  
> +config DRM_CLIENT_FBDEV
> +	tristate "Generic fbdev emulation"
> +	depends on DRM
> +	select FB
> +	select FRAMEBUFFER_CONSOLE if !EXPERT
> +	select FRAMEBUFFER_CONSOLE_DETECT_PRIMARY if FRAMEBUFFER_CONSOLE
> +	select FB_SYS_FOPS
> +	select FB_SYS_FILLRECT
> +	select FB_SYS_COPYAREA
> +	select FB_SYS_IMAGEBLIT
> +	select FB_DEFERRED_IO
> +	select FB_MODE_HELPERS
> +	select VIDEOMODE_HELPERS
> +	help
> +	  Generic fbdev emulation
> +
>  endmenu
> diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
> index f66554cd5c45..3ff694429dec 100644
> --- a/drivers/gpu/drm/client/Makefile
> +++ b/drivers/gpu/drm/client/Makefile
> @@ -1 +1,3 @@
>  # SPDX-License-Identifier: GPL-2.0
> +
> +obj-$(CONFIG_DRM_CLIENT_FBDEV) += drm_fbdev.o
> diff --git a/drivers/gpu/drm/client/drm_fbdev.c b/drivers/gpu/drm/client/drm_fbdev.c
> new file mode 100644
> index 000000000000..e28416d72de1
> --- /dev/null
> +++ b/drivers/gpu/drm/client/drm_fbdev.c
> @@ -0,0 +1,997 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright 2018 Noralf Trønnes
> +
> +#include <linux/console.h>
> +#include <linux/dma-buf.h>
> +#include <linux/fb.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.h>
> +#include <video/videomode.h>
> +
> +#include <drm/drm_client.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_print.h>
> +
> +struct drm_fbdev {
> +	struct mutex lock;
> +
> +	struct drm_client_dev *client;
> +	struct drm_client_display *display;
> +
> +	unsigned int open_count;
> +	struct drm_client_buffer *buffer;
> +	bool page_flip_sent;
> +	u32 curr_fb;
> +
> +	struct fb_info *info;
> +	u32 pseudo_palette[17];
> +
> +	bool flush;
> +	bool defio_no_flushing;
> +	struct drm_clip_rect dirty_clip;
> +	spinlock_t dirty_lock;
> +	struct work_struct dirty_work;
> +};
> +
> +static int drm_fbdev_mode_to_fb_mode(struct drm_device *dev,
> +				     struct drm_mode_modeinfo *mode,
> +				     struct fb_videomode *fb_mode)
> +{
> +	struct drm_display_mode display_mode = { };
> +	struct videomode videomode = { };
> +	int ret;
> +
> +	ret = drm_mode_convert_umode(dev, &display_mode, mode);
> +	if (ret)
> +		return ret;
> +
> +	memset(fb_mode, 0, sizeof(*fb_mode));
> +	drm_display_mode_to_videomode(&display_mode, &videomode);
> +	fb_videomode_from_videomode(&videomode, fb_mode);
> +
> +	return 0;
> +}
> +
> +static void drm_fbdev_destroy_modelist(struct fb_info *info)
> +{
> +	struct fb_modelist *modelist, *tmp;
> +
> +	list_for_each_entry_safe(modelist, tmp, &info->modelist, list) {
> +		kfree(modelist->mode.name);
> +		list_del(&modelist->list);
> +		kfree(modelist);
> +	}
> +}
> +
> +static void drm_fbdev_use_first_mode(struct fb_info *info)
> +{
> +	struct fb_modelist *modelist;
> +
> +	modelist = list_first_entry(&info->modelist, struct fb_modelist, list);
> +	fb_videomode_to_var(&info->var, &modelist->mode);
> +	info->mode = &modelist->mode;
> +}
> +
> +static struct drm_mode_modeinfo *drm_fbdev_get_drm_mode(struct drm_fbdev *fbdev)
> +{
> +	struct drm_mode_modeinfo *mode_pos, *mode = NULL;
> +	struct fb_info *info = fbdev->info;
> +	struct fb_videomode tmp;
> +
> +	mutex_lock(&fbdev->display->modes_lock);
> +	drm_client_display_for_each_mode(fbdev->display, mode_pos) {
> +		if (drm_fbdev_mode_to_fb_mode(fbdev->client->dev, mode_pos, &tmp))
> +			continue;
> +		if (fb_mode_is_equal(info->mode, &tmp)) {
> +			mode = mode_pos;
> +			break;
> +		}
> +	}
> +	mutex_unlock(&fbdev->display->modes_lock);
> +
> +	return mode;
> +}
> +
> +/* Return number of modes or negative error */
> +static int drm_fbdev_sync_modes(struct drm_fbdev *fbdev, bool force)
> +{
> +	struct fb_info *info = fbdev->info;
> +	struct drm_mode_modeinfo *mode;
> +	struct fb_videomode fb_mode;
> +	bool changed;
> +
> +	struct fb_modelist *fbdev_modelist;
> +	int num_modes;
> +
> +	num_modes = drm_client_display_update_modes(fbdev->display, &changed);
> +	if (num_modes <= 0)
> +		return num_modes;
> +
> +	if (!info)
> +		return num_modes;
> +
> +	if (!force && !changed)
> +		return num_modes;
> +
> +	drm_fbdev_destroy_modelist(info);
> +
> +	mutex_lock(&fbdev->display->modes_lock);
> +	drm_client_display_for_each_mode(fbdev->display, mode) {
> +		if (drm_fbdev_mode_to_fb_mode(fbdev->client->dev, mode, &fb_mode)) {
> +			num_modes--;
> +			continue;
> +		}
> +
> +		fbdev_modelist = kzalloc(sizeof(*fbdev_modelist), GFP_KERNEL);
> +		if (!fbdev_modelist) {
> +			drm_fbdev_destroy_modelist(info);
> +			mutex_unlock(&fbdev->display->modes_lock);
> +			return -ENOMEM;
> +		}
> +
> +		fbdev_modelist->mode = fb_mode;
> +		fbdev_modelist->mode.name = kstrndup(mode->name,
> +						     DRM_DISPLAY_MODE_LEN,
> +						     GFP_KERNEL);
> +
> +		if (mode->type & DRM_MODE_TYPE_PREFERRED)
> +			fbdev_modelist->mode.flag |= FB_MODE_IS_FIRST;
> +
> +		list_add_tail(&fbdev_modelist->list, &info->modelist);
> +	}
> +	mutex_unlock(&fbdev->display->modes_lock);
> +
> +	if (!fbdev->open_count)
> +		drm_fbdev_use_first_mode(info);
> +
> +	return num_modes;
> +}
> +
> +static void drm_fbdev_format_fill_var(u32 format, struct fb_var_screeninfo *var)
> +{
> +	switch (format) {
> +	case DRM_FORMAT_XRGB1555:
> +		var->red.offset = 10;
> +		var->red.length = 5;
> +		var->green.offset = 5;
> +		var->green.length = 5;
> +		var->blue.offset = 0;
> +		var->blue.length = 5;
> +		var->transp.offset = 0;
> +		var->transp.length = 0;
> +		break;
> +	case DRM_FORMAT_ARGB1555:
> +		var->red.offset = 10;
> +		var->red.length = 5;
> +		var->green.offset = 5;
> +		var->green.length = 5;
> +		var->blue.offset = 0;
> +		var->blue.length = 5;
> +		var->transp.offset = 15;
> +		var->transp.length = 1;
> +		break;
> +	case DRM_FORMAT_RGB565:
> +		var->red.offset = 11;
> +		var->red.length = 5;
> +		var->green.offset = 5;
> +		var->green.length = 6;
> +		var->blue.offset = 0;
> +		var->blue.length = 5;
> +		var->transp.offset = 0;
> +		var->transp.length = 0;
> +		break;
> +	case DRM_FORMAT_RGB888:
> +	case DRM_FORMAT_XRGB8888:
> +		var->red.offset = 16;
> +		var->red.length = 8;
> +		var->green.offset = 8;
> +		var->green.length = 8;
> +		var->blue.offset = 0;
> +		var->blue.length = 8;
> +		var->transp.offset = 0;
> +		var->transp.length = 0;
> +		break;
> +	case DRM_FORMAT_ARGB8888:
> +		var->red.offset = 16;
> +		var->red.length = 8;
> +		var->green.offset = 8;
> +		var->green.length = 8;
> +		var->blue.offset = 0;
> +		var->blue.length = 8;
> +		var->transp.offset = 24;
> +		var->transp.length = 8;
> +		break;
> +	default:
> +		WARN_ON_ONCE(1);
> +		return;
> +	}
> +
> +	var->colorspace = 0;
> +	var->grayscale = 0;
> +	var->nonstd = 0;
> +}
> +
> +int drm_fbdev_var_to_format(struct fb_var_screeninfo *var, u32 *format)
> +{
> +	switch (var->bits_per_pixel) {
> +	case 15:
> +		*format = DRM_FORMAT_ARGB1555;
> +		break;
> +	case 16:
> +		if (var->green.length != 5)
> +			*format = DRM_FORMAT_RGB565;
> +		else if (var->transp.length > 0)
> +			*format = DRM_FORMAT_ARGB1555;
> +		else
> +			*format = DRM_FORMAT_XRGB1555;
> +		break;
> +	case 24:
> +		*format = DRM_FORMAT_RGB888;
> +		break;
> +	case 32:
> +		if (var->transp.length > 0)
> +			*format = DRM_FORMAT_ARGB8888;
> +		else
> +			*format = DRM_FORMAT_XRGB8888;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static void drm_fbdev_dirty_work(struct work_struct *work)
> +{
> +	struct drm_fbdev *fbdev = container_of(work, struct drm_fbdev,
> +					       dirty_work);
> +	struct drm_clip_rect *clip = &fbdev->dirty_clip;
> +	struct drm_clip_rect clip_copy;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&fbdev->dirty_lock, flags);
> +	clip_copy = *clip;
> +	clip->x1 = clip->y1 = ~0;
> +	clip->x2 = clip->y2 = 0;
> +	spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
> +
> +	/* call dirty callback only when it has been really touched */
> +	if (clip_copy.x1 < clip_copy.x2 && clip_copy.y1 < clip_copy.y2)
> +		drm_client_display_flush(fbdev->display, fbdev->curr_fb,
> +					 &clip_copy, 1);
> +}
> +
> +static void drm_fbdev_dirty(struct fb_info *info, u32 x, u32 y,
> +			    u32 width, u32 height)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +	struct drm_clip_rect *clip = &fbdev->dirty_clip;
> +	unsigned long flags;
> +
> +	if (!fbdev->flush)
> +		return;
> +
> +	spin_lock_irqsave(&fbdev->dirty_lock, flags);
> +	clip->x1 = min_t(u32, clip->x1, x);
> +	clip->y1 = min_t(u32, clip->y1, y);
> +	clip->x2 = max_t(u32, clip->x2, x + width);
> +	clip->y2 = max_t(u32, clip->y2, y + height);
> +	spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
> +
> +	schedule_work(&fbdev->dirty_work);
> +}
> +
> +static void drm_fbdev_deferred_io(struct fb_info *info,
> +				  struct list_head *pagelist)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +	unsigned long start, end, min, max;
> +	struct page *page;
> +	u32 y1, y2;
> +
> +	/* Is userspace doing explicit pageflip flushing? */
> +	if (fbdev->defio_no_flushing)
> +		return;
> +
> +	min = ULONG_MAX;
> +	max = 0;
> +	list_for_each_entry(page, pagelist, lru) {
> +		start = page->index << PAGE_SHIFT;
> +		end = start + PAGE_SIZE;
> +		min = min(min, start);
> +		max = max(max, end);
> +	}
> +
> +	if (min < max) {
> +		y1 = min / info->fix.line_length;
> +		y2 = DIV_ROUND_UP(max, info->fix.line_length);
> +		y2 = min(y2, info->var.yres);
> +		drm_fbdev_dirty(info, 0, y1, info->var.xres, y2 - y1);
> +	}
> +}
> +
> +static struct fb_deferred_io drm_fbdev_fbdefio = {
> +	.delay		= HZ / 20,
> +	.deferred_io	= drm_fbdev_deferred_io,
> +};
> +
> +static int
> +drm_fbdev_fb_mmap_notsupp(struct fb_info *info, struct vm_area_struct *vma)
> +{
> +	return -ENOTSUPP;
> +}
> +
> +static void drm_fbdev_delete_buffer(struct drm_fbdev *fbdev)
> +{
> +	struct fb_info *info = fbdev->info;
> +
> +	if (info->fbdefio) {
> +		/* Stop worker and clear page->mapping */
> +		fb_deferred_io_cleanup(info);
> +		info->fbdefio = NULL;
> +	}
> +	if (fbdev->flush) {
> +		fbdev->flush = false;
> +		cancel_work_sync(&fbdev->dirty_work);
> +	}
> +
> +	drm_client_buffer_rmfb(fbdev->buffer);
> +	drm_client_buffer_delete(fbdev->buffer);
> +
> +	fbdev->buffer = NULL;
> +	fbdev->curr_fb = 0;
> +	fbdev->page_flip_sent = false;
> +	info->screen_buffer = NULL;
> +	info->screen_size = 0;
> +	info->fix.smem_len = 0;
> +	info->fix.line_length = 0;
> +}
> +
> +/* Temporary hack to make tinydrm work before converting to vmalloc buffers */
> +static int drm_fbdev_cma_deferred_io_mmap(struct fb_info *info,
> +					  struct vm_area_struct *vma)
> +{
> +	fb_deferred_io_mmap(info, vma);
> +	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
> +
> +	return 0;
> +}
> +
> +static int drm_fbdev_create_buffer(struct drm_fbdev *fbdev)
> +{
> +	struct drm_client_dev *client = fbdev->client;
> +	struct fb_info *info = fbdev->info;
> +	struct drm_client_buffer *buffer;
> +	struct drm_mode_modeinfo *mode;
> +	u32 format;
> +	int ret;
> +
> +	ret = drm_fbdev_var_to_format(&info->var, &format);
> +	if (ret)
> +		return ret;
> +
> +	buffer = drm_client_buffer_create(client, info->var.xres_virtual,
> +					  info->var.yres_virtual, format);
> +	if (IS_ERR(buffer))
> +		return PTR_ERR(buffer);
> +
> +	mode = drm_fbdev_get_drm_mode(fbdev);
> +	if (!mode)
> +		return -EINVAL;
> +
> +	ret = drm_client_buffer_addfb(buffer, mode);
> +	if (ret)
> +		goto err_free_buffer;
> +
> +	fbdev->curr_fb = buffer->fb_ids[0];
> +
> +	if (drm_mode_can_dirtyfb(client->dev, fbdev->curr_fb, client->file)) {
> +		fbdev->flush = true;
> +/*		if (is_vmalloc_addr(buffer->vaddr)) { */
> +/* Temporary hack for testing on tinydrm before it has moved to vmalloc */
> +		if (1) {
> +			fbdev->dirty_clip.x1 = fbdev->dirty_clip.y1 = ~0;
> +			fbdev->dirty_clip.x2 = fbdev->dirty_clip.y2 = 0;
> +			info->fbdefio = &drm_fbdev_fbdefio;
> +
> +			/* tinydrm hack */
> +			info->fix.smem_start = page_to_phys(virt_to_page(buffer->vaddr));
> +
> +			fb_deferred_io_init(info);
> +			/* tinydrm hack */
> +			info->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap;
> +		} else {
> +			info->fbops->fb_mmap = drm_fbdev_fb_mmap_notsupp;
> +		}
> +	}
> +
> +	fbdev->buffer = buffer;
> +	info->screen_buffer = buffer->vaddr;
> +	info->screen_size = buffer->size;
> +	info->fix.smem_len = buffer->size;
> +	info->fix.line_length = buffer->pitch;
> +
> +	return 0;
> +
> +err_free_buffer:
> +	drm_client_buffer_delete(buffer);
> +
> +	return ret;
> +}
> +
> +static int drm_fbdev_fb_open(struct fb_info *info, int user)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +	int ret = 0;
> +
> +	DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
> +
> +	mutex_lock(&fbdev->lock);
> +
> +	if (!fbdev->display) {
> +		ret = -ENODEV;
> +		goto out_unlock;
> +	}
> +
> +	if (!fbdev->open_count) {
> +		/* Pipeline is disabled, make sure it's forced on */
> +		info->var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
> +		ret = drm_fbdev_create_buffer(fbdev);
> +		if (ret)
> +			goto out_unlock;
> +	}
> +
> +	fbdev->open_count++;
> +
> +out_unlock:
> +	mutex_unlock(&fbdev->lock);
> +
> +	if (ret)
> +		DRM_DEV_ERROR(fbdev->client->dev->dev, "fb_open failed (%d)\n", ret);
> +
> +	return ret;
> +}
> +
> +static int drm_fbdev_fb_release(struct fb_info *info, int user)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +
> +	DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
> +	mutex_lock(&fbdev->lock);
> +
> +	if (--fbdev->open_count == 0) {
> +		drm_client_display_dpms(fbdev->display, DRM_MODE_DPMS_OFF);
> +		drm_fbdev_delete_buffer(fbdev);
> +	}
> +
> +	fbdev->defio_no_flushing = false;
> +
> +	mutex_unlock(&fbdev->lock);
> +
> +	return 0;
> +}
> +
> +static ssize_t drm_fbdev_fb_write(struct fb_info *info, const char __user *buf,
> +				  size_t count, loff_t *ppos)
> +{
> +	ssize_t ret;
> +
> +	ret = fb_sys_write(info, buf, count, ppos);
> +	if (ret > 0)
> +		drm_fbdev_dirty(info, 0, 0, info->var.xres, info->var.yres);
> +
> +	return ret;
> +}
> +
> +static void
> +drm_fbdev_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
> +{
> +	sys_fillrect(info, rect);
> +	drm_fbdev_dirty(info, rect->dx, rect->dy, rect->width, rect->height);
> +}
> +
> +static void
> +drm_fbdev_fb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
> +{
> +	sys_copyarea(info, area);
> +	drm_fbdev_dirty(info, area->dx, area->dy, area->width, area->height);
> +}
> +
> +static void
> +drm_fbdev_fb_imageblit(struct fb_info *info, const struct fb_image *image)
> +{
> +	sys_imageblit(info, image);
> +	drm_fbdev_dirty(info, image->dx, image->dy, image->width, image->height);
> +}
> +
> +static int
> +drm_fbdev_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
> +{
> +	u32 new_format, old_format, yres_virtual;
> +	struct drm_fbdev *fbdev = info->par;
> +	const struct fb_videomode *fb_mode;
> +	bool is_open;
> +	int ret;
> +
> +	mutex_lock(&fbdev->lock);
> +	is_open = fbdev->open_count;
> +	mutex_unlock(&fbdev->lock);
> +
> +	if (!is_open && in_dbg_master())
> +		return -EINVAL;
> +
> +	/* Can be called from sysfs */
> +	if (is_open && (var->xres_virtual > fbdev->buffer->width ||
> +	    var->yres_virtual > fbdev->buffer->height)) {
> +		DRM_DEBUG_KMS("Cannot increase virtual resolution while open\n");
> +		return -EBUSY;
> +	}
> +
> +	if (var->xres > var->xres_virtual || var->yres > var->yres_virtual) {
> +		DRM_DEBUG_KMS("Requested width/height to big: %dx%d > virtual %dx%d\n",
> +			      var->xres, var->yres, var->xres_virtual,
> +			      var->yres_virtual);
> +		return -EINVAL;
> +	}
> +
> +	ret = drm_fbdev_var_to_format(var, &new_format);
> +	if (ret) {
> +		DRM_DEBUG_KMS("Unsupported format\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = drm_fbdev_var_to_format(&info->var, &old_format);
> +	if (ret)
> +		return ret;
> +
> +	if (new_format != old_format && is_open) {
> +		DRM_DEBUG_KMS("Cannot change format while open\n");
> +		return -EBUSY;
> +	}
> +
> +	drm_fbdev_format_fill_var(new_format, var);
> +
> +	fb_mode = fb_find_best_mode(var, &info->modelist);
> +	if (!fb_mode)
> +		return -EINVAL;
> +
> +	yres_virtual = var->yres_virtual;
> +	fb_videomode_to_var(var, fb_mode);
> +	var->yres_virtual = yres_virtual;
> +
> +	return 0;
> +}
> +
> +static int drm_fbdev_fb_set_par(struct fb_info *info)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +	const struct fb_videomode *fb_mode;
> +	struct drm_mode_modeinfo *mode;
> +	bool mode_changed;
> +	int ret;
> +
> +	mutex_lock(&fbdev->lock);
> +
> +	if (!fbdev->open_count) {
> +		ret = 0;
> +		goto out_unlock;
> +	}
> +
> +	fb_mode = fb_match_mode(&info->var, &info->modelist);
> +	if (!fb_mode) {
> +		DRM_DEBUG_KMS("Couldn't find var mode\n");
> +		ret = -EINVAL;
> +		goto out_unlock;
> +	}
> +
> +	mode_changed = !fb_mode_is_equal(info->mode, fb_mode);
> +	info->mode = (struct fb_videomode *)fb_mode;
> +
> +	mode = drm_fbdev_get_drm_mode(fbdev);
> +	if (!mode) {
> +		DRM_DEBUG_KMS("Couldn't find the matching DRM mode\n");
> +		ret = -EINVAL;
> +		goto out_unlock;
> +	}
> +
> +	if (mode_changed) {
> +		drm_client_buffer_rmfb(fbdev->buffer);
> +		fbdev->curr_fb = 0;
> +		ret = drm_client_buffer_addfb(fbdev->buffer, mode);
> +		if (ret)
> +			goto out_unlock;
> +
> +		fbdev->curr_fb = fbdev->buffer->fb_ids[0];
> +		info->var.yoffset = 0;
> +	}
> +
> +//	info->var.width = drm_mode->width_mm;
> +//	info->var.height = drm_mode->height_mm;
> +
> +	/* Panning is only supported to do page flipping */
> +	info->fix.ypanstep = info->var.yres;
> +
> +	ret = drm_client_display_commit_mode(fbdev->display, fbdev->curr_fb, mode);
> +
> +out_unlock:
> +	mutex_unlock(&fbdev->lock);
> +
> +	return ret;
> +}
> +
> +/*
> + * Do we need to support FB_VISUAL_PSEUDOCOLOR?
> +
> +static int drm_fbdev_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
> +		    unsigned blue, unsigned transp, struct fb_info *info)
> +{
> +
> +}
> +*/
> +
> +static int setcmap_pseudo_palette(struct fb_cmap *cmap, struct fb_info *info)
> +{
> +	u32 *palette = (u32 *)info->pseudo_palette;
> +	int i;
> +
> +	if (cmap->start + cmap->len > 16)
> +		return -EINVAL;
> +
> +	for (i = 0; i < cmap->len; ++i) {
> +		u16 red = cmap->red[i];
> +		u16 green = cmap->green[i];
> +		u16 blue = cmap->blue[i];
> +		u32 value;
> +
> +		red >>= 16 - info->var.red.length;
> +		green >>= 16 - info->var.green.length;
> +		blue >>= 16 - info->var.blue.length;
> +		value = (red << info->var.red.offset) |
> +			(green << info->var.green.offset) |
> +			(blue << info->var.blue.offset);
> +		if (info->var.transp.length > 0) {
> +			u32 mask = (1 << info->var.transp.length) - 1;
> +
> +			mask <<= info->var.transp.offset;
> +			value |= mask;
> +		}
> +		palette[cmap->start + i] = value;
> +	}
> +
> +	return 0;
> +}
> +
> +static int drm_fbdev_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
> +{
> +	if (oops_in_progress)
> +		return -EBUSY;
> +
> +	if (info->fix.visual == FB_VISUAL_TRUECOLOR)
> +		return setcmap_pseudo_palette(cmap, info);
> +
> +	return -EINVAL;
> +}
> +
> +static int drm_fbdev_fb_blank(int blank, struct fb_info *info)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +	bool is_open;
> +	int mode;
> +
> +	if (oops_in_progress)
> +		return -EBUSY;
> +
> +	mutex_lock(&fbdev->lock);
> +	is_open = fbdev->open_count;
> +	mutex_unlock(&fbdev->lock);
> +
> +	if (!is_open)
> +		return -EINVAL;
> +
> +	if (blank == FB_BLANK_UNBLANK)
> +		mode = DRM_MODE_DPMS_ON;
> +	else
> +		mode = DRM_MODE_DPMS_OFF;
> +
> +	return drm_client_display_dpms(fbdev->display, mode);
> +}
> +
> +static int
> +drm_fbdev_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +	struct drm_event *event;
> +	unsigned int fb_idx;
> +	int ret = 0;
> +
> +	mutex_lock(&fbdev->lock);
> +
> +	if (!fbdev->open_count)
> +		goto out_unlock;
> +
> +	fb_idx = var->yoffset / info->var.yres;
> +	if (fb_idx >= fbdev->buffer->num_fbs) {
> +		ret = -EINVAL;
> +		goto out_unlock;
> +	}
> +
> +	/* Drain previous flip event if userspace didn't care */
> +	if (fbdev->page_flip_sent) {
> +		event = drm_client_read_event(fbdev->client, false);
> +		if (!IS_ERR(event))
> +			kfree(event);
> +		fbdev->page_flip_sent = false;
> +	}
> +
> +	if (fbdev->curr_fb == fbdev->buffer->fb_ids[fb_idx])
> +		goto out_unlock;
> +
> +	fbdev->curr_fb = fbdev->buffer->fb_ids[fb_idx];
> +	fbdev->defio_no_flushing = true;
> +
> +	ret = drm_client_display_page_flip(fbdev->display, fbdev->curr_fb, true);
> +	if (ret)
> +		goto out_unlock;
> +
> +	fbdev->page_flip_sent = true;
> +
> +out_unlock:
> +	mutex_unlock(&fbdev->lock);
> +
> +	return ret;
> +}
> +
> +static int drm_fbdev_fb_ioctl(struct fb_info *info, unsigned int cmd,
> +			      unsigned long arg)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +	struct drm_event *event;
> +	bool page_flip_sent;
> +	int ret = 0;
> +
> +	switch (cmd) {
> +//	case FBIOGET_VBLANK:
> +//		break;
> +	case FBIO_WAITFORVSYNC:
> +		mutex_lock(&fbdev->lock);
> +		page_flip_sent = fbdev->page_flip_sent;
> +		fbdev->page_flip_sent = false;
> +		mutex_unlock(&fbdev->lock);
> +
> +		if (page_flip_sent) {
> +			event = drm_client_read_event(fbdev->client, true);
> +			if (IS_ERR(event))
> +				ret = PTR_ERR(event);
> +			else
> +				kfree(event);
> +		} else {
> +			drm_client_display_wait_vblank(fbdev->display);
> +		}
> +
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +	}
> +
> +	return ret;
> +}
> +
> +static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +
> +	return dma_buf_mmap(fbdev->buffer->dma_buf, vma, 0);
> +}
> +
> +static void drm_fbdev_fb_destroy(struct fb_info *info)
> +{
> +	struct drm_fbdev *fbdev = info->par;
> +
> +	DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
> +	drm_client_display_free(fbdev->display);
> +	drm_client_free(fbdev->client);
> +	kfree(fbdev);
> +}
> +
> +static struct fb_ops drm_fbdev_fb_ops = {
> +	.owner		= THIS_MODULE,
> +	.fb_open	= drm_fbdev_fb_open,
> +	.fb_release	= drm_fbdev_fb_release,
> +	.fb_read	= fb_sys_read,
> +	.fb_write	= drm_fbdev_fb_write,
> +	.fb_check_var	= drm_fbdev_fb_check_var,
> +	.fb_set_par	= drm_fbdev_fb_set_par,
> +//	.fb_setcolreg	= drm_fbdev_fb_setcolreg,
> +	.fb_setcmap	= drm_fbdev_fb_setcmap,
> +	.fb_blank	= drm_fbdev_fb_blank,
> +	.fb_pan_display	= drm_fbdev_fb_pan_display,
> +	.fb_fillrect	= drm_fbdev_fb_fillrect,
> +	.fb_copyarea	= drm_fbdev_fb_copyarea,
> +	.fb_imageblit	= drm_fbdev_fb_imageblit,
> +	.fb_ioctl	= drm_fbdev_fb_ioctl,
> +	.fb_mmap	= drm_fbdev_fb_mmap,
> +	.fb_destroy	= drm_fbdev_fb_destroy,
> +};
> +
> +static int drm_fbdev_register_framebuffer(struct drm_fbdev *fbdev)
> +{
> +	struct drm_client_display *display;
> +	struct fb_info *info;
> +	struct fb_ops *fbops;
> +	u32 format;
> +	int ret;
> +
> +	display = drm_client_display_get_first_enabled(fbdev->client, false);
> +	if (IS_ERR_OR_NULL(display))
> +		return PTR_ERR_OR_ZERO(display);
> +
> +	fbdev->display = display;
> +
> +	/*
> +	 * fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per instance
> +	 * version is necessary. We do it for all users since we don't know
> +	 * yet if the fb has a dirty callback.
> +	 */
> +	fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
> +	if (!fbops) {
> +		ret = -ENOMEM;
> +		goto err_free;
> +	}
> +
> +	*fbops = drm_fbdev_fb_ops;
> +
> +	info = framebuffer_alloc(0, fbdev->client->dev->dev);
> +	if (!info) {
> +		ret = -ENOMEM;
> +		goto err_free;
> +	}
> +
> +	ret = fb_alloc_cmap(&info->cmap, 256, 0);
> +	if (ret)
> +		goto err_release;
> +
> +	info->par = fbdev;
> +	info->fbops = fbops;
> +	INIT_LIST_HEAD(&info->modelist);
> +	info->pseudo_palette = fbdev->pseudo_palette;
> +
> +	info->fix.type = FB_TYPE_PACKED_PIXELS;
> +	info->fix.visual = FB_VISUAL_TRUECOLOR;
> +	info->fix.ypanstep = info->var.yres;
> +
> +	strcpy(info->fix.id, "DRM emulated");
> +
> +	fbdev->info = info;
> +	ret = drm_fbdev_sync_modes(fbdev, true);
> +	if (ret < 0)
> +		goto err_free_cmap;
> +
> +	info->var.bits_per_pixel = drm_client_display_preferred_depth(fbdev->display);
> +	ret = drm_fbdev_var_to_format(&info->var, &format);
> +	if (ret) {
> +		DRM_WARN("Unsupported bpp, assuming x8r8g8b8 pixel format\n");
> +		format = DRM_FORMAT_XRGB8888;
> +	}
> +	drm_fbdev_format_fill_var(format, &info->var);
> +
> +	info->var.xres_virtual = info->var.xres;
> +	info->var.yres_virtual = info->var.yres;
> +
> +	info->var.yres_virtual *= CONFIG_DRM_FBDEV_OVERALLOC;
> +	info->var.yres_virtual /= 100;
> +
> +	ret = register_framebuffer(info);
> +	if (ret)
> +		goto err_free_cmap;
> +
> +	dev_info(fbdev->client->dev->dev, "fb%d: %s frame buffer device\n",
> +		 info->node, info->fix.id);
> +
> +	return 0;
> +
> +err_free_cmap:
> +	fb_dealloc_cmap(&info->cmap);
> +err_release:
> +	framebuffer_release(info);
> +err_free:
> +	kfree(fbops);
> +	fbdev->info = NULL;
> +	drm_client_display_free(fbdev->display);
> +	fbdev->display = NULL;
> +
> +	return ret;
> +}
> +
> +static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
> +{
> +	struct drm_fbdev *fbdev = client->private;
> +	int ret;
> +
> +	if (!fbdev->info)
> +		ret = drm_fbdev_register_framebuffer(fbdev);
> +	else
> +		ret = drm_fbdev_sync_modes(fbdev, false);
> +
> +	return ret;
> +}
> +
> +static int drm_fbdev_client_new(struct drm_client_dev *client)
> +{
> +	struct drm_fbdev *fbdev;
> +
> +	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
> +	if (!fbdev)
> +		return -ENOMEM;
> +
> +	mutex_init(&fbdev->lock);
> +	spin_lock_init(&fbdev->dirty_lock);
> +	INIT_WORK(&fbdev->dirty_work, drm_fbdev_dirty_work);
> +
> +	fbdev->client = client;
> +	client->private = fbdev;
> +
> +	/*
> +	 * vc4 isn't done with it's setup when drm_dev_register() is called.
> +	 * It should have shouldn't it?
> +	 * So to keep it from crashing defer setup to hotplug...
> +	 */
> +	if (client->dev->mode_config.max_width)
> +		drm_fbdev_client_hotplug(client);
> +
> +	return 0;
> +}
> +
> +static int drm_fbdev_client_remove(struct drm_client_dev *client)
> +{
> +	struct drm_fbdev *fbdev = client->private;
> +
> +	if (!fbdev->info) {
> +		kfree(fbdev);
> +		return 0;
> +	}
> +
> +	unregister_framebuffer(fbdev->info);
> +
> +	/* drm_fbdev_fb_destroy() frees the client */
> +	return 1;
> +}
> +
> +static int drm_fbdev_client_lastclose(struct drm_client_dev *client)
> +{
> +	struct drm_fbdev *fbdev = client->private;
> +	int ret = -ENOENT;
> +
> +	if (fbdev->info)
> +		ret = fbdev->info->fbops->fb_set_par(fbdev->info);
> +
> +	return ret;
> +}
> +
> +static const struct drm_client_funcs drm_fbdev_client_funcs = {
> +	.name		= "drm_fbdev",
> +	.new		= drm_fbdev_client_new,
> +	.remove		= drm_fbdev_client_remove,
> +	.lastclose	= drm_fbdev_client_lastclose,
> +	.hotplug	= drm_fbdev_client_hotplug,
> +};
> +
> +static int __init drm_fbdev_init(void)
> +{
> +	return drm_client_register(&drm_fbdev_client_funcs);
> +}
> +module_init(drm_fbdev_init);
> +
> +static void __exit drm_fbdev_exit(void)
> +{
> +	drm_client_unregister(&drm_fbdev_client_funcs);
> +}
> +module_exit(drm_fbdev_exit);
> +
> +MODULE_DESCRIPTION("DRM Generic fbdev emulation");
> +MODULE_AUTHOR("Noralf Trønnes");
> +MODULE_LICENSE("GPL");
> -- 
> 2.15.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v3 11/12] drm/client: Add bootsplash client
  2018-02-22 20:06 ` [RFC v3 11/12] drm/client: Add bootsplash client Noralf Trønnes
@ 2018-03-06  9:12   ` Daniel Vetter
  2018-03-06 15:21     ` Max Staudt
  0 siblings, 1 reply; 26+ messages in thread
From: Daniel Vetter @ 2018-03-06  9:12 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: daniel.vetter, intel-gfx, oneukum, tiwai, dri-devel,
	laurent.pinchart, Max Staudt, dh.herrmann, sndirsch

On Thu, Feb 22, 2018 at 09:06:52PM +0100, Noralf Trønnes wrote:
> Just a hack to test the client API.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Adding the suse folks who submitted the bootsplash a while ago, would be
great if they could pick this up and run with it.

> ---
>  drivers/gpu/drm/client/Kconfig          |   5 +
>  drivers/gpu/drm/client/Makefile         |   1 +
>  drivers/gpu/drm/client/drm_bootsplash.c | 205 ++++++++++++++++++++++++++++++++
>  3 files changed, 211 insertions(+)
>  create mode 100644 drivers/gpu/drm/client/drm_bootsplash.c
> 
> diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
> index 73902ab44c75..16cf1e14620a 100644
> --- a/drivers/gpu/drm/client/Kconfig
> +++ b/drivers/gpu/drm/client/Kconfig
> @@ -17,4 +17,9 @@ config DRM_CLIENT_FBDEV
>  	help
>  	  Generic fbdev emulation
>  
> +config DRM_CLIENT_BOOTSPLASH
> +	tristate "DRM Bootsplash"
> +	help
> +	  DRM Bootsplash
> +
>  endmenu
> diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
> index 3ff694429dec..8660530e4646 100644
> --- a/drivers/gpu/drm/client/Makefile
> +++ b/drivers/gpu/drm/client/Makefile
> @@ -1,3 +1,4 @@
>  # SPDX-License-Identifier: GPL-2.0
>  
>  obj-$(CONFIG_DRM_CLIENT_FBDEV) += drm_fbdev.o
> +obj-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += drm_bootsplash.o
> diff --git a/drivers/gpu/drm/client/drm_bootsplash.c b/drivers/gpu/drm/client/drm_bootsplash.c
> new file mode 100644
> index 000000000000..43c703606e74
> --- /dev/null
> +++ b/drivers/gpu/drm/client/drm_bootsplash.c
> @@ -0,0 +1,205 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.h>
> +
> +#include <drm/drm_client.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_print.h>
> +
> +struct drm_bootsplash {
> +	struct drm_client_dev *client;
> +	struct drm_client_display *display;
> +	struct drm_client_buffer *buffer[2];
> +	struct work_struct worker;
> +	bool stop;
> +};
> +
> +static u32 drm_bootsplash_color_table[3] = {
> +	0x00ff0000, 0x0000ff00, 0x000000ff,
> +};
> +
> +/* Draw a box with changing colors */
> +static void
> +drm_bootsplash_draw(struct drm_client_buffer *buffer, unsigned int sequence)
> +{
> +	unsigned int x, y;
> +	u32 *pix;
> +
> +	pix = buffer->vaddr;
> +	pix += ((buffer->height / 2) - 50) * buffer->width;
> +	pix += (buffer->width / 2) - 50;
> +
> +	for (y = 0; y < 100; y++) {
> +		for (x = 0; x < 100; x++)
> +			*pix++ = drm_bootsplash_color_table[sequence];
> +		pix += buffer->width - 100;
> +	}
> +}
> +
> +static void drm_bootsplash_worker(struct work_struct *work)
> +{
> +	struct drm_bootsplash *splash = container_of(work, struct drm_bootsplash,
> +						     worker);
> +	struct drm_event *event;
> +	unsigned int i = 0, sequence = 0, fb_id;
> +	int ret;
> +
> +	while (!splash->stop) {
> +		/* Are we still in charge? */
> +		fb_id = drm_client_display_current_fb(splash->display);
> +		if (fb_id != splash->buffer[i]->fb_ids[0])
> +			break;
> +
> +		/*
> +		 * We can race with userspace here between checking and doing
> +		 * the page flip, so double buffering isn't such a good idea.
> +		 * Tearing probably isn't a problem on a presumably small splash
> +		 * animation. I've kept it to test the page flip code.
> +		 */

I think a much cleaner way to solve all this is to tie it into our master
tracking. If there is a master (even if it's not displaying anything),
then none of the in-kernel clients should display anything.

If there is not, then we grab a temporary/weak master reference which
blocks other masters for the time being, but only until we've complete our
drawing operation. Then the in-kernel client drops that weak reference
again. A very simply solution would be to simply hold the device's master
lock (but I'm not sure whether that would result in deadlocks).

That would mean something like

		if (!drm_master_try_internal_acquire())
			/* someone else is master */
			break;

here instead of your fb id check.
> +
> +		i = !i;
> +		drm_bootsplash_draw(splash->buffer[i], sequence++);
> +		if (sequence == 3)
> +			sequence = 0;
> +
> +		ret = drm_client_display_page_flip(splash->display,
> +						   splash->buffer[i]->fb_ids[0],
> +						   true);
> +		if (!ret) {
> +			event = drm_client_read_event(splash->client, true);
> +			if (!IS_ERR(event))
> +				kfree(event);
> +		}

And
		drm_master_interal_relase()

here before we sleep again. Similar for all the fbdev ioctls and all that
stuff.

Just an idea, names definitely need improvements, and I have no idea
wheter it'll work. But using the same logic in the existing fbdev
emulation code would be really good (since that's used a lot more, at
least right now).
-Daniel

> +		msleep(500);
> +	}
> +
> +	for (i = 0; i < 2; i++)
> +		drm_client_framebuffer_delete(splash->buffer[i]);
> +	drm_client_display_free(splash->display);
> +}
> +
> +static int drm_bootsplash_setup(struct drm_bootsplash *splash)
> +{
> +	struct drm_client_dev *client = splash->client;
> +	struct drm_client_buffer *buffer[2];
> +	struct drm_client_display *display;
> +	struct drm_mode_modeinfo *mode;
> +	int ret, i;
> +
> +	display = drm_client_display_get_first_enabled(client, false);
> +	if (IS_ERR(display))
> +		return PTR_ERR(display);
> +	if (!display)
> +		return -ENOENT;
> +
> +	mode = drm_client_display_first_mode(display);
> +	if (!mode) {
> +		ret = -EINVAL;
> +		goto err_free_display;
> +	}
> +
> +	for (i = 0; i < 2; i++) {
> +		buffer[i] = drm_client_framebuffer_create(client, mode,
> +							  DRM_FORMAT_XRGB8888);
> +		if (IS_ERR(buffer[i])) {
> +			ret = PTR_ERR(buffer[i]);
> +			goto err_free_buffer;
> +		}
> +	}
> +
> +	ret = drm_client_display_commit_mode(display, buffer[0]->fb_ids[0], mode);
> +	if (ret)
> +		goto err_free_buffer;
> +
> +	splash->display = display;
> +	splash->buffer[0] = buffer[0];
> +	splash->buffer[1] = buffer[1];
> +
> +	schedule_work(&splash->worker);
> +
> +	return 0;
> +
> +err_free_buffer:
> +	for (i--; i >= 0; i--)
> +		drm_client_framebuffer_delete(buffer[i]);
> +err_free_display:
> +	drm_client_display_free(display);
> +
> +	return ret;
> +}
> +
> +static int drm_bootsplash_client_hotplug(struct drm_client_dev *client)
> +{
> +	struct drm_bootsplash *splash = client->private;
> +	int ret = 0;
> +
> +	if (!splash->display)
> +		ret = drm_bootsplash_setup(splash);
> +
> +	return ret;
> +}
> +
> +static int drm_bootsplash_client_new(struct drm_client_dev *client)
> +{
> +	struct drm_bootsplash *splash;
> +
> +	splash = kzalloc(sizeof(*splash), GFP_KERNEL);
> +	if (!splash)
> +		return -ENOMEM;
> +
> +	INIT_WORK(&splash->worker, drm_bootsplash_worker);
> +
> +	splash->client = client;
> +	client->private = splash;
> +
> +	/*
> +	 * vc4 isn't done with it's setup when drm_dev_register() is called.
> +	 * It should have shouldn't it?
> +	 * So to keep it from crashing defer setup to hotplug...
> +	 */
> +	if (client->dev->mode_config.max_width)
> +		drm_bootsplash_client_hotplug(client);
> +
> +	return 0;
> +}
> +
> +static int drm_bootsplash_client_remove(struct drm_client_dev *client)
> +{
> +	struct drm_bootsplash *splash = client->private;
> +
> +	if (splash->display) {
> +		splash->stop = true;
> +		flush_work(&splash->worker);
> +	}
> +
> +	kfree(splash);
> +
> +	return 0;
> +}
> +
> +static const struct drm_client_funcs drm_bootsplash_client_funcs = {
> +	.name		= "drm_bootsplash",
> +	.new		= drm_bootsplash_client_new,
> +	.remove		= drm_bootsplash_client_remove,
> +	.hotplug	= drm_bootsplash_client_hotplug,
> +};
> +
> +static int __init drm_bootsplash_init(void)
> +{
> +	return drm_client_register(&drm_bootsplash_client_funcs);
> +}
> +module_init(drm_bootsplash_init);
> +
> +static void __exit drm_bootsplash_exit(void)
> +{
> +	drm_client_unregister(&drm_bootsplash_client_funcs);
> +}
> +module_exit(drm_bootsplash_exit);
> +
> +MODULE_LICENSE("GPL");
> -- 
> 2.15.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

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

* Re: [RFC v3 11/12] drm/client: Add bootsplash client
  2018-03-06  9:12   ` Daniel Vetter
@ 2018-03-06 15:21     ` Max Staudt
  0 siblings, 0 replies; 26+ messages in thread
From: Max Staudt @ 2018-03-06 15:21 UTC (permalink / raw)
  To: Daniel Vetter, Noralf Trønnes
  Cc: daniel.vetter, intel-gfx, oneukum, tiwai, dri-devel,
	laurent.pinchart, sndirsch

Thanks for CCing!

I like the idea of this patchset. As far as I understand, this multiplexing is exactly what I would have had to write in order to port the bootsplash to DRM. And we finally get rid of the driver-specific FB emulation hacks, too. Good riddance.

Thanks for the initiative, Noralf!

I've stopped working on the bootsplash since there seemed to be no more interest in it - but if there is, I can port it to such an in-kernel DRM client architecture once it's in.


Max

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v3 09/12] drm: Add API for in-kernel clients
  2018-03-06  8:56   ` Daniel Vetter
@ 2018-03-08 17:12     ` Noralf Trønnes
  2018-03-12 16:51       ` Daniel Vetter
  0 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-03-08 17:12 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart, dh.herrmann


Den 06.03.2018 09.56, skrev Daniel Vetter:
> On Thu, Feb 22, 2018 at 09:06:50PM +0100, Noralf Trønnes wrote:
>> This adds an API for writing in-kernel clients.
>>
>> TODO:
>> - Flesh out and complete documentation.
>> - Cloned displays is not tested.
>> - Complete tiled display support and test it.
>> - Test plug/unplug different monitors.
>> - A runtime knob to prevent clients from attaching for debugging purposes.
>> - Maybe a way to unbind individual client instances.
>> - Maybe take the sysrq support in drm_fb_helper and move it here somehow.
>> - Add suspend/resume callbacks.
>>    Does anyone know why fbdev requires suspend/resume?
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> The core client api I like. Some of the opens I'm seeing:
>
> - If we go with using the internal kms api directly instead of IOCTL
>    wrappers then a huge pile of the functions you have here aren't needed
>    (e.g. all the event stuff we can just directly use vblank events instead
>    of all the wrapping). I'm leaning ever more into that direction, since
>    much less code to add.

Looking at drm_fb_helper once again I now see an opportunity to simplify
the modesetting code by nuking drm_fb_helper_connector and stop
maintaining an array of connectors. It looks to be possible to just
create an array temporarily in drm_setup_crtcs() for the duration of the
function. The connectors we care about are ref counted and attached to
modesets. This would remove the need for drm_fb_helper_add_one_connector().

So I might be able to do struct drm_fb_helper_crtc -> drm_client_crtc
and let the client API take over drm_setup_crtcs(). I'll give it a try.

There is one challenge I see upfront and that's the i915 fb_helper
callback in drm_setup_crtcs().

> - The register/unregister model needs more thought. Allowing both clients
>    to register whenever they want to, and drm_device instances to come and
>    go is what fbcon has done, and the resulting locking is a horror show.
>
>    I think if we require that all in-kernel drm_clients are registers when
>    loading drm.ko (and enabled/disabled only per module options and
>    Kconfig), then we can throw out all the locking. That avoids a lot of
>    the headaches.
>
>    2nd, if the list of clients is static over the lifetime of drm.ko, we
>    also don't need to iterate existing drivers. Which avoids me having to
>    review the iterator patch (that's the other aspect where fbcon totally
>    falls over and essentially just ignores a bunch of races).

Are you talking about linking the clients into drm.ko?

drivers/gpu/drm/Makefile:

drm-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += client/drm_bootsplash.o

drivers/gpu/drm/drm_drv.c:

  static int __init drm_core_init(void)
  {
+    drm_bootsplash_register();
+    drm_fbdev_register();
  }

drivers/gpu/drm/drm_internal.h:

#ifdef DRM_CLIENT_BOOTSPLASH
void drm_bootsplash_register(void);
#else
static inline void drm_bootsplash_register(void)
{
}
#endif

drivers/gpu/drm/client/drm_bootsplash.c:

static const struct drm_client_funcs drm_bootsplash_funcs = {
     .name        = "drm_bootsplash",
     .new        = drm_bootsplash_new,
     .remove        = drm_bootsplash_remove,
     .hotplug    = drm_bootsplash_hotplug,
};

void drm_bootsplash_register(void)
{
     drm_client_register(&drm_bootsplash_funcs);
}

drivers/gpu/drm/drm_client.c:

static LIST_HEAD(drm_client_funcs_list);

void drm_client_register(const struct drm_client_funcs *funcs)
{
     struct drm_client_funcs_entry *funcs_entry;

     funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
     if (!funcs_entry) {
         DRM_ERROR("Failed to register: %s\n", funcs->name);
         return;
     }

     funcs_entry->funcs = funcs;

     list_add(&funcs_entry->list, &drm_client_funcs_list);

     DRM_DEBUG_KMS("%s\n", funcs->name);
}


And each client having a runtime enable/disable knob:

drivers/gpu/drm/client/drm_bootsplash.c:

static bool drm_bootsplash_enabled = true;
module_param_named(bootsplash_enabled, drm_bootsplash_enabled, bool, 0600);
MODULE_PARM_DESC(bootsplash_enabled, "Enable bootsplash client 
[default=true]");


Simple USB Display
A few months back while looking at the udl shmem code, I got the idea
that I could turn a Raspberry Pi Zero into a $5 USB to HDMI/DSI/DPI/DBI/TV
adapter. The host side would be a simple tinydrm driver using the kernel
compression lib to speed up transfers. The gadget/device side would be a
userspace app decompressing the buffer into an exported dumb buffer.

While working with this client API I realized that I could use it and
write a kernel gadget driver instead avoiding the challenge of going
back and forth to userspace with the framebuffer. For such a client I
would have preferred it to be a loadable module not linked into drm.ko
to increase the chance that distributions would enable it.

Noralf.

>> ---
>>   drivers/gpu/drm/Kconfig             |    2 +
>>   drivers/gpu/drm/Makefile            |    3 +-
>>   drivers/gpu/drm/client/Kconfig      |    4 +
>>   drivers/gpu/drm/client/Makefile     |    1 +
>>   drivers/gpu/drm/client/drm_client.c | 1612 +++++++++++++++++++++++++++++++++++
> I'd move this into main drm/ directory, it's fairly core stuff.
>
>>   drivers/gpu/drm/drm_drv.c           |    6 +
>>   drivers/gpu/drm/drm_file.c          |    3 +
>>   drivers/gpu/drm/drm_probe_helper.c  |    3 +
>>   include/drm/drm_client.h            |  192 +++++
>>   include/drm/drm_device.h            |    1 +
>>   include/drm/drm_file.h              |    7 +
>>   11 files changed, 1833 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/gpu/drm/client/Kconfig
>>   create mode 100644 drivers/gpu/drm/client/Makefile
>>   create mode 100644 drivers/gpu/drm/client/drm_client.c
>>   create mode 100644 include/drm/drm_client.h
>>
>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>> index deeefa7a1773..d4ae15f9ee9f 100644
>> --- a/drivers/gpu/drm/Kconfig
>> +++ b/drivers/gpu/drm/Kconfig
>> @@ -154,6 +154,8 @@ config DRM_SCHED
>>   	tristate
>>   	depends on DRM
>>   
>> +source "drivers/gpu/drm/client/Kconfig"
>> +
>>   source "drivers/gpu/drm/i2c/Kconfig"
>>   
>>   source "drivers/gpu/drm/arm/Kconfig"
>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>> index 50093ff4479b..8e06dc7eeca1 100644
>> --- a/drivers/gpu/drm/Makefile
>> +++ b/drivers/gpu/drm/Makefile
>> @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
>>   		drm_encoder.o drm_mode_object.o drm_property.o \
>>   		drm_plane.o drm_color_mgmt.o drm_print.o \
>>   		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
>> -		drm_syncobj.o drm_lease.o
>> +		drm_syncobj.o drm_lease.o client/drm_client.o
>>   
>>   drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
>>   drm-$(CONFIG_DRM_VM) += drm_vm.o
>> @@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
>>   obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
>>   obj-$(CONFIG_DRM_PL111) += pl111/
>>   obj-$(CONFIG_DRM_TVE200) += tve200/
>> +obj-y			+= client/
>> diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
>> new file mode 100644
>> index 000000000000..4bb8e4655ff7
>> --- /dev/null
>> +++ b/drivers/gpu/drm/client/Kconfig
>> @@ -0,0 +1,4 @@
>> +menu "DRM Clients"
>> +	depends on DRM
>> +
>> +endmenu
>> diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
>> new file mode 100644
>> index 000000000000..f66554cd5c45
>> --- /dev/null
>> +++ b/drivers/gpu/drm/client/Makefile
>> @@ -0,0 +1 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> diff --git a/drivers/gpu/drm/client/drm_client.c b/drivers/gpu/drm/client/drm_client.c
>> new file mode 100644
>> index 000000000000..a633bf747316
>> --- /dev/null
>> +++ b/drivers/gpu/drm/client/drm_client.c
>> @@ -0,0 +1,1612 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +// Copyright 2018 Noralf Trønnes
>> +
>> +#include <linux/dma-buf.h>
>> +#include <linux/list.h>
>> +#include <linux/mutex.h>
>> +#include <linux/module.h>
>> +#include <linux/slab.h>
>> +
>> +#include <drm/drm_client.h>
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_drv.h>
>> +#include <drm/drm_file.h>
>> +#include <drm/drm_ioctl.h>
>> +#include <drm/drmP.h>
>> +
>> +#include "drm_crtc_internal.h"
>> +#include "drm_internal.h"
>> +
>> +struct drm_client_funcs_entry {
>> +	struct list_head list;
>> +	const struct drm_client_funcs *funcs;
>> +};
>> +
>> +static LIST_HEAD(drm_client_list);
> I think the client list itself should be on the drm_device, not in one
> global list that mixes up all the clients of all the drm_devices.
>
> I'll skip reviewing details since we have a bunch of high-level questions
> to figure out first.
> -Daniel
>
>> +static LIST_HEAD(drm_client_funcs_list);
>> +static DEFINE_MUTEX(drm_client_list_lock);
>> +
>> +static void drm_client_new(struct drm_device *dev,
>> +			   const struct drm_client_funcs *funcs)
>> +{
>> +	struct drm_client_dev *client;
>> +	int ret;
>> +
>> +	lockdep_assert_held(&drm_client_list_lock);
>> +
>> +	client = kzalloc(sizeof(*client), GFP_KERNEL);
>> +	if (!client)
>> +		return;
>> +
>> +	mutex_init(&client->lock);
>> +	client->dev = dev;
>> +	client->funcs = funcs;
>> +
>> +	ret = funcs->new(client);
>> +	DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", funcs->name, ret);
>> +	if (ret) {
>> +		drm_client_free(client);
>> +		return;
>> +	}
>> +
>> +	list_add(&client->list, &drm_client_list);
>> +}
>> +
>> +/**
>> + * drm_client_free - Free DRM client resources
>> + * @client: DRM client
>> + *
>> + * This is called automatically on client removal unless the client returns
>> + * non-zero in the &drm_client_funcs->remove callback. The fbdev client does
>> + * this when it can't close &drm_file because userspace has an open fd.
>> + */
>> +void drm_client_free(struct drm_client_dev *client)
>> +{
>> +	DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
>> +	if (WARN_ON(client->file)) {
>> +		client->file_ref_count = 1;
>> +		drm_client_put_file(client);
>> +	}
>> +	mutex_destroy(&client->lock);
>> +	kfree(client->crtcs);
>> +	kfree(client);
>> +}
>> +EXPORT_SYMBOL(drm_client_free);
>> +
>> +static void drm_client_remove(struct drm_client_dev *client)
>> +{
>> +	lockdep_assert_held(&drm_client_list_lock);
>> +
>> +	list_del(&client->list);
>> +
>> +	if (!client->funcs->remove || !client->funcs->remove(client))
>> +		drm_client_free(client);
>> +}
>> +
>> +/**
>> + * drm_client_register - Register a DRM client
>> + * @funcs: Client callbacks
>> + *
>> + * Returns:
>> + * Zero on success, negative error code on failure.
>> + */
>> +int drm_client_register(const struct drm_client_funcs *funcs)
>> +{
>> +	struct drm_client_funcs_entry *funcs_entry;
>> +	struct drm_device_list_iter iter;
>> +	struct drm_device *dev;
>> +
>> +	funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
>> +	if (!funcs_entry)
>> +		return -ENOMEM;
>> +
>> +	funcs_entry->funcs = funcs;
>> +
>> +	mutex_lock(&drm_global_mutex);
>> +	mutex_lock(&drm_client_list_lock);
>> +
>> +	drm_device_list_iter_begin(&iter);
>> +	drm_for_each_device_iter(dev, &iter)
>> +		if (drm_core_check_feature(dev, DRIVER_MODESET))
>> +			drm_client_new(dev, funcs);
>> +	drm_device_list_iter_end(&iter);
>> +
>> +	list_add(&funcs_entry->list, &drm_client_funcs_list);
>> +
>> +	mutex_unlock(&drm_client_list_lock);
>> +	mutex_unlock(&drm_global_mutex);
>> +
>> +	DRM_DEBUG_KMS("%s\n", funcs->name);
>> +
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL(drm_client_register);
>> +
>> +/**
>> + * drm_client_unregister - Unregister a DRM client
>> + * @funcs: Client callbacks
>> + */
>> +void drm_client_unregister(const struct drm_client_funcs *funcs)
>> +{
>> +	struct drm_client_funcs_entry *funcs_entry;
>> +	struct drm_client_dev *client, *tmp;
>> +
>> +	mutex_lock(&drm_client_list_lock);
>> +
>> +	list_for_each_entry_safe(client, tmp, &drm_client_list, list) {
>> +		if (client->funcs == funcs)
>> +			drm_client_remove(client);
>> +	}
>> +
>> +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list) {
>> +		if (funcs_entry->funcs == funcs) {
>> +			list_del(&funcs_entry->list);
>> +			kfree(funcs_entry);
>> +			break;
>> +		}
>> +	}
>> +
>> +	mutex_unlock(&drm_client_list_lock);
>> +
>> +	DRM_DEBUG_KMS("%s\n", funcs->name);
>> +}
>> +EXPORT_SYMBOL(drm_client_unregister);
>> +
>> +void drm_client_dev_register(struct drm_device *dev)
>> +{
>> +	struct drm_client_funcs_entry *funcs_entry;
>> +
>> +	/*
>> +	 * Minors are created at the beginning of drm_dev_register(), but can
>> +	 * be removed again if the function fails. Since we iterate DRM devices
>> +	 * by walking DRM minors, we need to stay under this lock.
>> +	 */
>> +	lockdep_assert_held(&drm_global_mutex);
>> +
>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>> +		return;
>> +
>> +	mutex_lock(&drm_client_list_lock);
>> +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list)
>> +		drm_client_new(dev, funcs_entry->funcs);
>> +	mutex_unlock(&drm_client_list_lock);
>> +}
>> +
>> +void drm_client_dev_unregister(struct drm_device *dev)
>> +{
>> +	struct drm_client_dev *client, *tmp;
>> +
>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>> +		return;
>> +
>> +	mutex_lock(&drm_client_list_lock);
>> +	list_for_each_entry_safe(client, tmp, &drm_client_list, list)
>> +		if (client->dev == dev)
>> +			drm_client_remove(client);
>> +	mutex_unlock(&drm_client_list_lock);
>> +}
>> +
>> +void drm_client_dev_hotplug(struct drm_device *dev)
>> +{
>> +	struct drm_client_dev *client;
>> +	int ret;
>> +
>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>> +		return;
>> +
>> +	mutex_lock(&drm_client_list_lock);
>> +	list_for_each_entry(client, &drm_client_list, list)
>> +		if (client->dev == dev && client->funcs->hotplug) {
>> +			ret = client->funcs->hotplug(client);
>> +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
>> +					  client->funcs->name, ret);
>> +		}
>> +	mutex_unlock(&drm_client_list_lock);
>> +}
>> +
>> +void drm_client_dev_lastclose(struct drm_device *dev)
>> +{
>> +	struct drm_client_dev *client;
>> +	int ret;
>> +
>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>> +		return;
>> +
>> +	mutex_lock(&drm_client_list_lock);
>> +	list_for_each_entry(client, &drm_client_list, list)
>> +		if (client->dev == dev && client->funcs->lastclose) {
>> +			ret = client->funcs->lastclose(client);
>> +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
>> +					  client->funcs->name, ret);
>> +		}
>> +	mutex_unlock(&drm_client_list_lock);
>> +}
>> +
>> +/* Get static info */
>> +static int drm_client_init(struct drm_client_dev *client, struct drm_file *file)
>> +{
>> +	struct drm_mode_card_res card_res = {};
>> +	struct drm_device *dev = client->dev;
>> +	u32 *crtcs;
>> +	int ret;
>> +
>> +	ret = drm_mode_getresources(dev, &card_res, file, false);
>> +	if (ret)
>> +		return ret;
>> +	if (!card_res.count_crtcs)
>> +		return -ENOENT;
>> +
>> +	crtcs = kmalloc_array(card_res.count_crtcs, sizeof(*crtcs), GFP_KERNEL);
>> +	if (!crtcs)
>> +		return -ENOMEM;
>> +
>> +	card_res.count_fbs = 0;
>> +	card_res.count_connectors = 0;
>> +	card_res.count_encoders = 0;
>> +	card_res.crtc_id_ptr = (unsigned long)crtcs;
>> +
>> +	ret = drm_mode_getresources(dev, &card_res, file, false);
>> +	if (ret) {
>> +		kfree(crtcs);
>> +		return ret;
>> +	}
>> +
>> +	client->crtcs = crtcs;
>> +	client->num_crtcs = card_res.count_crtcs;
>> +	client->min_width = card_res.min_width;
>> +	client->max_width = card_res.max_width;
>> +	client->min_height = card_res.min_height;
>> +	client->max_height = card_res.max_height;
>> +
>> +	return 0;
>> +}
>> +
>> +/**
>> + * drm_client_get_file - Get a DRM file
>> + * @client: DRM client
>> + *
>> + * This function makes sure the client has a &drm_file available. The client
>> + * doesn't normally need to call this, since all client functions that depends
>> + * on a DRM file will call it. A matching call to drm_client_put_file() is
>> + * necessary.
>> + *
>> + * The reason for not opening a DRM file when a @client is created is because
>> + * we have to take a ref on the driver module due to &drm_driver->postclose
>> + * being called in drm_file_free(). Having a DRM file open for the lifetime of
>> + * the client instance would block driver module unload.
>> + *
>> + * Returns:
>> + * Zero on success, negative error code on failure.
>> + */
>> +int drm_client_get_file(struct drm_client_dev *client)
>> +{
>> +	struct drm_device *dev = client->dev;
>> +	struct drm_file *file;
>> +	int ret = 0;
>> +
>> +	mutex_lock(&client->lock);
>> +
>> +	if (client->file_ref_count++) {
>> +		mutex_unlock(&client->lock);
>> +		return 0;
>> +	}
>> +
>> +	if (!try_module_get(dev->driver->fops->owner)) {
>> +		ret = -ENODEV;
>> +		goto err_unlock;
>> +	}
>> +
>> +	drm_dev_get(dev);
>> +
>> +	file = drm_file_alloc(dev->primary);
>> +	if (IS_ERR(file)) {
>> +		ret = PTR_ERR(file);
>> +		goto err_put;
>> +	}
>> +
>> +	if (!client->crtcs) {
>> +		ret = drm_client_init(client, file);
>> +		if (ret)
>> +			goto err_free;
>> +	}
>> +
>> +	mutex_lock(&dev->filelist_mutex);
>> +	list_add(&file->lhead, &dev->filelist_internal);
>> +	mutex_unlock(&dev->filelist_mutex);
>> +
>> +	client->file = file;
>> +
>> +	mutex_unlock(&client->lock);
>> +
>> +	return 0;
>> +
>> +err_free:
>> +	drm_file_free(file);
>> +err_put:
>> +	drm_dev_put(dev);
>> +	module_put(dev->driver->fops->owner);
>> +err_unlock:
>> +	client->file_ref_count = 0;
>> +	mutex_unlock(&client->lock);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_get_file);
>> +
>> +void drm_client_put_file(struct drm_client_dev *client)
>> +{
>> +	struct drm_device *dev = client->dev;
>> +
>> +	if (!client)
>> +		return;
>> +
>> +	mutex_lock(&client->lock);
>> +
>> +	if (WARN_ON(!client->file_ref_count))
>> +		goto out_unlock;
>> +
>> +	if (--client->file_ref_count)
>> +		goto out_unlock;
>> +
>> +	mutex_lock(&dev->filelist_mutex);
>> +	list_del(&client->file->lhead);
>> +	mutex_unlock(&dev->filelist_mutex);
>> +
>> +	drm_file_free(client->file);
>> +	client->file = NULL;
>> +	drm_dev_put(dev);
>> +	module_put(dev->driver->fops->owner);
>> +out_unlock:
>> +	mutex_unlock(&client->lock);
>> +}
>> +EXPORT_SYMBOL(drm_client_put_file);
>> +
>> +static struct drm_pending_event *
>> +drm_client_read_get_pending_event(struct drm_device *dev, struct drm_file *file)
>> +{
>> +	struct drm_pending_event *e = NULL;
>> +	int ret;
>> +
>> +	ret = mutex_lock_interruptible(&file->event_read_lock);
>> +	if (ret)
>> +		return ERR_PTR(ret);
>> +
>> +	spin_lock_irq(&dev->event_lock);
>> +	if (!list_empty(&file->event_list)) {
>> +		e = list_first_entry(&file->event_list,
>> +				     struct drm_pending_event, link);
>> +		file->event_space += e->event->length;
>> +		list_del(&e->link);
>> +	}
>> +	spin_unlock_irq(&dev->event_lock);
>> +
>> +	mutex_unlock(&file->event_read_lock);
>> +
>> +	return e;
>> +}
>> +
>> +struct drm_event *
>> +drm_client_read_event(struct drm_client_dev *client, bool block)
>> +{
>> +	struct drm_file *file = client->file;
>> +	struct drm_device *dev = client->dev;
>> +	struct drm_pending_event *e;
>> +	struct drm_event *event;
>> +	int ret;
>> +
>> +	/* Allocate so it fits all events, there's a sanity check later */
>> +	event = kzalloc(128, GFP_KERNEL);
>> +	if (!event)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	e = drm_client_read_get_pending_event(dev, file);
>> +	if (IS_ERR(e)) {
>> +		ret = PTR_ERR(e);
>> +		goto err_free;
>> +	}
>> +
>> +	if (!e && !block) {
>> +		ret = 0;
>> +		goto err_free;
>> +	}
>> +
>> +	ret = wait_event_interruptible_timeout(file->event_wait,
>> +					       !list_empty(&file->event_list),
>> +					       5 * HZ);
>> +	if (!ret)
>> +		ret = -ETIMEDOUT;
>> +	if (ret < 0)
>> +		goto err_free;
>> +
>> +	e = drm_client_read_get_pending_event(dev, file);
>> +	if (IS_ERR_OR_NULL(e)) {
>> +		ret = PTR_ERR_OR_ZERO(e);
>> +		goto err_free;
>> +	}
>> +
>> +	if (WARN_ON(e->event->length > 128)) {
>> +		/* Increase buffer if this happens */
>> +		ret = -ENOMEM;
>> +		goto err_free;
>> +	}
>> +
>> +	memcpy(event, e->event, e->event->length);
>> +	kfree(e);
>> +
>> +	return event;
>> +
>> +err_free:
>> +	kfree(event);
>> +
>> +	return ret ? ERR_PTR(ret) : NULL;
>> +}
>> +EXPORT_SYMBOL(drm_client_read_event);
>> +
>> +static void drm_client_connector_free(struct drm_client_connector *connector)
>> +{
>> +	if (!connector)
>> +		return;
>> +	kfree(connector->modes);
>> +	kfree(connector);
>> +}
>> +
>> +static struct drm_client_connector *
>> +drm_client_get_connector(struct drm_client_dev *client, unsigned int id)
>> +{
>> +	struct drm_mode_get_connector req = {
>> +		.connector_id = id,
>> +	};
>> +	struct drm_client_connector *connector;
>> +	struct drm_mode_modeinfo *modes = NULL;
>> +	struct drm_device *dev = client->dev;
>> +	struct drm_connector *conn;
>> +	bool non_desktop;
>> +	int ret;
>> +
>> +	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
>> +	if (!connector)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	ret = drm_mode_getconnector(dev, &req, client->file, false);
>> +	if (ret)
>> +		goto err_free;
>> +
>> +	connector->conn_id = id;
>> +	connector->status = req.connection;
>> +
>> +	conn = drm_connector_lookup(dev, client->file, id);
>> +	if (!conn) {
>> +		ret = -ENOENT;
>> +		goto err_free;
>> +	}
>> +
>> +	non_desktop = conn->display_info.non_desktop;
>> +
>> +	connector->has_tile = conn->has_tile;
>> +	connector->tile_h_loc = conn->tile_h_loc;
>> +	connector->tile_v_loc = conn->tile_v_loc;
>> +	if (conn->tile_group)
>> +		connector->tile_group = conn->tile_group->id;
>> +
>> +	drm_connector_put(conn);
>> +
>> +	if (non_desktop) {
>> +		kfree(connector);
>> +		return NULL;
>> +	}
>> +
>> +	if (!req.count_modes)
>> +		return connector;
>> +
>> +	modes = kcalloc(req.count_modes, sizeof(*modes), GFP_KERNEL);
>> +	if (!modes) {
>> +		ret = -ENOMEM;
>> +		goto err_free;
>> +	}
>> +
>> +	connector->modes = modes;
>> +	connector->num_modes = req.count_modes;
>> +
>> +	req.count_props = 0;
>> +	req.count_encoders = 0;
>> +	req.modes_ptr = (unsigned long)modes;
>> +
>> +	ret = drm_mode_getconnector(dev, &req, client->file, false);
>> +	if (ret)
>> +		goto err_free;
>> +
>> +	return connector;
>> +
>> +err_free:
>> +	kfree(modes);
>> +	kfree(connector);
>> +
>> +	return ERR_PTR(ret);
>> +}
>> +
>> +static int drm_client_get_connectors(struct drm_client_dev *client,
>> +				     struct drm_client_connector ***connectors)
>> +{
>> +	struct drm_mode_card_res card_res = {};
>> +	struct drm_device *dev = client->dev;
>> +	int ret, num_connectors;
>> +	u32 *connector_ids;
>> +	unsigned int i;
>> +
>> +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
>> +	if (ret)
>> +		return ret;
>> +	if (!card_res.count_connectors)
>> +		return 0;
>> +
>> +	num_connectors = card_res.count_connectors;
>> +	connector_ids = kcalloc(num_connectors,
>> +				sizeof(*connector_ids), GFP_KERNEL);
>> +	if (!connector_ids)
>> +		return -ENOMEM;
>> +
>> +	card_res.count_fbs = 0;
>> +	card_res.count_crtcs = 0;
>> +	card_res.count_encoders = 0;
>> +	card_res.connector_id_ptr = (unsigned long)connector_ids;
>> +
>> +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
>> +	if (ret)
>> +		goto err_free;
>> +
>> +	*connectors = kcalloc(num_connectors, sizeof(**connectors), GFP_KERNEL);
>> +	if (!(*connectors)) {
>> +		ret = -ENOMEM;
>> +		goto err_free;
>> +	}
>> +
>> +	for (i = 0; i < num_connectors; i++) {
>> +		struct drm_client_connector *connector;
>> +
>> +		connector = drm_client_get_connector(client, connector_ids[i]);
>> +		if (IS_ERR(connector)) {
>> +			ret = PTR_ERR(connector);
>> +			goto err_free;
>> +		}
>> +		if (connector)
>> +			(*connectors)[i] = connector;
>> +		else
>> +			num_connectors--;
>> +	}
>> +
>> +	if (!num_connectors) {
>> +		ret = 0;
>> +		goto err_free;
>> +	}
>> +
>> +	return num_connectors;
>> +
>> +err_free:
>> +	if (connectors)
>> +		for (i = 0; i < num_connectors; i++)
>> +			drm_client_connector_free((*connectors)[i]);
>> +
>> +	kfree(connectors);
>> +	kfree(connector_ids);
>> +
>> +	return ret;
>> +}
>> +
>> +static bool
>> +drm_client_connector_is_enabled(struct drm_client_connector *connector,
>> +				bool strict)
>> +{
>> +	if (strict)
>> +		return connector->status == connector_status_connected;
>> +	else
>> +		return connector->status != connector_status_disconnected;
>> +}
>> +
>> +struct drm_mode_modeinfo *
>> +drm_client_display_first_mode(struct drm_client_display *display)
>> +{
>> +	if (!display->num_modes)
>> +		return NULL;
>> +	return display->modes;
>> +}
>> +EXPORT_SYMBOL(drm_client_display_first_mode);
>> +
>> +struct drm_mode_modeinfo *
>> +drm_client_display_next_mode(struct drm_client_display *display,
>> +			     struct drm_mode_modeinfo *mode)
>> +{
>> +	struct drm_mode_modeinfo *modes = display->modes;
>> +
>> +	if (++mode < &modes[display->num_modes])
>> +		return mode;
>> +
>> +	return NULL;
>> +}
>> +EXPORT_SYMBOL(drm_client_display_next_mode);
>> +
>> +static void
>> +drm_client_display_fill_tile_modes(struct drm_client_display *display,
>> +				   struct drm_mode_modeinfo *tile_modes)
>> +{
>> +	unsigned int i, j, num_modes = display->connectors[0]->num_modes;
>> +	struct drm_mode_modeinfo *tile_mode, *conn_mode;
>> +
>> +	if (!num_modes) {
>> +		kfree(tile_modes);
>> +		kfree(display->modes);
>> +		display->modes = NULL;
>> +		display->num_modes = 0;
>> +		return;
>> +	}
>> +
>> +	for (i = 0; i < num_modes; i++) {
>> +		tile_mode = &tile_modes[i];
>> +
>> +		conn_mode = &display->connectors[0]->modes[i];
>> +		tile_mode->clock = conn_mode->clock;
>> +		tile_mode->vscan = conn_mode->vscan;
>> +		tile_mode->vrefresh = conn_mode->vrefresh;
>> +		tile_mode->flags = conn_mode->flags;
>> +		tile_mode->type = conn_mode->type;
>> +
>> +		for (j = 0; j < display->num_connectors; j++) {
>> +			conn_mode = &display->connectors[j]->modes[i];
>> +
>> +			if (!display->connectors[j]->tile_h_loc) {
>> +				tile_mode->hdisplay += conn_mode->hdisplay;
>> +				tile_mode->hsync_start += conn_mode->hsync_start;
>> +				tile_mode->hsync_end += conn_mode->hsync_end;
>> +				tile_mode->htotal += conn_mode->htotal;
>> +			}
>> +
>> +			if (!display->connectors[j]->tile_v_loc) {
>> +				tile_mode->vdisplay += conn_mode->vdisplay;
>> +				tile_mode->vsync_start += conn_mode->vsync_start;
>> +				tile_mode->vsync_end += conn_mode->vsync_end;
>> +				tile_mode->vtotal += conn_mode->vtotal;
>> +			}
>> +		}
>> +	}
>> +
>> +	kfree(display->modes);
>> +	display->modes = tile_modes;
>> +	display->num_modes = num_modes;
>> +}
>> +
>> +/**
>> + * drm_client_display_update_modes - Fetch display modes
>> + * @display: Client display
>> + * @mode_changed: Optional pointer to boolen which return whether the modes
>> + *                have changed or not.
>> + *
>> + * This function can be used in the client hotplug callback to check if the
>> + * video modes have changed and get them up-to-date.
>> + *
>> + * Returns:
>> + * Number of modes on success, negative error code on failure.
>> + */
>> +int drm_client_display_update_modes(struct drm_client_display *display,
>> +				    bool *mode_changed)
>> +{
>> +	unsigned int num_connectors = display->num_connectors;
>> +	struct drm_client_dev *client = display->client;
>> +	struct drm_mode_modeinfo *display_tile_modes;
>> +	struct drm_client_connector **connectors;
>> +	unsigned int i, num_modes = 0;
>> +	bool dummy_changed = false;
>> +	int ret;
>> +
>> +	if (mode_changed)
>> +		*mode_changed = false;
>> +	else
>> +		mode_changed = &dummy_changed;
>> +
>> +	if (display->cloned)
>> +		return 2;
>> +
>> +	ret = drm_client_get_file(client);
>> +	if (ret)
>> +		return ret;
>> +
>> +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
>> +	if (!connectors) {
>> +		ret = -ENOMEM;
>> +		goto out_put_file;
>> +	}
>> +
>> +	/* Get a new set for comparison */
>> +	for (i = 0; i < num_connectors; i++) {
>> +		connectors[i] = drm_client_get_connector(client, display->connectors[i]->conn_id);
>> +		if (IS_ERR_OR_NULL(connectors[i])) {
>> +			ret = PTR_ERR_OR_ZERO(connectors[i]);
>> +			if (!ret)
>> +				ret = -ENOENT;
>> +			goto out_cleanup;
>> +		}
>> +	}
>> +
>> +	/* All connectors should have the same number of modes */
>> +	num_modes = connectors[0]->num_modes;
>> +	for (i = 0; i < num_connectors; i++) {
>> +		if (num_modes != connectors[i]->num_modes) {
>> +			ret = -EINVAL;
>> +			goto out_cleanup;
>> +		}
>> +	}
>> +
>> +	if (num_connectors > 1) {
>> +		display_tile_modes = kcalloc(num_modes, sizeof(*display_tile_modes), GFP_KERNEL);
>> +		if (!display_tile_modes) {
>> +			ret = -ENOMEM;
>> +			goto out_cleanup;
>> +		}
>> +	}
>> +
>> +	mutex_lock(&display->modes_lock);
>> +
>> +	for (i = 0; i < num_connectors; i++) {
>> +		display->connectors[i]->status = connectors[i]->status;
>> +		if (display->connectors[i]->num_modes != connectors[i]->num_modes) {
>> +			display->connectors[i]->num_modes = connectors[i]->num_modes;
>> +			kfree(display->connectors[i]->modes);
>> +			display->connectors[i]->modes = connectors[i]->modes;
>> +			connectors[i]->modes = NULL;
>> +			*mode_changed = true;
>> +		}
>> +	}
>> +
>> +	if (num_connectors > 1)
>> +		drm_client_display_fill_tile_modes(display, display_tile_modes);
>> +	else
>> +		display->modes = display->connectors[0]->modes;
>> +
>> +	mutex_unlock(&display->modes_lock);
>> +
>> +out_cleanup:
>> +	for (i = 0; i < num_connectors; i++)
>> +		drm_client_connector_free(connectors[i]);
>> +	kfree(connectors);
>> +out_put_file:
>> +	drm_client_put_file(client);
>> +
>> +	return ret ? ret : num_modes;
>> +}
>> +EXPORT_SYMBOL(drm_client_display_update_modes);
>> +
>> +void drm_client_display_free(struct drm_client_display *display)
>> +{
>> +	unsigned int i;
>> +
>> +	if (!display)
>> +		return;
>> +
>> +	/* tile modes? */
>> +	if (display->modes != display->connectors[0]->modes)
>> +		kfree(display->modes);
>> +
>> +	for (i = 0; i < display->num_connectors; i++)
>> +		drm_client_connector_free(display->connectors[i]);
>> +
>> +	kfree(display->connectors);
>> +	mutex_destroy(&display->modes_lock);
>> +	kfree(display);
>> +}
>> +EXPORT_SYMBOL(drm_client_display_free);
>> +
>> +static struct drm_client_display *
>> +drm_client_display_alloc(struct drm_client_dev *client,
>> +			 unsigned int num_connectors)
>> +{
>> +	struct drm_client_display *display;
>> +	struct drm_client_connector **connectors;
>> +
>> +	display = kzalloc(sizeof(*display), GFP_KERNEL);
>> +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
>> +	if (!display || !connectors) {
>> +		kfree(display);
>> +		kfree(connectors);
>> +		return NULL;
>> +	}
>> +
>> +	mutex_init(&display->modes_lock);
>> +	display->client = client;
>> +	display->connectors = connectors;
>> +	display->num_connectors = num_connectors;
>> +
>> +	return display;
>> +}
>> +
>> +/* Logic is from drm_fb_helper */
>> +static struct drm_client_display *
>> +drm_client_connector_pick_cloned(struct drm_client_dev *client,
>> +				 struct drm_client_connector **connectors,
>> +				 unsigned int num_connectors)
>> +{
>> +	struct drm_mode_modeinfo modes[2], udmt_mode, *mode, *tmp;
>> +	struct drm_display_mode *dmt_display_mode = NULL;
>> +	unsigned int i, j, conns[2], num_conns = 0;
>> +	struct drm_client_connector *connector;
>> +	struct drm_device *dev = client->dev;
>> +	struct drm_client_display *display;
>> +
>> +	/* only contemplate cloning in the single crtc case */
>> +	if (dev->mode_config.num_crtc > 1)
>> +		return NULL;
>> +retry:
>> +	for (i = 0; i < num_connectors; i++) {
>> +		connector = connectors[i];
>> +		if (!connector || connector->has_tile || !connector->num_modes)
>> +			continue;
>> +
>> +		for (j = 0; j < connector->num_modes; j++) {
>> +			mode = &connector->modes[j];
>> +			if (dmt_display_mode) {
>> +				if (drm_umode_equal(&udmt_mode, mode)) {
>> +					conns[num_conns] = i;
>> +					modes[num_conns++] = *mode;
>> +					break;
>> +				}
>> +			} else {
>> +				if (mode->type & DRM_MODE_TYPE_USERDEF) {
>> +					conns[num_conns] = i;
>> +					modes[num_conns++] = *mode;
>> +					break;
>> +				}
>> +			}
>> +		}
>> +		if (num_conns == 2)
>> +			break;
>> +	}
>> +
>> +	if (num_conns == 2)
>> +		goto found;
>> +
>> +	if (dmt_display_mode)
>> +		return NULL;
>> +
>> +	dmt_display_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
>> +	drm_mode_convert_to_umode(&udmt_mode, dmt_display_mode);
>> +	drm_mode_destroy(dev, dmt_display_mode);
>> +
>> +	goto retry;
>> +
>> +found:
>> +	tmp = kcalloc(2, sizeof(*tmp), GFP_KERNEL);
>> +	if (!tmp)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	display = drm_client_display_alloc(client, 2);
>> +	if (!display) {
>> +		kfree(tmp);
>> +		return ERR_PTR(-ENOMEM);
>> +	}
>> +
>> +	for (i = 0; i < 2; i++) {
>> +		connector = connectors[conns[i]];
>> +		display->connectors[i] = connector;
>> +		connectors[conns[i]] = NULL;
>> +		kfree(connector->modes);
>> +		tmp[i] = modes[i];
>> +		connector->modes = &tmp[i];
>> +		connector->num_modes = 1;
>> +	}
>> +
>> +	display->cloned = true;
>> +	display->modes = &tmp[0];
>> +	display->num_modes = 1;
>> +
>> +	return display;
>> +}
>> +
>> +static struct drm_client_display *
>> +drm_client_connector_pick_tile(struct drm_client_dev *client,
>> +			       struct drm_client_connector **connectors,
>> +			       unsigned int num_connectors)
>> +{
>> +	unsigned int i, num_conns, num_modes, tile_group = 0;
>> +	struct drm_mode_modeinfo *tile_modes = NULL;
>> +	struct drm_client_connector *connector;
>> +	struct drm_client_display *display;
>> +	u16 conns[32];
>> +
>> +	for (i = 0; i < num_connectors; i++) {
>> +		connector = connectors[i];
>> +		if (!connector || !connector->tile_group)
>> +			continue;
>> +
>> +		if (!tile_group) {
>> +			tile_group = connector->tile_group;
>> +			num_modes = connector->num_modes;
>> +		}
>> +
>> +		if (connector->tile_group != tile_group)
>> +			continue;
>> +
>> +		if (num_modes != connector->num_modes) {
>> +			DRM_ERROR("Tile connectors must have the same number of modes\n");
>> +			return ERR_PTR(-EINVAL);
>> +		}
>> +
>> +		conns[num_conns++] = i;
>> +		if (WARN_ON(num_conns == 33))
>> +			return ERR_PTR(-EINVAL);
>> +	}
>> +
>> +	if (!num_conns)
>> +		return NULL;
>> +
>> +	if (num_modes) {
>> +		tile_modes = kcalloc(num_modes, sizeof(*tile_modes), GFP_KERNEL);
>> +		if (!tile_modes)
>> +			return ERR_PTR(-ENOMEM);
>> +	}
>> +
>> +	display = drm_client_display_alloc(client, num_conns);
>> +	if (!display) {
>> +		kfree(tile_modes);
>> +		return ERR_PTR(-ENOMEM);
>> +	}
>> +
>> +	if (num_modes)
>> +		drm_client_display_fill_tile_modes(display, tile_modes);
>> +
>> +	return display;
>> +}
>> +
>> +static struct drm_client_display *
>> +drm_client_connector_pick_not_tile(struct drm_client_dev *client,
>> +				   struct drm_client_connector **connectors,
>> +				   unsigned int num_connectors)
>> +{
>> +	struct drm_client_display *display;
>> +	unsigned int i;
>> +
>> +	for (i = 0; i < num_connectors; i++) {
>> +		if (!connectors[i] || connectors[i]->has_tile)
>> +			continue;
>> +		break;
>> +	}
>> +
>> +	if (i == num_connectors)
>> +		return NULL;
>> +
>> +	display = drm_client_display_alloc(client, 1);
>> +	if (!display)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	display->connectors[0] = connectors[i];
>> +	connectors[i] = NULL;
>> +	display->modes = display->connectors[0]->modes;
>> +	display->num_modes = display->connectors[0]->num_modes;
>> +
>> +	return display;
>> +}
>> +
>> +/* Get connectors and bundle them up into displays */
>> +static int drm_client_get_displays(struct drm_client_dev *client,
>> +				   struct drm_client_display ***displays)
>> +{
>> +	int ret, num_connectors, num_displays = 0;
>> +	struct drm_client_connector **connectors;
>> +	struct drm_client_display *display;
>> +	unsigned int i;
>> +
>> +	ret = drm_client_get_file(client);
>> +	if (ret)
>> +		return ret;
>> +
>> +	num_connectors = drm_client_get_connectors(client, &connectors);
>> +	if (num_connectors <= 0) {
>> +		ret = num_connectors;
>> +		goto err_put_file;
>> +	}
>> +
>> +	*displays = kcalloc(num_connectors, sizeof(*displays), GFP_KERNEL);
>> +	if (!(*displays)) {
>> +		ret = -ENOMEM;
>> +		goto err_free;
>> +	}
>> +
>> +	display = drm_client_connector_pick_cloned(client, connectors,
>> +						   num_connectors);
>> +	if (IS_ERR(display)) {
>> +		ret = PTR_ERR(display);
>> +		goto err_free;
>> +	}
>> +	if (display)
>> +		(*displays)[num_displays++] = display;
>> +
>> +	for (i = 0; i < num_connectors; i++) {
>> +		display = drm_client_connector_pick_tile(client, connectors,
>> +							 num_connectors);
>> +		if (IS_ERR(display)) {
>> +			ret = PTR_ERR(display);
>> +			goto err_free;
>> +		}
>> +		if (!display)
>> +			break;
>> +		(*displays)[num_displays++] = display;
>> +	}
>> +
>> +	for (i = 0; i < num_connectors; i++) {
>> +		display = drm_client_connector_pick_not_tile(client, connectors,
>> +							     num_connectors);
>> +		if (IS_ERR(display)) {
>> +			ret = PTR_ERR(display);
>> +			goto err_free;
>> +		}
>> +		if (!display)
>> +			break;
>> +		(*displays)[num_displays++] = display;
>> +	}
>> +
>> +	for (i = 0; i < num_connectors; i++) {
>> +		if (connectors[i]) {
>> +			DRM_INFO("Connector %u fell through the cracks.\n",
>> +				 connectors[i]->conn_id);
>> +			drm_client_connector_free(connectors[i]);
>> +		}
>> +	}
>> +
>> +	drm_client_put_file(client);
>> +	kfree(connectors);
>> +
>> +	return num_displays;
>> +
>> +err_free:
>> +	for (i = 0; i < num_displays; i++)
>> +		drm_client_display_free((*displays)[i]);
>> +	kfree(*displays);
>> +	*displays = NULL;
>> +	for (i = 0; i < num_connectors; i++)
>> +		drm_client_connector_free(connectors[i]);
>> +	kfree(connectors);
>> +err_put_file:
>> +	drm_client_put_file(client);
>> +
>> +	return ret;
>> +}
>> +
>> +static bool
>> +drm_client_display_is_enabled(struct drm_client_display *display, bool strict)
>> +{
>> +	unsigned int i;
>> +
>> +	if (!display->num_modes)
>> +		return false;
>> +
>> +	for (i = 0; i < display->num_connectors; i++)
>> +		if (!drm_client_connector_is_enabled(display->connectors[i], strict))
>> +			return false;
>> +
>> +	return true;
>> +}
>> +
>> +/**
>> + * drm_client_display_get_first_enabled - Get first enabled display
>> + * @client: DRM client
>> + * @strict: If true the connector(s) have to be connected, if false they can
>> + *          also have unknown status.
>> + *
>> + * This function gets all connectors and bundles them into displays
>> + * (tiled/cloned). It then picks the first one with connectors that is enabled
>> + * according to @strict.
>> + *
>> + * Returns:
>> + * Pointer to a client display if such a display was found, NULL if not found
>> + * or an error pointer on failure.
>> + */
>> +struct drm_client_display *
>> +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict)
>> +{
>> +	struct drm_client_display **displays, *display = NULL;
>> +	int num_displays;
>> +	unsigned int i;
>> +
>> +	num_displays = drm_client_get_displays(client, &displays);
>> +	if (num_displays < 0)
>> +		return ERR_PTR(num_displays);
>> +	if (!num_displays)
>> +		return NULL;
>> +
>> +	for (i = 0; i < num_displays; i++) {
>> +		if (!display &&
>> +		    drm_client_display_is_enabled(displays[i], strict)) {
>> +			display = displays[i];
>> +			continue;
>> +		}
>> +		drm_client_display_free(displays[i]);
>> +	}
>> +
>> +	kfree(displays);
>> +
>> +	return display;
>> +}
>> +EXPORT_SYMBOL(drm_client_display_get_first_enabled);
>> +
>> +unsigned int
>> +drm_client_display_preferred_depth(struct drm_client_display *display)
>> +{
>> +	struct drm_connector *conn;
>> +	unsigned int ret;
>> +
>> +	conn = drm_connector_lookup(display->client->dev, NULL,
>> +				    display->connectors[0]->conn_id);
>> +	if (!conn)
>> +		return 0;
>> +
>> +	if (conn->cmdline_mode.bpp_specified)
>> +		ret = conn->cmdline_mode.bpp;
>> +	else
>> +		ret = display->client->dev->mode_config.preferred_depth;
>> +
>> +	drm_connector_put(conn);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_display_preferred_depth);
>> +
>> +int drm_client_display_dpms(struct drm_client_display *display, int mode)
>> +{
>> +	struct drm_mode_obj_set_property prop;
>> +
>> +	prop.value = mode;
>> +	prop.prop_id = display->client->dev->mode_config.dpms_property->base.id;
>> +	prop.obj_id = display->connectors[0]->conn_id;
>> +	prop.obj_type = DRM_MODE_OBJECT_CONNECTOR;
>> +
>> +	return drm_mode_obj_set_property(display->client->dev, &prop,
>> +					 display->client->file);
>> +}
>> +EXPORT_SYMBOL(drm_client_display_dpms);
>> +
>> +int drm_client_display_wait_vblank(struct drm_client_display *display)
>> +{
>> +	struct drm_crtc *crtc;
>> +	union drm_wait_vblank vblank_req = {
>> +		.request = {
>> +			.type = _DRM_VBLANK_RELATIVE,
>> +			.sequence = 1,
>> +		},
>> +	};
>> +
>> +	crtc = drm_crtc_find(display->client->dev, display->client->file,
>> +			     display->connectors[0]->crtc_id);
>> +	if (!crtc)
>> +		return -ENOENT;
>> +
>> +	vblank_req.request.type |= drm_crtc_index(crtc) << _DRM_VBLANK_HIGH_CRTC_SHIFT;
>> +
>> +	return drm_wait_vblank(display->client->dev, &vblank_req,
>> +			       display->client->file);
>> +}
>> +EXPORT_SYMBOL(drm_client_display_wait_vblank);
>> +
>> +static int drm_client_get_crtc_index(struct drm_client_dev *client, u32 id)
>> +{
>> +	int i;
>> +
>> +	for (i = 0; i < client->num_crtcs; i++)
>> +		if (client->crtcs[i] == id)
>> +			return i;
>> +
>> +	return -ENOENT;
>> +}
>> +
>> +static int drm_client_display_find_crtcs(struct drm_client_display *display)
>> +{
>> +	struct drm_client_dev *client = display->client;
>> +	struct drm_device *dev = client->dev;
>> +	struct drm_file *file = client->file;
>> +	u32 encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
>> +	unsigned int i, j, available_crtcs = ~0;
>> +	struct drm_mode_get_connector conn_req;
>> +	struct drm_mode_get_encoder enc_req;
>> +	int ret;
>> +
>> +	/* Already done? */
>> +	if (display->connectors[0]->crtc_id)
>> +		return 0;
>> +
>> +	for (i = 0; i < display->num_connectors; i++) {
>> +		u32 active_crtcs = 0, crtcs_for_connector = 0;
>> +		int crtc_idx;
>> +
>> +		memset(&conn_req, 0, sizeof(conn_req));
>> +		conn_req.connector_id = display->connectors[i]->conn_id;
>> +		conn_req.encoders_ptr = (unsigned long)(encoder_ids);
>> +		conn_req.count_encoders = DRM_CONNECTOR_MAX_ENCODER;
>> +		ret = drm_mode_getconnector(dev, &conn_req, file, false);
>> +		if (ret)
>> +			return ret;
>> +
>> +		if (conn_req.encoder_id) {
>> +			memset(&enc_req, 0, sizeof(enc_req));
>> +			enc_req.encoder_id = conn_req.encoder_id;
>> +			ret = drm_mode_getencoder(dev, &enc_req, file);
>> +			if (ret)
>> +				return ret;
>> +			crtcs_for_connector |= enc_req.possible_crtcs;
>> +			if (crtcs_for_connector & available_crtcs)
>> +				goto found;
>> +		}
>> +
>> +		for (j = 0; j < conn_req.count_encoders; j++) {
>> +			memset(&enc_req, 0, sizeof(enc_req));
>> +			enc_req.encoder_id = encoder_ids[j];
>> +			ret = drm_mode_getencoder(dev, &enc_req, file);
>> +			if (ret)
>> +				return ret;
>> +
>> +			crtcs_for_connector |= enc_req.possible_crtcs;
>> +
>> +			if (enc_req.crtc_id) {
>> +				crtc_idx = drm_client_get_crtc_index(client, enc_req.crtc_id);
>> +				if (crtc_idx >= 0)
>> +					active_crtcs |= 1 << crtc_idx;
>> +			}
>> +		}
>> +
>> +found:
>> +		crtcs_for_connector &= available_crtcs;
>> +		active_crtcs &= available_crtcs;
>> +
>> +		if (!crtcs_for_connector)
>> +			return -ENOENT;
>> +
>> +		if (active_crtcs)
>> +			crtc_idx = ffs(active_crtcs) - 1;
>> +		else
>> +			crtc_idx = ffs(crtcs_for_connector) - 1;
>> +
>> +		if (crtc_idx >= client->num_crtcs)
>> +			return -EINVAL;
>> +
>> +		display->connectors[i]->crtc_id = client->crtcs[crtc_idx];
>> +		available_crtcs &= ~BIT(crtc_idx);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +/**
>> + * drm_client_display_commit_mode - Commit a mode to the crtc(s)
>> + * @display: Client display
>> + * @fb_id: Framebuffer id
>> + * @mode: Video mode
>> + *
>> + * Returns:
>> + * Zero on success, negative error code on failure.
>> + */
>> +int drm_client_display_commit_mode(struct drm_client_display *display,
>> +				   u32 fb_id, struct drm_mode_modeinfo *mode)
>> +{
>> +	struct drm_client_dev *client = display->client;
>> +	struct drm_device *dev = client->dev;
>> +	unsigned int num_crtcs = client->num_crtcs;
>> +	struct drm_file *file = client->file;
>> +	unsigned int *xoffsets = NULL, *yoffsets = NULL;
>> +	struct drm_mode_crtc *crtc_reqs, *req;
>> +	u32 cloned_conn_ids[2];
>> +	unsigned int i;
>> +	int idx, ret;
>> +
>> +	ret = drm_client_display_find_crtcs(display);
>> +	if (ret)
>> +		return ret;
>> +
>> +	crtc_reqs = kcalloc(num_crtcs, sizeof(*crtc_reqs), GFP_KERNEL);
>> +	if (!crtc_reqs)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < num_crtcs; i++)
>> +		crtc_reqs[i].crtc_id = client->crtcs[i];
>> +
>> +	if (drm_client_display_is_tiled(display)) {
>> +		/* TODO calculate tile crtc offsets */
>> +	}
>> +
>> +	for (i = 0; i < display->num_connectors; i++) {
>> +		idx = drm_client_get_crtc_index(client, display->connectors[i]->crtc_id);
>> +		if (idx < 0)
>> +			return -ENOENT;
>> +
>> +		req = &crtc_reqs[idx];
>> +
>> +		req->fb_id = fb_id;
>> +		if (xoffsets) {
>> +			req->x = xoffsets[i];
>> +			req->y = yoffsets[i];
>> +		}
>> +		req->mode_valid = 1;
>> +		req->mode = *mode;
>> +
>> +		if (display->cloned) {
>> +			cloned_conn_ids[0] = display->connectors[0]->conn_id;
>> +			cloned_conn_ids[1] = display->connectors[1]->conn_id;
>> +			req->set_connectors_ptr = (unsigned long)(cloned_conn_ids);
>> +			req->count_connectors = 2;
>> +			break;
>> +		}
>> +
>> +		req->set_connectors_ptr = (unsigned long)(&display->connectors[i]->conn_id);
>> +		req->count_connectors = 1;
>> +	}
>> +
>> +	for (i = 0; i < num_crtcs; i++) {
>> +		ret = drm_mode_setcrtc(dev, &crtc_reqs[i], file, false);
>> +		if (ret)
>> +			break;
>> +	}
>> +
>> +	kfree(xoffsets);
>> +	kfree(yoffsets);
>> +	kfree(crtc_reqs);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_display_commit_mode);
>> +
>> +unsigned int drm_client_display_current_fb(struct drm_client_display *display)
>> +{
>> +	struct drm_client_dev *client = display->client;
>> +	int ret;
>> +	struct drm_mode_crtc crtc_req = {
>> +		.crtc_id = display->connectors[0]->crtc_id,
>> +	};
>> +
>> +	ret = drm_mode_getcrtc(client->dev, &crtc_req, client->file);
>> +	if (ret)
>> +		return 0;
>> +
>> +	return crtc_req.fb_id;
>> +}
>> +EXPORT_SYMBOL(drm_client_display_current_fb);
>> +
>> +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
>> +			     struct drm_clip_rect *clips, unsigned int num_clips)
>> +{
>> +	struct drm_client_dev *client = display->client;
>> +	struct drm_mode_fb_dirty_cmd dirty_req = {
>> +		.fb_id = fb_id,
>> +		.clips_ptr = (unsigned long)clips,
>> +		.num_clips = num_clips,
>> +	};
>> +	int ret;
>> +
>> +	if (display->no_flushing)
>> +		return 0;
>> +
>> +	ret = drm_mode_dirtyfb(client->dev, &dirty_req, client->file, false);
>> +	if (ret == -ENOSYS) {
>> +		ret = 0;
>> +		display->no_flushing = true;
>> +	}
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_display_flush);
>> +
>> +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
>> +				 bool event)
>> +{
>> +	struct drm_client_dev *client = display->client;
>> +	struct drm_mode_crtc_page_flip_target page_flip_req = {
>> +		.crtc_id = display->connectors[0]->crtc_id,
>> +		.fb_id = fb_id,
>> +	};
>> +
>> +	if (event)
>> +		page_flip_req.flags = DRM_MODE_PAGE_FLIP_EVENT;
>> +
>> +	return drm_mode_page_flip(client->dev, &page_flip_req, client->file);
>> +	/*
>> +	 * TODO:
>> +	 * Where do we flush on page flip? Should the driver handle that?
>> +	 */
>> +}
>> +EXPORT_SYMBOL(drm_client_display_page_flip);
>> +
>> +/**
>> + * drm_client_framebuffer_create - Create a client framebuffer
>> + * @client: DRM client
>> + * @mode: Display mode to create a buffer for
>> + * @format: Buffer format
>> + *
>> + * This function creates a &drm_client_buffer which consists of a
>> + * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf
>> + * exported to aquire a virtual address which is stored in
>> + * &drm_client_buffer->vaddr.
>> + * Call drm_client_framebuffer_delete() to free the buffer.
>> + *
>> + * Returns:
>> + * Pointer to a client buffer or an error pointer on failure.
>> + */
>> +struct drm_client_buffer *
>> +drm_client_framebuffer_create(struct drm_client_dev *client,
>> +			      struct drm_mode_modeinfo *mode, u32 format)
>> +{
>> +	struct drm_client_buffer *buffer;
>> +	int ret;
>> +
>> +	buffer = drm_client_buffer_create(client, mode->hdisplay,
>> +					  mode->vdisplay, format);
>> +	if (IS_ERR(buffer))
>> +		return buffer;
>> +
>> +	ret = drm_client_buffer_addfb(buffer, mode);
>> +	if (ret) {
>> +		drm_client_buffer_delete(buffer);
>> +		return ERR_PTR(ret);
>> +	}
>> +
>> +	return buffer;
>> +}
>> +EXPORT_SYMBOL(drm_client_framebuffer_create);
>> +
>> +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
>> +{
>> +	drm_client_buffer_rmfb(buffer);
>> +	drm_client_buffer_delete(buffer);
>> +}
>> +EXPORT_SYMBOL(drm_client_framebuffer_delete);
>> +
>> +struct drm_client_buffer *
>> +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
>> +			 u32 format)
>> +{
>> +	struct drm_mode_create_dumb dumb_args = { 0 };
>> +	struct drm_prime_handle prime_args = { 0 };
>> +	struct drm_client_buffer *buffer;
>> +	struct dma_buf *dma_buf;
>> +	void *vaddr;
>> +	int ret;
>> +
>> +	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
>> +	if (!buffer)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	ret = drm_client_get_file(client);
>> +	if (ret)
>> +		goto err_free;
>> +
>> +	buffer->client = client;
>> +	buffer->width = width;
>> +	buffer->height = height;
>> +	buffer->format = format;
>> +
>> +	dumb_args.width = buffer->width;
>> +	dumb_args.height = buffer->height;
>> +	dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
>> +	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
>> +	if (ret)
>> +		goto err_put_file;
>> +
>> +	buffer->handle = dumb_args.handle;
>> +	buffer->pitch = dumb_args.pitch;
>> +	buffer->size = dumb_args.size;
>> +
>> +	prime_args.handle = dumb_args.handle;
>> +	ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file);
>> +	if (ret)
>> +		goto err_delete;
>> +
>> +	dma_buf = dma_buf_get(prime_args.fd);
>> +	if (IS_ERR(dma_buf)) {
>> +		ret = PTR_ERR(dma_buf);
>> +		goto err_delete;
>> +	}
>> +
>> +	buffer->dma_buf = dma_buf;
>> +
>> +	vaddr = dma_buf_vmap(dma_buf);
>> +	if (!vaddr) {
>> +		ret = -ENOMEM;
>> +		goto err_delete;
>> +	}
>> +
>> +	buffer->vaddr = vaddr;
>> +
>> +	return buffer;
>> +
>> +err_delete:
>> +	drm_client_buffer_delete(buffer);
>> +err_put_file:
>> +	drm_client_put_file(client);
>> +err_free:
>> +	kfree(buffer);
>> +
>> +	return ERR_PTR(ret);
>> +}
>> +EXPORT_SYMBOL(drm_client_buffer_create);
>> +
>> +void drm_client_buffer_delete(struct drm_client_buffer *buffer)
>> +{
>> +	if (!buffer)
>> +		return;
>> +
>> +	if (buffer->vaddr)
>> +		dma_buf_vunmap(buffer->dma_buf, buffer->vaddr);
>> +
>> +	if (buffer->dma_buf)
>> +		dma_buf_put(buffer->dma_buf);
>> +
>> +	drm_mode_destroy_dumb(buffer->client->dev, buffer->handle,
>> +			      buffer->client->file);
>> +	drm_client_put_file(buffer->client);
>> +	kfree(buffer);
>> +}
>> +EXPORT_SYMBOL(drm_client_buffer_delete);
>> +
>> +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
>> +			    struct drm_mode_modeinfo *mode)
>> +{
>> +	struct drm_client_dev *client = buffer->client;
>> +	struct drm_mode_fb_cmd2 fb_req = { };
>> +	unsigned int num_fbs, *fb_ids;
>> +	int i, ret;
>> +
>> +	if (buffer->num_fbs)
>> +		return -EINVAL;
>> +
>> +	if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
>> +		return -EINVAL;
>> +
>> +	num_fbs = buffer->height / mode->vdisplay;
>> +	fb_ids = kcalloc(num_fbs, sizeof(*fb_ids), GFP_KERNEL);
>> +	if (!fb_ids)
>> +		return -ENOMEM;
>> +
>> +	fb_req.width = mode->hdisplay;
>> +	fb_req.height = mode->vdisplay;
>> +	fb_req.pixel_format = buffer->format;
>> +	fb_req.handles[0] = buffer->handle;
>> +	fb_req.pitches[0] = buffer->pitch;
>> +
>> +	for (i = 0; i < num_fbs; i++) {
>> +		fb_req.offsets[0] = i * mode->vdisplay * buffer->pitch;
>> +		ret = drm_mode_addfb2(client->dev, &fb_req, client->file,
>> +				      client->funcs->name);
>> +		if (ret)
>> +			goto err_remove;
>> +		fb_ids[i] = fb_req.fb_id;
>> +	}
>> +
>> +	buffer->fb_ids = fb_ids;
>> +	buffer->num_fbs = num_fbs;
>> +
>> +	return 0;
>> +
>> +err_remove:
>> +	for (i--; i >= 0; i--)
>> +		drm_mode_rmfb(client->dev, fb_ids[i], client->file);
>> +	kfree(fb_ids);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_buffer_addfb);
>> +
>> +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
>> +{
>> +	unsigned int i;
>> +	int ret;
>> +
>> +	if (!buffer || !buffer->num_fbs)
>> +		return 0;
>> +
>> +	for (i = 0; i < buffer->num_fbs; i++) {
>> +		ret = drm_mode_rmfb(buffer->client->dev, buffer->fb_ids[i],
>> +				    buffer->client->file);
>> +		if (ret)
>> +			DRM_DEV_ERROR(buffer->client->dev->dev,
>> +				      "Error removing FB:%u (%d)\n",
>> +				      buffer->fb_ids[i], ret);
>> +	}
>> +
>> +	kfree(buffer->fb_ids);
>> +	buffer->fb_ids = NULL;
>> +	buffer->num_fbs = 0;
>> +
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL(drm_client_buffer_rmfb);
>> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
>> index f869de185986..db161337d87c 100644
>> --- a/drivers/gpu/drm/drm_drv.c
>> +++ b/drivers/gpu/drm/drm_drv.c
>> @@ -33,6 +33,7 @@
>>   #include <linux/mount.h>
>>   #include <linux/slab.h>
>>   
>> +#include <drm/drm_client.h>
>>   #include <drm/drm_drv.h>
>>   #include <drm/drmP.h>
>>   
>> @@ -463,6 +464,7 @@ int drm_dev_init(struct drm_device *dev,
>>   	dev->driver = driver;
>>   
>>   	INIT_LIST_HEAD(&dev->filelist);
>> +	INIT_LIST_HEAD(&dev->filelist_internal);
>>   	INIT_LIST_HEAD(&dev->ctxlist);
>>   	INIT_LIST_HEAD(&dev->vmalist);
>>   	INIT_LIST_HEAD(&dev->maplist);
>> @@ -787,6 +789,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
>>   		 dev->dev ? dev_name(dev->dev) : "virtual device",
>>   		 dev->primary->index);
>>   
>> +	drm_client_dev_register(dev);
>> +
>>   	goto out_unlock;
>>   
>>   err_minors:
>> @@ -839,6 +843,8 @@ void drm_dev_unregister(struct drm_device *dev)
>>   	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
>>   	drm_minor_unregister(dev, DRM_MINOR_RENDER);
>>   	drm_minor_unregister(dev, DRM_MINOR_CONTROL);
>> +
>> +	drm_client_dev_unregister(dev);
>>   }
>>   EXPORT_SYMBOL(drm_dev_unregister);
>>   
>> diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
>> index 55505378df47..bcc688e58776 100644
>> --- a/drivers/gpu/drm/drm_file.c
>> +++ b/drivers/gpu/drm/drm_file.c
>> @@ -35,6 +35,7 @@
>>   #include <linux/slab.h>
>>   #include <linux/module.h>
>>   
>> +#include <drm/drm_client.h>
>>   #include <drm/drm_file.h>
>>   #include <drm/drmP.h>
>>   
>> @@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
>>   
>>   	if (drm_core_check_feature(dev, DRIVER_LEGACY))
>>   		drm_legacy_dev_reinit(dev);
>> +
>> +	drm_client_dev_lastclose(dev);
>>   }
>>   
>>   /**
>> diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c
>> index 2d1643bdae78..5d2a6c6717f5 100644
>> --- a/drivers/gpu/drm/drm_probe_helper.c
>> +++ b/drivers/gpu/drm/drm_probe_helper.c
>> @@ -33,6 +33,7 @@
>>   #include <linux/moduleparam.h>
>>   
>>   #include <drm/drmP.h>
>> +#include <drm/drm_client.h>
>>   #include <drm/drm_crtc.h>
>>   #include <drm/drm_fourcc.h>
>>   #include <drm/drm_crtc_helper.h>
>> @@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
>>   	drm_sysfs_hotplug_event(dev);
>>   	if (dev->mode_config.funcs->output_poll_changed)
>>   		dev->mode_config.funcs->output_poll_changed(dev);
>> +
>> +	drm_client_dev_hotplug(dev);
>>   }
>>   EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
>>   
>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>> new file mode 100644
>> index 000000000000..88f6f87919c5
>> --- /dev/null
>> +++ b/include/drm/drm_client.h
>> @@ -0,0 +1,192 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +
>> +#include <linux/mutex.h>
>> +
>> +struct dma_buf;
>> +struct drm_clip_rect;
>> +struct drm_device;
>> +struct drm_file;
>> +struct drm_mode_modeinfo;
>> +
>> +struct drm_client_dev;
>> +
>> +/**
>> + * struct drm_client_funcs - DRM client  callbacks
>> + */
>> +struct drm_client_funcs {
>> +	/**
>> +	 * @name:
>> +	 *
>> +	 * Name of the client.
>> +	 */
>> +	const char *name;
>> +
>> +	/**
>> +	 * @new:
>> +	 *
>> +	 * Called when a client or a &drm_device is registered.
>> +	 * If the callback returns anything but zero, then this client instance
>> +	 * is dropped.
>> +	 *
>> +	 * This callback is mandatory.
>> +	 */
>> +	int (*new)(struct drm_client_dev *client);
>> +
>> +	/**
>> +	 * @remove:
>> +	 *
>> +	 * Called when a &drm_device is unregistered or the client is
>> +	 * unregistered. If zero is returned drm_client_free() is called
>> +	 * automatically. If the client can't drop it's resources it should
>> +	 * return non-zero and call drm_client_free() later.
>> +	 *
>> +	 * This callback is optional.
>> +	 */
>> +	int (*remove)(struct drm_client_dev *client);
>> +
>> +	/**
>> +	 * @lastclose:
>> +	 *
>> +	 * Called on drm_lastclose(). The first client instance in the list
>> +	 * that returns zero gets the privilege to restore and no more clients
>> +	 * are called.
>> +	 *
>> +	 * This callback is optional.
>> +	 */
>> +	int (*lastclose)(struct drm_client_dev *client);
>> +
>> +	/**
>> +	 * @hotplug:
>> +	 *
>> +	 * Called on drm_kms_helper_hotplug_event().
>> +	 *
>> +	 * This callback is optional.
>> +	 */
>> +	int (*hotplug)(struct drm_client_dev *client);
>> +
>> +// TODO
>> +//	void (*suspend)(struct drm_client_dev *client);
>> +//	void (*resume)(struct drm_client_dev *client);
>> +};
>> +
>> +/**
>> + * struct drm_client_dev - DRM client instance
>> + */
>> +struct drm_client_dev {
>> +	struct list_head list;
>> +	struct drm_device *dev;
>> +	const struct drm_client_funcs *funcs;
>> +	struct mutex lock;
>> +	struct drm_file *file;
>> +	unsigned int file_ref_count;
>> +	u32 *crtcs;
>> +	unsigned int num_crtcs;
>> +	u32 min_width;
>> +	u32 max_width;
>> +	u32 min_height;
>> +	u32 max_height;
>> +	void *private;
>> +};
>> +
>> +void drm_client_free(struct drm_client_dev *client);
>> +int drm_client_register(const struct drm_client_funcs *funcs);
>> +void drm_client_unregister(const struct drm_client_funcs *funcs);
>> +
>> +void drm_client_dev_register(struct drm_device *dev);
>> +void drm_client_dev_unregister(struct drm_device *dev);
>> +void drm_client_dev_hotplug(struct drm_device *dev);
>> +void drm_client_dev_lastclose(struct drm_device *dev);
>> +
>> +int drm_client_get_file(struct drm_client_dev *client);
>> +void drm_client_put_file(struct drm_client_dev *client);
>> +struct drm_event *
>> +drm_client_read_event(struct drm_client_dev *client, bool block);
>> +
>> +struct drm_client_connector {
>> +	unsigned int conn_id;
>> +	unsigned int status;
>> +	unsigned int crtc_id;
>> +	struct drm_mode_modeinfo *modes;
>> +	unsigned int num_modes;
>> +	bool has_tile;
>> +	int tile_group;
>> +	u8 tile_h_loc, tile_v_loc;
>> +};
>> +
>> +struct drm_client_display {
>> +	struct drm_client_dev *client;
>> +
>> +	struct drm_client_connector **connectors;
>> +	unsigned int num_connectors;
>> +
>> +	struct mutex modes_lock;
>> +	struct drm_mode_modeinfo *modes;
>> +	unsigned int num_modes;
>> +
>> +	bool cloned;
>> +	bool no_flushing;
>> +};
>> +
>> +void drm_client_display_free(struct drm_client_display *display);
>> +struct drm_client_display *
>> +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict);
>> +
>> +int drm_client_display_update_modes(struct drm_client_display *display,
>> +				    bool *mode_changed);
>> +
>> +static inline bool
>> +drm_client_display_is_tiled(struct drm_client_display *display)
>> +{
>> +	return !display->cloned && display->num_connectors > 1;
>> +}
>> +
>> +int drm_client_display_dpms(struct drm_client_display *display, int mode);
>> +int drm_client_display_wait_vblank(struct drm_client_display *display);
>> +
>> +struct drm_mode_modeinfo *
>> +drm_client_display_first_mode(struct drm_client_display *display);
>> +struct drm_mode_modeinfo *
>> +drm_client_display_next_mode(struct drm_client_display *display,
>> +			     struct drm_mode_modeinfo *mode);
>> +
>> +#define drm_client_display_for_each_mode(display, mode) \
>> +	for (mode = drm_client_display_first_mode(display); mode; \
>> +	     mode = drm_client_display_next_mode(display, mode))
>> +
>> +unsigned int
>> +drm_client_display_preferred_depth(struct drm_client_display *display);
>> +
>> +int drm_client_display_commit_mode(struct drm_client_display *display,
>> +				   u32 fb_id, struct drm_mode_modeinfo *mode);
>> +unsigned int drm_client_display_current_fb(struct drm_client_display *display);
>> +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
>> +			     struct drm_clip_rect *clips, unsigned int num_clips);
>> +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
>> +				 bool event);
>> +
>> +struct drm_client_buffer {
>> +	struct drm_client_dev *client;
>> +	u32 width;
>> +	u32 height;
>> +	u32 format;
>> +	u32 handle;
>> +	u32 pitch;
>> +	u64 size;
>> +	struct dma_buf *dma_buf;
>> +	void *vaddr;
>> +
>> +	unsigned int *fb_ids;
>> +	unsigned int num_fbs;
>> +};
>> +
>> +struct drm_client_buffer *
>> +drm_client_framebuffer_create(struct drm_client_dev *client,
>> +			      struct drm_mode_modeinfo *mode, u32 format);
>> +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
>> +struct drm_client_buffer *
>> +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
>> +			 u32 format);
>> +void drm_client_buffer_delete(struct drm_client_buffer *buffer);
>> +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
>> +			    struct drm_mode_modeinfo *mode);
>> +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer);
>> diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
>> index 7c4fa32f3fc6..32dfed3d5a86 100644
>> --- a/include/drm/drm_device.h
>> +++ b/include/drm/drm_device.h
>> @@ -67,6 +67,7 @@ struct drm_device {
>>   
>>   	struct mutex filelist_mutex;
>>   	struct list_head filelist;
>> +	struct list_head filelist_internal;
>>   
>>   	/** \name Memory management */
>>   	/*@{ */
>> diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
>> index 5176c3797680..39af8a4be7b3 100644
>> --- a/include/drm/drm_file.h
>> +++ b/include/drm/drm_file.h
>> @@ -248,6 +248,13 @@ struct drm_file {
>>   	 */
>>   	void *driver_priv;
>>   
>> +	/**
>> +	 * @user_priv:
>> +	 *
>> +	 * Optional pointer for user private data. Useful for in-kernel clients.
>> +	 */
>> +	void *user_priv;
>> +
>>   	/**
>>   	 * @fbs:
>>   	 *
>> -- 
>> 2.15.1
>>

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

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

* Re: [RFC v3 09/12] drm: Add API for in-kernel clients
  2018-03-08 17:12     ` Noralf Trønnes
@ 2018-03-12 16:51       ` Daniel Vetter
  2018-03-12 20:21         ` Noralf Trønnes
  0 siblings, 1 reply; 26+ messages in thread
From: Daniel Vetter @ 2018-03-12 16:51 UTC (permalink / raw)
  To: Noralf Tr??nnes
  Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart, dh.herrmann

On Thu, Mar 08, 2018 at 06:12:11PM +0100, Noralf Tr??nnes wrote:
> 
> Den 06.03.2018 09.56, skrev Daniel Vetter:
> > On Thu, Feb 22, 2018 at 09:06:50PM +0100, Noralf Tr??nnes wrote:
> > > This adds an API for writing in-kernel clients.
> > > 
> > > TODO:
> > > - Flesh out and complete documentation.
> > > - Cloned displays is not tested.
> > > - Complete tiled display support and test it.
> > > - Test plug/unplug different monitors.
> > > - A runtime knob to prevent clients from attaching for debugging purposes.
> > > - Maybe a way to unbind individual client instances.
> > > - Maybe take the sysrq support in drm_fb_helper and move it here somehow.
> > > - Add suspend/resume callbacks.
> > >    Does anyone know why fbdev requires suspend/resume?
> > > 
> > > Signed-off-by: Noralf Tr??nnes <noralf@tronnes.org>
> > The core client api I like. Some of the opens I'm seeing:
> > 
> > - If we go with using the internal kms api directly instead of IOCTL
> >    wrappers then a huge pile of the functions you have here aren't needed
> >    (e.g. all the event stuff we can just directly use vblank events instead
> >    of all the wrapping). I'm leaning ever more into that direction, since
> >    much less code to add.
> 
> Looking at drm_fb_helper once again I now see an opportunity to simplify
> the modesetting code by nuking drm_fb_helper_connector and stop
> maintaining an array of connectors. It looks to be possible to just
> create an array temporarily in drm_setup_crtcs() for the duration of the
> function. The connectors we care about are ref counted and attached to
> modesets. This would remove the need for drm_fb_helper_add_one_connector().
> 
> So I might be able to do struct drm_fb_helper_crtc -> drm_client_crtc
> and let the client API take over drm_setup_crtcs(). I'll give it a try.

I'm more wondering why we need drm_client_crtc at all, why is drm_crtc not
good enough. Or maybe I'm missing something. Imo ioctl wrappers should be
the exception where we really, really need them (because the backend of
the ioctl isn't implemented in a generic way, e.g. dumb buffers), not for
stuff where we already have a perfectly useable in-kernel abi (anything
related to modesetting).

And in a way the ioctl wrappers wouldn't really be ioctl wrappers
conceptually, but simple share some of the same code with the ioctl call
chain. The idea is to provide some minimal wrappar around the ->dumb*
callbacks.

Anything else is not needed, I think.

> There is one challenge I see upfront and that's the i915 fb_helper
> callback in drm_setup_crtcs().
> 
> > - The register/unregister model needs more thought. Allowing both clients
> >    to register whenever they want to, and drm_device instances to come and
> >    go is what fbcon has done, and the resulting locking is a horror show.
> > 
> >    I think if we require that all in-kernel drm_clients are registers when
> >    loading drm.ko (and enabled/disabled only per module options and
> >    Kconfig), then we can throw out all the locking. That avoids a lot of
> >    the headaches.
> > 
> >    2nd, if the list of clients is static over the lifetime of drm.ko, we
> >    also don't need to iterate existing drivers. Which avoids me having to
> >    review the iterator patch (that's the other aspect where fbcon totally
> >    falls over and essentially just ignores a bunch of races).
> 
> Are you talking about linking the clients into drm.ko?
> 
> drivers/gpu/drm/Makefile:
> 
> drm-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += client/drm_bootsplash.o
> 
> drivers/gpu/drm/drm_drv.c:
> 
> ??static int __init drm_core_init(void)
> ??{
> +?????? drm_bootsplash_register();
> +?????? drm_fbdev_register();
> ??}
> 
> drivers/gpu/drm/drm_internal.h:
> 
> #ifdef DRM_CLIENT_BOOTSPLASH
> void drm_bootsplash_register(void);
> #else
> static inline void drm_bootsplash_register(void)
> {
> }
> #endif
> 
> drivers/gpu/drm/client/drm_bootsplash.c:
> 
> static const struct drm_client_funcs drm_bootsplash_funcs = {
> ?????? .name?????? ?????? = "drm_bootsplash",
> ?????? .new?????? ?????? = drm_bootsplash_new,
> ?????? .remove?????? ?????? = drm_bootsplash_remove,
> ?????? .hotplug?????? = drm_bootsplash_hotplug,
> };
> 
> void drm_bootsplash_register(void)
> {
> ?????? drm_client_register(&drm_bootsplash_funcs);
> }
> 
> drivers/gpu/drm/drm_client.c:
> 
> static LIST_HEAD(drm_client_funcs_list);
> 
> void drm_client_register(const struct drm_client_funcs *funcs)
> {
> ?????? struct drm_client_funcs_entry *funcs_entry;
> 
> ?????? funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
> ?????? if (!funcs_entry) {
> ?????? ?????? DRM_ERROR("Failed to register: %s\n", funcs->name);
> ?????? ?????? return;
> ?????? }
> 
> ?????? funcs_entry->funcs = funcs;
> 
> ?????? list_add(&funcs_entry->list, &drm_client_funcs_list);
> 
> ?????? DRM_DEBUG_KMS("%s\n", funcs->name);
> }
> 
> 
> And each client having a runtime enable/disable knob:
> 
> drivers/gpu/drm/client/drm_bootsplash.c:
> 
> static bool drm_bootsplash_enabled = true;
> module_param_named(bootsplash_enabled, drm_bootsplash_enabled, bool, 0600);
> MODULE_PARM_DESC(bootsplash_enabled, "Enable bootsplash client
> [default=true]");


Yup, pretty much.

> Simple USB Display
> A few months back while looking at the udl shmem code, I got the idea
> that I could turn a Raspberry Pi Zero into a $5 USB to HDMI/DSI/DPI/DBI/TV
> adapter. The host side would be a simple tinydrm driver using the kernel
> compression lib to speed up transfers. The gadget/device side would be a
> userspace app decompressing the buffer into an exported dumb buffer.
> 
> While working with this client API I realized that I could use it and
> write a kernel gadget driver instead avoiding the challenge of going
> back and forth to userspace with the framebuffer. For such a client I
> would have preferred it to be a loadable module not linked into drm.ko
> to increase the chance that distributions would enable it.

I think that we can do as a loadable client, since you probably want some
configfs interface to define which drm driver it should control. The
reason behind the static list of built-in clients is purely for the
auto-register stuff that we want for fbdev emulation and other things.
Auto-registration where both sides can be loaded in any order is real pain
wrt locking. Explicit registration where you can load both sides is
totally fine and I think would cover your usb gadget display driver
use-case.

btw usb gadget driver to drive drm kms drivers sounds like a really cool
thing.
-Daniel

> 
> Noralf.
> 
> > > ---
> > >   drivers/gpu/drm/Kconfig             |    2 +
> > >   drivers/gpu/drm/Makefile            |    3 +-
> > >   drivers/gpu/drm/client/Kconfig      |    4 +
> > >   drivers/gpu/drm/client/Makefile     |    1 +
> > >   drivers/gpu/drm/client/drm_client.c | 1612 +++++++++++++++++++++++++++++++++++
> > I'd move this into main drm/ directory, it's fairly core stuff.
> > 
> > >   drivers/gpu/drm/drm_drv.c           |    6 +
> > >   drivers/gpu/drm/drm_file.c          |    3 +
> > >   drivers/gpu/drm/drm_probe_helper.c  |    3 +
> > >   include/drm/drm_client.h            |  192 +++++
> > >   include/drm/drm_device.h            |    1 +
> > >   include/drm/drm_file.h              |    7 +
> > >   11 files changed, 1833 insertions(+), 1 deletion(-)
> > >   create mode 100644 drivers/gpu/drm/client/Kconfig
> > >   create mode 100644 drivers/gpu/drm/client/Makefile
> > >   create mode 100644 drivers/gpu/drm/client/drm_client.c
> > >   create mode 100644 include/drm/drm_client.h
> > > 
> > > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> > > index deeefa7a1773..d4ae15f9ee9f 100644
> > > --- a/drivers/gpu/drm/Kconfig
> > > +++ b/drivers/gpu/drm/Kconfig
> > > @@ -154,6 +154,8 @@ config DRM_SCHED
> > >   	tristate
> > >   	depends on DRM
> > > +source "drivers/gpu/drm/client/Kconfig"
> > > +
> > >   source "drivers/gpu/drm/i2c/Kconfig"
> > >   source "drivers/gpu/drm/arm/Kconfig"
> > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > index 50093ff4479b..8e06dc7eeca1 100644
> > > --- a/drivers/gpu/drm/Makefile
> > > +++ b/drivers/gpu/drm/Makefile
> > > @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
> > >   		drm_encoder.o drm_mode_object.o drm_property.o \
> > >   		drm_plane.o drm_color_mgmt.o drm_print.o \
> > >   		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> > > -		drm_syncobj.o drm_lease.o
> > > +		drm_syncobj.o drm_lease.o client/drm_client.o
> > >   drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
> > >   drm-$(CONFIG_DRM_VM) += drm_vm.o
> > > @@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
> > >   obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
> > >   obj-$(CONFIG_DRM_PL111) += pl111/
> > >   obj-$(CONFIG_DRM_TVE200) += tve200/
> > > +obj-y			+= client/
> > > diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
> > > new file mode 100644
> > > index 000000000000..4bb8e4655ff7
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/client/Kconfig
> > > @@ -0,0 +1,4 @@
> > > +menu "DRM Clients"
> > > +	depends on DRM
> > > +
> > > +endmenu
> > > diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
> > > new file mode 100644
> > > index 000000000000..f66554cd5c45
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/client/Makefile
> > > @@ -0,0 +1 @@
> > > +# SPDX-License-Identifier: GPL-2.0
> > > diff --git a/drivers/gpu/drm/client/drm_client.c b/drivers/gpu/drm/client/drm_client.c
> > > new file mode 100644
> > > index 000000000000..a633bf747316
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/client/drm_client.c
> > > @@ -0,0 +1,1612 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +// Copyright 2018 Noralf Tr??nnes
> > > +
> > > +#include <linux/dma-buf.h>
> > > +#include <linux/list.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/module.h>
> > > +#include <linux/slab.h>
> > > +
> > > +#include <drm/drm_client.h>
> > > +#include <drm/drm_connector.h>
> > > +#include <drm/drm_drv.h>
> > > +#include <drm/drm_file.h>
> > > +#include <drm/drm_ioctl.h>
> > > +#include <drm/drmP.h>
> > > +
> > > +#include "drm_crtc_internal.h"
> > > +#include "drm_internal.h"
> > > +
> > > +struct drm_client_funcs_entry {
> > > +	struct list_head list;
> > > +	const struct drm_client_funcs *funcs;
> > > +};
> > > +
> > > +static LIST_HEAD(drm_client_list);
> > I think the client list itself should be on the drm_device, not in one
> > global list that mixes up all the clients of all the drm_devices.
> > 
> > I'll skip reviewing details since we have a bunch of high-level questions
> > to figure out first.
> > -Daniel
> > 
> > > +static LIST_HEAD(drm_client_funcs_list);
> > > +static DEFINE_MUTEX(drm_client_list_lock);
> > > +
> > > +static void drm_client_new(struct drm_device *dev,
> > > +			   const struct drm_client_funcs *funcs)
> > > +{
> > > +	struct drm_client_dev *client;
> > > +	int ret;
> > > +
> > > +	lockdep_assert_held(&drm_client_list_lock);
> > > +
> > > +	client = kzalloc(sizeof(*client), GFP_KERNEL);
> > > +	if (!client)
> > > +		return;
> > > +
> > > +	mutex_init(&client->lock);
> > > +	client->dev = dev;
> > > +	client->funcs = funcs;
> > > +
> > > +	ret = funcs->new(client);
> > > +	DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", funcs->name, ret);
> > > +	if (ret) {
> > > +		drm_client_free(client);
> > > +		return;
> > > +	}
> > > +
> > > +	list_add(&client->list, &drm_client_list);
> > > +}
> > > +
> > > +/**
> > > + * drm_client_free - Free DRM client resources
> > > + * @client: DRM client
> > > + *
> > > + * This is called automatically on client removal unless the client returns
> > > + * non-zero in the &drm_client_funcs->remove callback. The fbdev client does
> > > + * this when it can't close &drm_file because userspace has an open fd.
> > > + */
> > > +void drm_client_free(struct drm_client_dev *client)
> > > +{
> > > +	DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
> > > +	if (WARN_ON(client->file)) {
> > > +		client->file_ref_count = 1;
> > > +		drm_client_put_file(client);
> > > +	}
> > > +	mutex_destroy(&client->lock);
> > > +	kfree(client->crtcs);
> > > +	kfree(client);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_free);
> > > +
> > > +static void drm_client_remove(struct drm_client_dev *client)
> > > +{
> > > +	lockdep_assert_held(&drm_client_list_lock);
> > > +
> > > +	list_del(&client->list);
> > > +
> > > +	if (!client->funcs->remove || !client->funcs->remove(client))
> > > +		drm_client_free(client);
> > > +}
> > > +
> > > +/**
> > > + * drm_client_register - Register a DRM client
> > > + * @funcs: Client callbacks
> > > + *
> > > + * Returns:
> > > + * Zero on success, negative error code on failure.
> > > + */
> > > +int drm_client_register(const struct drm_client_funcs *funcs)
> > > +{
> > > +	struct drm_client_funcs_entry *funcs_entry;
> > > +	struct drm_device_list_iter iter;
> > > +	struct drm_device *dev;
> > > +
> > > +	funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
> > > +	if (!funcs_entry)
> > > +		return -ENOMEM;
> > > +
> > > +	funcs_entry->funcs = funcs;
> > > +
> > > +	mutex_lock(&drm_global_mutex);
> > > +	mutex_lock(&drm_client_list_lock);
> > > +
> > > +	drm_device_list_iter_begin(&iter);
> > > +	drm_for_each_device_iter(dev, &iter)
> > > +		if (drm_core_check_feature(dev, DRIVER_MODESET))
> > > +			drm_client_new(dev, funcs);
> > > +	drm_device_list_iter_end(&iter);
> > > +
> > > +	list_add(&funcs_entry->list, &drm_client_funcs_list);
> > > +
> > > +	mutex_unlock(&drm_client_list_lock);
> > > +	mutex_unlock(&drm_global_mutex);
> > > +
> > > +	DRM_DEBUG_KMS("%s\n", funcs->name);
> > > +
> > > +	return 0;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_register);
> > > +
> > > +/**
> > > + * drm_client_unregister - Unregister a DRM client
> > > + * @funcs: Client callbacks
> > > + */
> > > +void drm_client_unregister(const struct drm_client_funcs *funcs)
> > > +{
> > > +	struct drm_client_funcs_entry *funcs_entry;
> > > +	struct drm_client_dev *client, *tmp;
> > > +
> > > +	mutex_lock(&drm_client_list_lock);
> > > +
> > > +	list_for_each_entry_safe(client, tmp, &drm_client_list, list) {
> > > +		if (client->funcs == funcs)
> > > +			drm_client_remove(client);
> > > +	}
> > > +
> > > +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list) {
> > > +		if (funcs_entry->funcs == funcs) {
> > > +			list_del(&funcs_entry->list);
> > > +			kfree(funcs_entry);
> > > +			break;
> > > +		}
> > > +	}
> > > +
> > > +	mutex_unlock(&drm_client_list_lock);
> > > +
> > > +	DRM_DEBUG_KMS("%s\n", funcs->name);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_unregister);
> > > +
> > > +void drm_client_dev_register(struct drm_device *dev)
> > > +{
> > > +	struct drm_client_funcs_entry *funcs_entry;
> > > +
> > > +	/*
> > > +	 * Minors are created at the beginning of drm_dev_register(), but can
> > > +	 * be removed again if the function fails. Since we iterate DRM devices
> > > +	 * by walking DRM minors, we need to stay under this lock.
> > > +	 */
> > > +	lockdep_assert_held(&drm_global_mutex);
> > > +
> > > +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> > > +		return;
> > > +
> > > +	mutex_lock(&drm_client_list_lock);
> > > +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list)
> > > +		drm_client_new(dev, funcs_entry->funcs);
> > > +	mutex_unlock(&drm_client_list_lock);
> > > +}
> > > +
> > > +void drm_client_dev_unregister(struct drm_device *dev)
> > > +{
> > > +	struct drm_client_dev *client, *tmp;
> > > +
> > > +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> > > +		return;
> > > +
> > > +	mutex_lock(&drm_client_list_lock);
> > > +	list_for_each_entry_safe(client, tmp, &drm_client_list, list)
> > > +		if (client->dev == dev)
> > > +			drm_client_remove(client);
> > > +	mutex_unlock(&drm_client_list_lock);
> > > +}
> > > +
> > > +void drm_client_dev_hotplug(struct drm_device *dev)
> > > +{
> > > +	struct drm_client_dev *client;
> > > +	int ret;
> > > +
> > > +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> > > +		return;
> > > +
> > > +	mutex_lock(&drm_client_list_lock);
> > > +	list_for_each_entry(client, &drm_client_list, list)
> > > +		if (client->dev == dev && client->funcs->hotplug) {
> > > +			ret = client->funcs->hotplug(client);
> > > +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
> > > +					  client->funcs->name, ret);
> > > +		}
> > > +	mutex_unlock(&drm_client_list_lock);
> > > +}
> > > +
> > > +void drm_client_dev_lastclose(struct drm_device *dev)
> > > +{
> > > +	struct drm_client_dev *client;
> > > +	int ret;
> > > +
> > > +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> > > +		return;
> > > +
> > > +	mutex_lock(&drm_client_list_lock);
> > > +	list_for_each_entry(client, &drm_client_list, list)
> > > +		if (client->dev == dev && client->funcs->lastclose) {
> > > +			ret = client->funcs->lastclose(client);
> > > +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
> > > +					  client->funcs->name, ret);
> > > +		}
> > > +	mutex_unlock(&drm_client_list_lock);
> > > +}
> > > +
> > > +/* Get static info */
> > > +static int drm_client_init(struct drm_client_dev *client, struct drm_file *file)
> > > +{
> > > +	struct drm_mode_card_res card_res = {};
> > > +	struct drm_device *dev = client->dev;
> > > +	u32 *crtcs;
> > > +	int ret;
> > > +
> > > +	ret = drm_mode_getresources(dev, &card_res, file, false);
> > > +	if (ret)
> > > +		return ret;
> > > +	if (!card_res.count_crtcs)
> > > +		return -ENOENT;
> > > +
> > > +	crtcs = kmalloc_array(card_res.count_crtcs, sizeof(*crtcs), GFP_KERNEL);
> > > +	if (!crtcs)
> > > +		return -ENOMEM;
> > > +
> > > +	card_res.count_fbs = 0;
> > > +	card_res.count_connectors = 0;
> > > +	card_res.count_encoders = 0;
> > > +	card_res.crtc_id_ptr = (unsigned long)crtcs;
> > > +
> > > +	ret = drm_mode_getresources(dev, &card_res, file, false);
> > > +	if (ret) {
> > > +		kfree(crtcs);
> > > +		return ret;
> > > +	}
> > > +
> > > +	client->crtcs = crtcs;
> > > +	client->num_crtcs = card_res.count_crtcs;
> > > +	client->min_width = card_res.min_width;
> > > +	client->max_width = card_res.max_width;
> > > +	client->min_height = card_res.min_height;
> > > +	client->max_height = card_res.max_height;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/**
> > > + * drm_client_get_file - Get a DRM file
> > > + * @client: DRM client
> > > + *
> > > + * This function makes sure the client has a &drm_file available. The client
> > > + * doesn't normally need to call this, since all client functions that depends
> > > + * on a DRM file will call it. A matching call to drm_client_put_file() is
> > > + * necessary.
> > > + *
> > > + * The reason for not opening a DRM file when a @client is created is because
> > > + * we have to take a ref on the driver module due to &drm_driver->postclose
> > > + * being called in drm_file_free(). Having a DRM file open for the lifetime of
> > > + * the client instance would block driver module unload.
> > > + *
> > > + * Returns:
> > > + * Zero on success, negative error code on failure.
> > > + */
> > > +int drm_client_get_file(struct drm_client_dev *client)
> > > +{
> > > +	struct drm_device *dev = client->dev;
> > > +	struct drm_file *file;
> > > +	int ret = 0;
> > > +
> > > +	mutex_lock(&client->lock);
> > > +
> > > +	if (client->file_ref_count++) {
> > > +		mutex_unlock(&client->lock);
> > > +		return 0;
> > > +	}
> > > +
> > > +	if (!try_module_get(dev->driver->fops->owner)) {
> > > +		ret = -ENODEV;
> > > +		goto err_unlock;
> > > +	}
> > > +
> > > +	drm_dev_get(dev);
> > > +
> > > +	file = drm_file_alloc(dev->primary);
> > > +	if (IS_ERR(file)) {
> > > +		ret = PTR_ERR(file);
> > > +		goto err_put;
> > > +	}
> > > +
> > > +	if (!client->crtcs) {
> > > +		ret = drm_client_init(client, file);
> > > +		if (ret)
> > > +			goto err_free;
> > > +	}
> > > +
> > > +	mutex_lock(&dev->filelist_mutex);
> > > +	list_add(&file->lhead, &dev->filelist_internal);
> > > +	mutex_unlock(&dev->filelist_mutex);
> > > +
> > > +	client->file = file;
> > > +
> > > +	mutex_unlock(&client->lock);
> > > +
> > > +	return 0;
> > > +
> > > +err_free:
> > > +	drm_file_free(file);
> > > +err_put:
> > > +	drm_dev_put(dev);
> > > +	module_put(dev->driver->fops->owner);
> > > +err_unlock:
> > > +	client->file_ref_count = 0;
> > > +	mutex_unlock(&client->lock);
> > > +
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_get_file);
> > > +
> > > +void drm_client_put_file(struct drm_client_dev *client)
> > > +{
> > > +	struct drm_device *dev = client->dev;
> > > +
> > > +	if (!client)
> > > +		return;
> > > +
> > > +	mutex_lock(&client->lock);
> > > +
> > > +	if (WARN_ON(!client->file_ref_count))
> > > +		goto out_unlock;
> > > +
> > > +	if (--client->file_ref_count)
> > > +		goto out_unlock;
> > > +
> > > +	mutex_lock(&dev->filelist_mutex);
> > > +	list_del(&client->file->lhead);
> > > +	mutex_unlock(&dev->filelist_mutex);
> > > +
> > > +	drm_file_free(client->file);
> > > +	client->file = NULL;
> > > +	drm_dev_put(dev);
> > > +	module_put(dev->driver->fops->owner);
> > > +out_unlock:
> > > +	mutex_unlock(&client->lock);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_put_file);
> > > +
> > > +static struct drm_pending_event *
> > > +drm_client_read_get_pending_event(struct drm_device *dev, struct drm_file *file)
> > > +{
> > > +	struct drm_pending_event *e = NULL;
> > > +	int ret;
> > > +
> > > +	ret = mutex_lock_interruptible(&file->event_read_lock);
> > > +	if (ret)
> > > +		return ERR_PTR(ret);
> > > +
> > > +	spin_lock_irq(&dev->event_lock);
> > > +	if (!list_empty(&file->event_list)) {
> > > +		e = list_first_entry(&file->event_list,
> > > +				     struct drm_pending_event, link);
> > > +		file->event_space += e->event->length;
> > > +		list_del(&e->link);
> > > +	}
> > > +	spin_unlock_irq(&dev->event_lock);
> > > +
> > > +	mutex_unlock(&file->event_read_lock);
> > > +
> > > +	return e;
> > > +}
> > > +
> > > +struct drm_event *
> > > +drm_client_read_event(struct drm_client_dev *client, bool block)
> > > +{
> > > +	struct drm_file *file = client->file;
> > > +	struct drm_device *dev = client->dev;
> > > +	struct drm_pending_event *e;
> > > +	struct drm_event *event;
> > > +	int ret;
> > > +
> > > +	/* Allocate so it fits all events, there's a sanity check later */
> > > +	event = kzalloc(128, GFP_KERNEL);
> > > +	if (!event)
> > > +		return ERR_PTR(-ENOMEM);
> > > +
> > > +	e = drm_client_read_get_pending_event(dev, file);
> > > +	if (IS_ERR(e)) {
> > > +		ret = PTR_ERR(e);
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	if (!e && !block) {
> > > +		ret = 0;
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	ret = wait_event_interruptible_timeout(file->event_wait,
> > > +					       !list_empty(&file->event_list),
> > > +					       5 * HZ);
> > > +	if (!ret)
> > > +		ret = -ETIMEDOUT;
> > > +	if (ret < 0)
> > > +		goto err_free;
> > > +
> > > +	e = drm_client_read_get_pending_event(dev, file);
> > > +	if (IS_ERR_OR_NULL(e)) {
> > > +		ret = PTR_ERR_OR_ZERO(e);
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	if (WARN_ON(e->event->length > 128)) {
> > > +		/* Increase buffer if this happens */
> > > +		ret = -ENOMEM;
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	memcpy(event, e->event, e->event->length);
> > > +	kfree(e);
> > > +
> > > +	return event;
> > > +
> > > +err_free:
> > > +	kfree(event);
> > > +
> > > +	return ret ? ERR_PTR(ret) : NULL;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_read_event);
> > > +
> > > +static void drm_client_connector_free(struct drm_client_connector *connector)
> > > +{
> > > +	if (!connector)
> > > +		return;
> > > +	kfree(connector->modes);
> > > +	kfree(connector);
> > > +}
> > > +
> > > +static struct drm_client_connector *
> > > +drm_client_get_connector(struct drm_client_dev *client, unsigned int id)
> > > +{
> > > +	struct drm_mode_get_connector req = {
> > > +		.connector_id = id,
> > > +	};
> > > +	struct drm_client_connector *connector;
> > > +	struct drm_mode_modeinfo *modes = NULL;
> > > +	struct drm_device *dev = client->dev;
> > > +	struct drm_connector *conn;
> > > +	bool non_desktop;
> > > +	int ret;
> > > +
> > > +	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
> > > +	if (!connector)
> > > +		return ERR_PTR(-ENOMEM);
> > > +
> > > +	ret = drm_mode_getconnector(dev, &req, client->file, false);
> > > +	if (ret)
> > > +		goto err_free;
> > > +
> > > +	connector->conn_id = id;
> > > +	connector->status = req.connection;
> > > +
> > > +	conn = drm_connector_lookup(dev, client->file, id);
> > > +	if (!conn) {
> > > +		ret = -ENOENT;
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	non_desktop = conn->display_info.non_desktop;
> > > +
> > > +	connector->has_tile = conn->has_tile;
> > > +	connector->tile_h_loc = conn->tile_h_loc;
> > > +	connector->tile_v_loc = conn->tile_v_loc;
> > > +	if (conn->tile_group)
> > > +		connector->tile_group = conn->tile_group->id;
> > > +
> > > +	drm_connector_put(conn);
> > > +
> > > +	if (non_desktop) {
> > > +		kfree(connector);
> > > +		return NULL;
> > > +	}
> > > +
> > > +	if (!req.count_modes)
> > > +		return connector;
> > > +
> > > +	modes = kcalloc(req.count_modes, sizeof(*modes), GFP_KERNEL);
> > > +	if (!modes) {
> > > +		ret = -ENOMEM;
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	connector->modes = modes;
> > > +	connector->num_modes = req.count_modes;
> > > +
> > > +	req.count_props = 0;
> > > +	req.count_encoders = 0;
> > > +	req.modes_ptr = (unsigned long)modes;
> > > +
> > > +	ret = drm_mode_getconnector(dev, &req, client->file, false);
> > > +	if (ret)
> > > +		goto err_free;
> > > +
> > > +	return connector;
> > > +
> > > +err_free:
> > > +	kfree(modes);
> > > +	kfree(connector);
> > > +
> > > +	return ERR_PTR(ret);
> > > +}
> > > +
> > > +static int drm_client_get_connectors(struct drm_client_dev *client,
> > > +				     struct drm_client_connector ***connectors)
> > > +{
> > > +	struct drm_mode_card_res card_res = {};
> > > +	struct drm_device *dev = client->dev;
> > > +	int ret, num_connectors;
> > > +	u32 *connector_ids;
> > > +	unsigned int i;
> > > +
> > > +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
> > > +	if (ret)
> > > +		return ret;
> > > +	if (!card_res.count_connectors)
> > > +		return 0;
> > > +
> > > +	num_connectors = card_res.count_connectors;
> > > +	connector_ids = kcalloc(num_connectors,
> > > +				sizeof(*connector_ids), GFP_KERNEL);
> > > +	if (!connector_ids)
> > > +		return -ENOMEM;
> > > +
> > > +	card_res.count_fbs = 0;
> > > +	card_res.count_crtcs = 0;
> > > +	card_res.count_encoders = 0;
> > > +	card_res.connector_id_ptr = (unsigned long)connector_ids;
> > > +
> > > +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
> > > +	if (ret)
> > > +		goto err_free;
> > > +
> > > +	*connectors = kcalloc(num_connectors, sizeof(**connectors), GFP_KERNEL);
> > > +	if (!(*connectors)) {
> > > +		ret = -ENOMEM;
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		struct drm_client_connector *connector;
> > > +
> > > +		connector = drm_client_get_connector(client, connector_ids[i]);
> > > +		if (IS_ERR(connector)) {
> > > +			ret = PTR_ERR(connector);
> > > +			goto err_free;
> > > +		}
> > > +		if (connector)
> > > +			(*connectors)[i] = connector;
> > > +		else
> > > +			num_connectors--;
> > > +	}
> > > +
> > > +	if (!num_connectors) {
> > > +		ret = 0;
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	return num_connectors;
> > > +
> > > +err_free:
> > > +	if (connectors)
> > > +		for (i = 0; i < num_connectors; i++)
> > > +			drm_client_connector_free((*connectors)[i]);
> > > +
> > > +	kfree(connectors);
> > > +	kfree(connector_ids);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static bool
> > > +drm_client_connector_is_enabled(struct drm_client_connector *connector,
> > > +				bool strict)
> > > +{
> > > +	if (strict)
> > > +		return connector->status == connector_status_connected;
> > > +	else
> > > +		return connector->status != connector_status_disconnected;
> > > +}
> > > +
> > > +struct drm_mode_modeinfo *
> > > +drm_client_display_first_mode(struct drm_client_display *display)
> > > +{
> > > +	if (!display->num_modes)
> > > +		return NULL;
> > > +	return display->modes;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_first_mode);
> > > +
> > > +struct drm_mode_modeinfo *
> > > +drm_client_display_next_mode(struct drm_client_display *display,
> > > +			     struct drm_mode_modeinfo *mode)
> > > +{
> > > +	struct drm_mode_modeinfo *modes = display->modes;
> > > +
> > > +	if (++mode < &modes[display->num_modes])
> > > +		return mode;
> > > +
> > > +	return NULL;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_next_mode);
> > > +
> > > +static void
> > > +drm_client_display_fill_tile_modes(struct drm_client_display *display,
> > > +				   struct drm_mode_modeinfo *tile_modes)
> > > +{
> > > +	unsigned int i, j, num_modes = display->connectors[0]->num_modes;
> > > +	struct drm_mode_modeinfo *tile_mode, *conn_mode;
> > > +
> > > +	if (!num_modes) {
> > > +		kfree(tile_modes);
> > > +		kfree(display->modes);
> > > +		display->modes = NULL;
> > > +		display->num_modes = 0;
> > > +		return;
> > > +	}
> > > +
> > > +	for (i = 0; i < num_modes; i++) {
> > > +		tile_mode = &tile_modes[i];
> > > +
> > > +		conn_mode = &display->connectors[0]->modes[i];
> > > +		tile_mode->clock = conn_mode->clock;
> > > +		tile_mode->vscan = conn_mode->vscan;
> > > +		tile_mode->vrefresh = conn_mode->vrefresh;
> > > +		tile_mode->flags = conn_mode->flags;
> > > +		tile_mode->type = conn_mode->type;
> > > +
> > > +		for (j = 0; j < display->num_connectors; j++) {
> > > +			conn_mode = &display->connectors[j]->modes[i];
> > > +
> > > +			if (!display->connectors[j]->tile_h_loc) {
> > > +				tile_mode->hdisplay += conn_mode->hdisplay;
> > > +				tile_mode->hsync_start += conn_mode->hsync_start;
> > > +				tile_mode->hsync_end += conn_mode->hsync_end;
> > > +				tile_mode->htotal += conn_mode->htotal;
> > > +			}
> > > +
> > > +			if (!display->connectors[j]->tile_v_loc) {
> > > +				tile_mode->vdisplay += conn_mode->vdisplay;
> > > +				tile_mode->vsync_start += conn_mode->vsync_start;
> > > +				tile_mode->vsync_end += conn_mode->vsync_end;
> > > +				tile_mode->vtotal += conn_mode->vtotal;
> > > +			}
> > > +		}
> > > +	}
> > > +
> > > +	kfree(display->modes);
> > > +	display->modes = tile_modes;
> > > +	display->num_modes = num_modes;
> > > +}
> > > +
> > > +/**
> > > + * drm_client_display_update_modes - Fetch display modes
> > > + * @display: Client display
> > > + * @mode_changed: Optional pointer to boolen which return whether the modes
> > > + *                have changed or not.
> > > + *
> > > + * This function can be used in the client hotplug callback to check if the
> > > + * video modes have changed and get them up-to-date.
> > > + *
> > > + * Returns:
> > > + * Number of modes on success, negative error code on failure.
> > > + */
> > > +int drm_client_display_update_modes(struct drm_client_display *display,
> > > +				    bool *mode_changed)
> > > +{
> > > +	unsigned int num_connectors = display->num_connectors;
> > > +	struct drm_client_dev *client = display->client;
> > > +	struct drm_mode_modeinfo *display_tile_modes;
> > > +	struct drm_client_connector **connectors;
> > > +	unsigned int i, num_modes = 0;
> > > +	bool dummy_changed = false;
> > > +	int ret;
> > > +
> > > +	if (mode_changed)
> > > +		*mode_changed = false;
> > > +	else
> > > +		mode_changed = &dummy_changed;
> > > +
> > > +	if (display->cloned)
> > > +		return 2;
> > > +
> > > +	ret = drm_client_get_file(client);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
> > > +	if (!connectors) {
> > > +		ret = -ENOMEM;
> > > +		goto out_put_file;
> > > +	}
> > > +
> > > +	/* Get a new set for comparison */
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		connectors[i] = drm_client_get_connector(client, display->connectors[i]->conn_id);
> > > +		if (IS_ERR_OR_NULL(connectors[i])) {
> > > +			ret = PTR_ERR_OR_ZERO(connectors[i]);
> > > +			if (!ret)
> > > +				ret = -ENOENT;
> > > +			goto out_cleanup;
> > > +		}
> > > +	}
> > > +
> > > +	/* All connectors should have the same number of modes */
> > > +	num_modes = connectors[0]->num_modes;
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		if (num_modes != connectors[i]->num_modes) {
> > > +			ret = -EINVAL;
> > > +			goto out_cleanup;
> > > +		}
> > > +	}
> > > +
> > > +	if (num_connectors > 1) {
> > > +		display_tile_modes = kcalloc(num_modes, sizeof(*display_tile_modes), GFP_KERNEL);
> > > +		if (!display_tile_modes) {
> > > +			ret = -ENOMEM;
> > > +			goto out_cleanup;
> > > +		}
> > > +	}
> > > +
> > > +	mutex_lock(&display->modes_lock);
> > > +
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		display->connectors[i]->status = connectors[i]->status;
> > > +		if (display->connectors[i]->num_modes != connectors[i]->num_modes) {
> > > +			display->connectors[i]->num_modes = connectors[i]->num_modes;
> > > +			kfree(display->connectors[i]->modes);
> > > +			display->connectors[i]->modes = connectors[i]->modes;
> > > +			connectors[i]->modes = NULL;
> > > +			*mode_changed = true;
> > > +		}
> > > +	}
> > > +
> > > +	if (num_connectors > 1)
> > > +		drm_client_display_fill_tile_modes(display, display_tile_modes);
> > > +	else
> > > +		display->modes = display->connectors[0]->modes;
> > > +
> > > +	mutex_unlock(&display->modes_lock);
> > > +
> > > +out_cleanup:
> > > +	for (i = 0; i < num_connectors; i++)
> > > +		drm_client_connector_free(connectors[i]);
> > > +	kfree(connectors);
> > > +out_put_file:
> > > +	drm_client_put_file(client);
> > > +
> > > +	return ret ? ret : num_modes;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_update_modes);
> > > +
> > > +void drm_client_display_free(struct drm_client_display *display)
> > > +{
> > > +	unsigned int i;
> > > +
> > > +	if (!display)
> > > +		return;
> > > +
> > > +	/* tile modes? */
> > > +	if (display->modes != display->connectors[0]->modes)
> > > +		kfree(display->modes);
> > > +
> > > +	for (i = 0; i < display->num_connectors; i++)
> > > +		drm_client_connector_free(display->connectors[i]);
> > > +
> > > +	kfree(display->connectors);
> > > +	mutex_destroy(&display->modes_lock);
> > > +	kfree(display);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_free);
> > > +
> > > +static struct drm_client_display *
> > > +drm_client_display_alloc(struct drm_client_dev *client,
> > > +			 unsigned int num_connectors)
> > > +{
> > > +	struct drm_client_display *display;
> > > +	struct drm_client_connector **connectors;
> > > +
> > > +	display = kzalloc(sizeof(*display), GFP_KERNEL);
> > > +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
> > > +	if (!display || !connectors) {
> > > +		kfree(display);
> > > +		kfree(connectors);
> > > +		return NULL;
> > > +	}
> > > +
> > > +	mutex_init(&display->modes_lock);
> > > +	display->client = client;
> > > +	display->connectors = connectors;
> > > +	display->num_connectors = num_connectors;
> > > +
> > > +	return display;
> > > +}
> > > +
> > > +/* Logic is from drm_fb_helper */
> > > +static struct drm_client_display *
> > > +drm_client_connector_pick_cloned(struct drm_client_dev *client,
> > > +				 struct drm_client_connector **connectors,
> > > +				 unsigned int num_connectors)
> > > +{
> > > +	struct drm_mode_modeinfo modes[2], udmt_mode, *mode, *tmp;
> > > +	struct drm_display_mode *dmt_display_mode = NULL;
> > > +	unsigned int i, j, conns[2], num_conns = 0;
> > > +	struct drm_client_connector *connector;
> > > +	struct drm_device *dev = client->dev;
> > > +	struct drm_client_display *display;
> > > +
> > > +	/* only contemplate cloning in the single crtc case */
> > > +	if (dev->mode_config.num_crtc > 1)
> > > +		return NULL;
> > > +retry:
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		connector = connectors[i];
> > > +		if (!connector || connector->has_tile || !connector->num_modes)
> > > +			continue;
> > > +
> > > +		for (j = 0; j < connector->num_modes; j++) {
> > > +			mode = &connector->modes[j];
> > > +			if (dmt_display_mode) {
> > > +				if (drm_umode_equal(&udmt_mode, mode)) {
> > > +					conns[num_conns] = i;
> > > +					modes[num_conns++] = *mode;
> > > +					break;
> > > +				}
> > > +			} else {
> > > +				if (mode->type & DRM_MODE_TYPE_USERDEF) {
> > > +					conns[num_conns] = i;
> > > +					modes[num_conns++] = *mode;
> > > +					break;
> > > +				}
> > > +			}
> > > +		}
> > > +		if (num_conns == 2)
> > > +			break;
> > > +	}
> > > +
> > > +	if (num_conns == 2)
> > > +		goto found;
> > > +
> > > +	if (dmt_display_mode)
> > > +		return NULL;
> > > +
> > > +	dmt_display_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
> > > +	drm_mode_convert_to_umode(&udmt_mode, dmt_display_mode);
> > > +	drm_mode_destroy(dev, dmt_display_mode);
> > > +
> > > +	goto retry;
> > > +
> > > +found:
> > > +	tmp = kcalloc(2, sizeof(*tmp), GFP_KERNEL);
> > > +	if (!tmp)
> > > +		return ERR_PTR(-ENOMEM);
> > > +
> > > +	display = drm_client_display_alloc(client, 2);
> > > +	if (!display) {
> > > +		kfree(tmp);
> > > +		return ERR_PTR(-ENOMEM);
> > > +	}
> > > +
> > > +	for (i = 0; i < 2; i++) {
> > > +		connector = connectors[conns[i]];
> > > +		display->connectors[i] = connector;
> > > +		connectors[conns[i]] = NULL;
> > > +		kfree(connector->modes);
> > > +		tmp[i] = modes[i];
> > > +		connector->modes = &tmp[i];
> > > +		connector->num_modes = 1;
> > > +	}
> > > +
> > > +	display->cloned = true;
> > > +	display->modes = &tmp[0];
> > > +	display->num_modes = 1;
> > > +
> > > +	return display;
> > > +}
> > > +
> > > +static struct drm_client_display *
> > > +drm_client_connector_pick_tile(struct drm_client_dev *client,
> > > +			       struct drm_client_connector **connectors,
> > > +			       unsigned int num_connectors)
> > > +{
> > > +	unsigned int i, num_conns, num_modes, tile_group = 0;
> > > +	struct drm_mode_modeinfo *tile_modes = NULL;
> > > +	struct drm_client_connector *connector;
> > > +	struct drm_client_display *display;
> > > +	u16 conns[32];
> > > +
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		connector = connectors[i];
> > > +		if (!connector || !connector->tile_group)
> > > +			continue;
> > > +
> > > +		if (!tile_group) {
> > > +			tile_group = connector->tile_group;
> > > +			num_modes = connector->num_modes;
> > > +		}
> > > +
> > > +		if (connector->tile_group != tile_group)
> > > +			continue;
> > > +
> > > +		if (num_modes != connector->num_modes) {
> > > +			DRM_ERROR("Tile connectors must have the same number of modes\n");
> > > +			return ERR_PTR(-EINVAL);
> > > +		}
> > > +
> > > +		conns[num_conns++] = i;
> > > +		if (WARN_ON(num_conns == 33))
> > > +			return ERR_PTR(-EINVAL);
> > > +	}
> > > +
> > > +	if (!num_conns)
> > > +		return NULL;
> > > +
> > > +	if (num_modes) {
> > > +		tile_modes = kcalloc(num_modes, sizeof(*tile_modes), GFP_KERNEL);
> > > +		if (!tile_modes)
> > > +			return ERR_PTR(-ENOMEM);
> > > +	}
> > > +
> > > +	display = drm_client_display_alloc(client, num_conns);
> > > +	if (!display) {
> > > +		kfree(tile_modes);
> > > +		return ERR_PTR(-ENOMEM);
> > > +	}
> > > +
> > > +	if (num_modes)
> > > +		drm_client_display_fill_tile_modes(display, tile_modes);
> > > +
> > > +	return display;
> > > +}
> > > +
> > > +static struct drm_client_display *
> > > +drm_client_connector_pick_not_tile(struct drm_client_dev *client,
> > > +				   struct drm_client_connector **connectors,
> > > +				   unsigned int num_connectors)
> > > +{
> > > +	struct drm_client_display *display;
> > > +	unsigned int i;
> > > +
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		if (!connectors[i] || connectors[i]->has_tile)
> > > +			continue;
> > > +		break;
> > > +	}
> > > +
> > > +	if (i == num_connectors)
> > > +		return NULL;
> > > +
> > > +	display = drm_client_display_alloc(client, 1);
> > > +	if (!display)
> > > +		return ERR_PTR(-ENOMEM);
> > > +
> > > +	display->connectors[0] = connectors[i];
> > > +	connectors[i] = NULL;
> > > +	display->modes = display->connectors[0]->modes;
> > > +	display->num_modes = display->connectors[0]->num_modes;
> > > +
> > > +	return display;
> > > +}
> > > +
> > > +/* Get connectors and bundle them up into displays */
> > > +static int drm_client_get_displays(struct drm_client_dev *client,
> > > +				   struct drm_client_display ***displays)
> > > +{
> > > +	int ret, num_connectors, num_displays = 0;
> > > +	struct drm_client_connector **connectors;
> > > +	struct drm_client_display *display;
> > > +	unsigned int i;
> > > +
> > > +	ret = drm_client_get_file(client);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	num_connectors = drm_client_get_connectors(client, &connectors);
> > > +	if (num_connectors <= 0) {
> > > +		ret = num_connectors;
> > > +		goto err_put_file;
> > > +	}
> > > +
> > > +	*displays = kcalloc(num_connectors, sizeof(*displays), GFP_KERNEL);
> > > +	if (!(*displays)) {
> > > +		ret = -ENOMEM;
> > > +		goto err_free;
> > > +	}
> > > +
> > > +	display = drm_client_connector_pick_cloned(client, connectors,
> > > +						   num_connectors);
> > > +	if (IS_ERR(display)) {
> > > +		ret = PTR_ERR(display);
> > > +		goto err_free;
> > > +	}
> > > +	if (display)
> > > +		(*displays)[num_displays++] = display;
> > > +
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		display = drm_client_connector_pick_tile(client, connectors,
> > > +							 num_connectors);
> > > +		if (IS_ERR(display)) {
> > > +			ret = PTR_ERR(display);
> > > +			goto err_free;
> > > +		}
> > > +		if (!display)
> > > +			break;
> > > +		(*displays)[num_displays++] = display;
> > > +	}
> > > +
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		display = drm_client_connector_pick_not_tile(client, connectors,
> > > +							     num_connectors);
> > > +		if (IS_ERR(display)) {
> > > +			ret = PTR_ERR(display);
> > > +			goto err_free;
> > > +		}
> > > +		if (!display)
> > > +			break;
> > > +		(*displays)[num_displays++] = display;
> > > +	}
> > > +
> > > +	for (i = 0; i < num_connectors; i++) {
> > > +		if (connectors[i]) {
> > > +			DRM_INFO("Connector %u fell through the cracks.\n",
> > > +				 connectors[i]->conn_id);
> > > +			drm_client_connector_free(connectors[i]);
> > > +		}
> > > +	}
> > > +
> > > +	drm_client_put_file(client);
> > > +	kfree(connectors);
> > > +
> > > +	return num_displays;
> > > +
> > > +err_free:
> > > +	for (i = 0; i < num_displays; i++)
> > > +		drm_client_display_free((*displays)[i]);
> > > +	kfree(*displays);
> > > +	*displays = NULL;
> > > +	for (i = 0; i < num_connectors; i++)
> > > +		drm_client_connector_free(connectors[i]);
> > > +	kfree(connectors);
> > > +err_put_file:
> > > +	drm_client_put_file(client);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static bool
> > > +drm_client_display_is_enabled(struct drm_client_display *display, bool strict)
> > > +{
> > > +	unsigned int i;
> > > +
> > > +	if (!display->num_modes)
> > > +		return false;
> > > +
> > > +	for (i = 0; i < display->num_connectors; i++)
> > > +		if (!drm_client_connector_is_enabled(display->connectors[i], strict))
> > > +			return false;
> > > +
> > > +	return true;
> > > +}
> > > +
> > > +/**
> > > + * drm_client_display_get_first_enabled - Get first enabled display
> > > + * @client: DRM client
> > > + * @strict: If true the connector(s) have to be connected, if false they can
> > > + *          also have unknown status.
> > > + *
> > > + * This function gets all connectors and bundles them into displays
> > > + * (tiled/cloned). It then picks the first one with connectors that is enabled
> > > + * according to @strict.
> > > + *
> > > + * Returns:
> > > + * Pointer to a client display if such a display was found, NULL if not found
> > > + * or an error pointer on failure.
> > > + */
> > > +struct drm_client_display *
> > > +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict)
> > > +{
> > > +	struct drm_client_display **displays, *display = NULL;
> > > +	int num_displays;
> > > +	unsigned int i;
> > > +
> > > +	num_displays = drm_client_get_displays(client, &displays);
> > > +	if (num_displays < 0)
> > > +		return ERR_PTR(num_displays);
> > > +	if (!num_displays)
> > > +		return NULL;
> > > +
> > > +	for (i = 0; i < num_displays; i++) {
> > > +		if (!display &&
> > > +		    drm_client_display_is_enabled(displays[i], strict)) {
> > > +			display = displays[i];
> > > +			continue;
> > > +		}
> > > +		drm_client_display_free(displays[i]);
> > > +	}
> > > +
> > > +	kfree(displays);
> > > +
> > > +	return display;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_get_first_enabled);
> > > +
> > > +unsigned int
> > > +drm_client_display_preferred_depth(struct drm_client_display *display)
> > > +{
> > > +	struct drm_connector *conn;
> > > +	unsigned int ret;
> > > +
> > > +	conn = drm_connector_lookup(display->client->dev, NULL,
> > > +				    display->connectors[0]->conn_id);
> > > +	if (!conn)
> > > +		return 0;
> > > +
> > > +	if (conn->cmdline_mode.bpp_specified)
> > > +		ret = conn->cmdline_mode.bpp;
> > > +	else
> > > +		ret = display->client->dev->mode_config.preferred_depth;
> > > +
> > > +	drm_connector_put(conn);
> > > +
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_preferred_depth);
> > > +
> > > +int drm_client_display_dpms(struct drm_client_display *display, int mode)
> > > +{
> > > +	struct drm_mode_obj_set_property prop;
> > > +
> > > +	prop.value = mode;
> > > +	prop.prop_id = display->client->dev->mode_config.dpms_property->base.id;
> > > +	prop.obj_id = display->connectors[0]->conn_id;
> > > +	prop.obj_type = DRM_MODE_OBJECT_CONNECTOR;
> > > +
> > > +	return drm_mode_obj_set_property(display->client->dev, &prop,
> > > +					 display->client->file);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_dpms);
> > > +
> > > +int drm_client_display_wait_vblank(struct drm_client_display *display)
> > > +{
> > > +	struct drm_crtc *crtc;
> > > +	union drm_wait_vblank vblank_req = {
> > > +		.request = {
> > > +			.type = _DRM_VBLANK_RELATIVE,
> > > +			.sequence = 1,
> > > +		},
> > > +	};
> > > +
> > > +	crtc = drm_crtc_find(display->client->dev, display->client->file,
> > > +			     display->connectors[0]->crtc_id);
> > > +	if (!crtc)
> > > +		return -ENOENT;
> > > +
> > > +	vblank_req.request.type |= drm_crtc_index(crtc) << _DRM_VBLANK_HIGH_CRTC_SHIFT;
> > > +
> > > +	return drm_wait_vblank(display->client->dev, &vblank_req,
> > > +			       display->client->file);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_wait_vblank);
> > > +
> > > +static int drm_client_get_crtc_index(struct drm_client_dev *client, u32 id)
> > > +{
> > > +	int i;
> > > +
> > > +	for (i = 0; i < client->num_crtcs; i++)
> > > +		if (client->crtcs[i] == id)
> > > +			return i;
> > > +
> > > +	return -ENOENT;
> > > +}
> > > +
> > > +static int drm_client_display_find_crtcs(struct drm_client_display *display)
> > > +{
> > > +	struct drm_client_dev *client = display->client;
> > > +	struct drm_device *dev = client->dev;
> > > +	struct drm_file *file = client->file;
> > > +	u32 encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
> > > +	unsigned int i, j, available_crtcs = ~0;
> > > +	struct drm_mode_get_connector conn_req;
> > > +	struct drm_mode_get_encoder enc_req;
> > > +	int ret;
> > > +
> > > +	/* Already done? */
> > > +	if (display->connectors[0]->crtc_id)
> > > +		return 0;
> > > +
> > > +	for (i = 0; i < display->num_connectors; i++) {
> > > +		u32 active_crtcs = 0, crtcs_for_connector = 0;
> > > +		int crtc_idx;
> > > +
> > > +		memset(&conn_req, 0, sizeof(conn_req));
> > > +		conn_req.connector_id = display->connectors[i]->conn_id;
> > > +		conn_req.encoders_ptr = (unsigned long)(encoder_ids);
> > > +		conn_req.count_encoders = DRM_CONNECTOR_MAX_ENCODER;
> > > +		ret = drm_mode_getconnector(dev, &conn_req, file, false);
> > > +		if (ret)
> > > +			return ret;
> > > +
> > > +		if (conn_req.encoder_id) {
> > > +			memset(&enc_req, 0, sizeof(enc_req));
> > > +			enc_req.encoder_id = conn_req.encoder_id;
> > > +			ret = drm_mode_getencoder(dev, &enc_req, file);
> > > +			if (ret)
> > > +				return ret;
> > > +			crtcs_for_connector |= enc_req.possible_crtcs;
> > > +			if (crtcs_for_connector & available_crtcs)
> > > +				goto found;
> > > +		}
> > > +
> > > +		for (j = 0; j < conn_req.count_encoders; j++) {
> > > +			memset(&enc_req, 0, sizeof(enc_req));
> > > +			enc_req.encoder_id = encoder_ids[j];
> > > +			ret = drm_mode_getencoder(dev, &enc_req, file);
> > > +			if (ret)
> > > +				return ret;
> > > +
> > > +			crtcs_for_connector |= enc_req.possible_crtcs;
> > > +
> > > +			if (enc_req.crtc_id) {
> > > +				crtc_idx = drm_client_get_crtc_index(client, enc_req.crtc_id);
> > > +				if (crtc_idx >= 0)
> > > +					active_crtcs |= 1 << crtc_idx;
> > > +			}
> > > +		}
> > > +
> > > +found:
> > > +		crtcs_for_connector &= available_crtcs;
> > > +		active_crtcs &= available_crtcs;
> > > +
> > > +		if (!crtcs_for_connector)
> > > +			return -ENOENT;
> > > +
> > > +		if (active_crtcs)
> > > +			crtc_idx = ffs(active_crtcs) - 1;
> > > +		else
> > > +			crtc_idx = ffs(crtcs_for_connector) - 1;
> > > +
> > > +		if (crtc_idx >= client->num_crtcs)
> > > +			return -EINVAL;
> > > +
> > > +		display->connectors[i]->crtc_id = client->crtcs[crtc_idx];
> > > +		available_crtcs &= ~BIT(crtc_idx);
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/**
> > > + * drm_client_display_commit_mode - Commit a mode to the crtc(s)
> > > + * @display: Client display
> > > + * @fb_id: Framebuffer id
> > > + * @mode: Video mode
> > > + *
> > > + * Returns:
> > > + * Zero on success, negative error code on failure.
> > > + */
> > > +int drm_client_display_commit_mode(struct drm_client_display *display,
> > > +				   u32 fb_id, struct drm_mode_modeinfo *mode)
> > > +{
> > > +	struct drm_client_dev *client = display->client;
> > > +	struct drm_device *dev = client->dev;
> > > +	unsigned int num_crtcs = client->num_crtcs;
> > > +	struct drm_file *file = client->file;
> > > +	unsigned int *xoffsets = NULL, *yoffsets = NULL;
> > > +	struct drm_mode_crtc *crtc_reqs, *req;
> > > +	u32 cloned_conn_ids[2];
> > > +	unsigned int i;
> > > +	int idx, ret;
> > > +
> > > +	ret = drm_client_display_find_crtcs(display);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	crtc_reqs = kcalloc(num_crtcs, sizeof(*crtc_reqs), GFP_KERNEL);
> > > +	if (!crtc_reqs)
> > > +		return -ENOMEM;
> > > +
> > > +	for (i = 0; i < num_crtcs; i++)
> > > +		crtc_reqs[i].crtc_id = client->crtcs[i];
> > > +
> > > +	if (drm_client_display_is_tiled(display)) {
> > > +		/* TODO calculate tile crtc offsets */
> > > +	}
> > > +
> > > +	for (i = 0; i < display->num_connectors; i++) {
> > > +		idx = drm_client_get_crtc_index(client, display->connectors[i]->crtc_id);
> > > +		if (idx < 0)
> > > +			return -ENOENT;
> > > +
> > > +		req = &crtc_reqs[idx];
> > > +
> > > +		req->fb_id = fb_id;
> > > +		if (xoffsets) {
> > > +			req->x = xoffsets[i];
> > > +			req->y = yoffsets[i];
> > > +		}
> > > +		req->mode_valid = 1;
> > > +		req->mode = *mode;
> > > +
> > > +		if (display->cloned) {
> > > +			cloned_conn_ids[0] = display->connectors[0]->conn_id;
> > > +			cloned_conn_ids[1] = display->connectors[1]->conn_id;
> > > +			req->set_connectors_ptr = (unsigned long)(cloned_conn_ids);
> > > +			req->count_connectors = 2;
> > > +			break;
> > > +		}
> > > +
> > > +		req->set_connectors_ptr = (unsigned long)(&display->connectors[i]->conn_id);
> > > +		req->count_connectors = 1;
> > > +	}
> > > +
> > > +	for (i = 0; i < num_crtcs; i++) {
> > > +		ret = drm_mode_setcrtc(dev, &crtc_reqs[i], file, false);
> > > +		if (ret)
> > > +			break;
> > > +	}
> > > +
> > > +	kfree(xoffsets);
> > > +	kfree(yoffsets);
> > > +	kfree(crtc_reqs);
> > > +
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_commit_mode);
> > > +
> > > +unsigned int drm_client_display_current_fb(struct drm_client_display *display)
> > > +{
> > > +	struct drm_client_dev *client = display->client;
> > > +	int ret;
> > > +	struct drm_mode_crtc crtc_req = {
> > > +		.crtc_id = display->connectors[0]->crtc_id,
> > > +	};
> > > +
> > > +	ret = drm_mode_getcrtc(client->dev, &crtc_req, client->file);
> > > +	if (ret)
> > > +		return 0;
> > > +
> > > +	return crtc_req.fb_id;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_current_fb);
> > > +
> > > +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
> > > +			     struct drm_clip_rect *clips, unsigned int num_clips)
> > > +{
> > > +	struct drm_client_dev *client = display->client;
> > > +	struct drm_mode_fb_dirty_cmd dirty_req = {
> > > +		.fb_id = fb_id,
> > > +		.clips_ptr = (unsigned long)clips,
> > > +		.num_clips = num_clips,
> > > +	};
> > > +	int ret;
> > > +
> > > +	if (display->no_flushing)
> > > +		return 0;
> > > +
> > > +	ret = drm_mode_dirtyfb(client->dev, &dirty_req, client->file, false);
> > > +	if (ret == -ENOSYS) {
> > > +		ret = 0;
> > > +		display->no_flushing = true;
> > > +	}
> > > +
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_flush);
> > > +
> > > +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
> > > +				 bool event)
> > > +{
> > > +	struct drm_client_dev *client = display->client;
> > > +	struct drm_mode_crtc_page_flip_target page_flip_req = {
> > > +		.crtc_id = display->connectors[0]->crtc_id,
> > > +		.fb_id = fb_id,
> > > +	};
> > > +
> > > +	if (event)
> > > +		page_flip_req.flags = DRM_MODE_PAGE_FLIP_EVENT;
> > > +
> > > +	return drm_mode_page_flip(client->dev, &page_flip_req, client->file);
> > > +	/*
> > > +	 * TODO:
> > > +	 * Where do we flush on page flip? Should the driver handle that?
> > > +	 */
> > > +}
> > > +EXPORT_SYMBOL(drm_client_display_page_flip);
> > > +
> > > +/**
> > > + * drm_client_framebuffer_create - Create a client framebuffer
> > > + * @client: DRM client
> > > + * @mode: Display mode to create a buffer for
> > > + * @format: Buffer format
> > > + *
> > > + * This function creates a &drm_client_buffer which consists of a
> > > + * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf
> > > + * exported to aquire a virtual address which is stored in
> > > + * &drm_client_buffer->vaddr.
> > > + * Call drm_client_framebuffer_delete() to free the buffer.
> > > + *
> > > + * Returns:
> > > + * Pointer to a client buffer or an error pointer on failure.
> > > + */
> > > +struct drm_client_buffer *
> > > +drm_client_framebuffer_create(struct drm_client_dev *client,
> > > +			      struct drm_mode_modeinfo *mode, u32 format)
> > > +{
> > > +	struct drm_client_buffer *buffer;
> > > +	int ret;
> > > +
> > > +	buffer = drm_client_buffer_create(client, mode->hdisplay,
> > > +					  mode->vdisplay, format);
> > > +	if (IS_ERR(buffer))
> > > +		return buffer;
> > > +
> > > +	ret = drm_client_buffer_addfb(buffer, mode);
> > > +	if (ret) {
> > > +		drm_client_buffer_delete(buffer);
> > > +		return ERR_PTR(ret);
> > > +	}
> > > +
> > > +	return buffer;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_framebuffer_create);
> > > +
> > > +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
> > > +{
> > > +	drm_client_buffer_rmfb(buffer);
> > > +	drm_client_buffer_delete(buffer);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_framebuffer_delete);
> > > +
> > > +struct drm_client_buffer *
> > > +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
> > > +			 u32 format)
> > > +{
> > > +	struct drm_mode_create_dumb dumb_args = { 0 };
> > > +	struct drm_prime_handle prime_args = { 0 };
> > > +	struct drm_client_buffer *buffer;
> > > +	struct dma_buf *dma_buf;
> > > +	void *vaddr;
> > > +	int ret;
> > > +
> > > +	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
> > > +	if (!buffer)
> > > +		return ERR_PTR(-ENOMEM);
> > > +
> > > +	ret = drm_client_get_file(client);
> > > +	if (ret)
> > > +		goto err_free;
> > > +
> > > +	buffer->client = client;
> > > +	buffer->width = width;
> > > +	buffer->height = height;
> > > +	buffer->format = format;
> > > +
> > > +	dumb_args.width = buffer->width;
> > > +	dumb_args.height = buffer->height;
> > > +	dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
> > > +	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
> > > +	if (ret)
> > > +		goto err_put_file;
> > > +
> > > +	buffer->handle = dumb_args.handle;
> > > +	buffer->pitch = dumb_args.pitch;
> > > +	buffer->size = dumb_args.size;
> > > +
> > > +	prime_args.handle = dumb_args.handle;
> > > +	ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file);
> > > +	if (ret)
> > > +		goto err_delete;
> > > +
> > > +	dma_buf = dma_buf_get(prime_args.fd);
> > > +	if (IS_ERR(dma_buf)) {
> > > +		ret = PTR_ERR(dma_buf);
> > > +		goto err_delete;
> > > +	}
> > > +
> > > +	buffer->dma_buf = dma_buf;
> > > +
> > > +	vaddr = dma_buf_vmap(dma_buf);
> > > +	if (!vaddr) {
> > > +		ret = -ENOMEM;
> > > +		goto err_delete;
> > > +	}
> > > +
> > > +	buffer->vaddr = vaddr;
> > > +
> > > +	return buffer;
> > > +
> > > +err_delete:
> > > +	drm_client_buffer_delete(buffer);
> > > +err_put_file:
> > > +	drm_client_put_file(client);
> > > +err_free:
> > > +	kfree(buffer);
> > > +
> > > +	return ERR_PTR(ret);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_buffer_create);
> > > +
> > > +void drm_client_buffer_delete(struct drm_client_buffer *buffer)
> > > +{
> > > +	if (!buffer)
> > > +		return;
> > > +
> > > +	if (buffer->vaddr)
> > > +		dma_buf_vunmap(buffer->dma_buf, buffer->vaddr);
> > > +
> > > +	if (buffer->dma_buf)
> > > +		dma_buf_put(buffer->dma_buf);
> > > +
> > > +	drm_mode_destroy_dumb(buffer->client->dev, buffer->handle,
> > > +			      buffer->client->file);
> > > +	drm_client_put_file(buffer->client);
> > > +	kfree(buffer);
> > > +}
> > > +EXPORT_SYMBOL(drm_client_buffer_delete);
> > > +
> > > +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
> > > +			    struct drm_mode_modeinfo *mode)
> > > +{
> > > +	struct drm_client_dev *client = buffer->client;
> > > +	struct drm_mode_fb_cmd2 fb_req = { };
> > > +	unsigned int num_fbs, *fb_ids;
> > > +	int i, ret;
> > > +
> > > +	if (buffer->num_fbs)
> > > +		return -EINVAL;
> > > +
> > > +	if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
> > > +		return -EINVAL;
> > > +
> > > +	num_fbs = buffer->height / mode->vdisplay;
> > > +	fb_ids = kcalloc(num_fbs, sizeof(*fb_ids), GFP_KERNEL);
> > > +	if (!fb_ids)
> > > +		return -ENOMEM;
> > > +
> > > +	fb_req.width = mode->hdisplay;
> > > +	fb_req.height = mode->vdisplay;
> > > +	fb_req.pixel_format = buffer->format;
> > > +	fb_req.handles[0] = buffer->handle;
> > > +	fb_req.pitches[0] = buffer->pitch;
> > > +
> > > +	for (i = 0; i < num_fbs; i++) {
> > > +		fb_req.offsets[0] = i * mode->vdisplay * buffer->pitch;
> > > +		ret = drm_mode_addfb2(client->dev, &fb_req, client->file,
> > > +				      client->funcs->name);
> > > +		if (ret)
> > > +			goto err_remove;
> > > +		fb_ids[i] = fb_req.fb_id;
> > > +	}
> > > +
> > > +	buffer->fb_ids = fb_ids;
> > > +	buffer->num_fbs = num_fbs;
> > > +
> > > +	return 0;
> > > +
> > > +err_remove:
> > > +	for (i--; i >= 0; i--)
> > > +		drm_mode_rmfb(client->dev, fb_ids[i], client->file);
> > > +	kfree(fb_ids);
> > > +
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_buffer_addfb);
> > > +
> > > +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
> > > +{
> > > +	unsigned int i;
> > > +	int ret;
> > > +
> > > +	if (!buffer || !buffer->num_fbs)
> > > +		return 0;
> > > +
> > > +	for (i = 0; i < buffer->num_fbs; i++) {
> > > +		ret = drm_mode_rmfb(buffer->client->dev, buffer->fb_ids[i],
> > > +				    buffer->client->file);
> > > +		if (ret)
> > > +			DRM_DEV_ERROR(buffer->client->dev->dev,
> > > +				      "Error removing FB:%u (%d)\n",
> > > +				      buffer->fb_ids[i], ret);
> > > +	}
> > > +
> > > +	kfree(buffer->fb_ids);
> > > +	buffer->fb_ids = NULL;
> > > +	buffer->num_fbs = 0;
> > > +
> > > +	return 0;
> > > +}
> > > +EXPORT_SYMBOL(drm_client_buffer_rmfb);
> > > diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
> > > index f869de185986..db161337d87c 100644
> > > --- a/drivers/gpu/drm/drm_drv.c
> > > +++ b/drivers/gpu/drm/drm_drv.c
> > > @@ -33,6 +33,7 @@
> > >   #include <linux/mount.h>
> > >   #include <linux/slab.h>
> > > +#include <drm/drm_client.h>
> > >   #include <drm/drm_drv.h>
> > >   #include <drm/drmP.h>
> > > @@ -463,6 +464,7 @@ int drm_dev_init(struct drm_device *dev,
> > >   	dev->driver = driver;
> > >   	INIT_LIST_HEAD(&dev->filelist);
> > > +	INIT_LIST_HEAD(&dev->filelist_internal);
> > >   	INIT_LIST_HEAD(&dev->ctxlist);
> > >   	INIT_LIST_HEAD(&dev->vmalist);
> > >   	INIT_LIST_HEAD(&dev->maplist);
> > > @@ -787,6 +789,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
> > >   		 dev->dev ? dev_name(dev->dev) : "virtual device",
> > >   		 dev->primary->index);
> > > +	drm_client_dev_register(dev);
> > > +
> > >   	goto out_unlock;
> > >   err_minors:
> > > @@ -839,6 +843,8 @@ void drm_dev_unregister(struct drm_device *dev)
> > >   	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
> > >   	drm_minor_unregister(dev, DRM_MINOR_RENDER);
> > >   	drm_minor_unregister(dev, DRM_MINOR_CONTROL);
> > > +
> > > +	drm_client_dev_unregister(dev);
> > >   }
> > >   EXPORT_SYMBOL(drm_dev_unregister);
> > > diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
> > > index 55505378df47..bcc688e58776 100644
> > > --- a/drivers/gpu/drm/drm_file.c
> > > +++ b/drivers/gpu/drm/drm_file.c
> > > @@ -35,6 +35,7 @@
> > >   #include <linux/slab.h>
> > >   #include <linux/module.h>
> > > +#include <drm/drm_client.h>
> > >   #include <drm/drm_file.h>
> > >   #include <drm/drmP.h>
> > > @@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
> > >   	if (drm_core_check_feature(dev, DRIVER_LEGACY))
> > >   		drm_legacy_dev_reinit(dev);
> > > +
> > > +	drm_client_dev_lastclose(dev);
> > >   }
> > >   /**
> > > diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c
> > > index 2d1643bdae78..5d2a6c6717f5 100644
> > > --- a/drivers/gpu/drm/drm_probe_helper.c
> > > +++ b/drivers/gpu/drm/drm_probe_helper.c
> > > @@ -33,6 +33,7 @@
> > >   #include <linux/moduleparam.h>
> > >   #include <drm/drmP.h>
> > > +#include <drm/drm_client.h>
> > >   #include <drm/drm_crtc.h>
> > >   #include <drm/drm_fourcc.h>
> > >   #include <drm/drm_crtc_helper.h>
> > > @@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
> > >   	drm_sysfs_hotplug_event(dev);
> > >   	if (dev->mode_config.funcs->output_poll_changed)
> > >   		dev->mode_config.funcs->output_poll_changed(dev);
> > > +
> > > +	drm_client_dev_hotplug(dev);
> > >   }
> > >   EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
> > > diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> > > new file mode 100644
> > > index 000000000000..88f6f87919c5
> > > --- /dev/null
> > > +++ b/include/drm/drm_client.h
> > > @@ -0,0 +1,192 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +
> > > +#include <linux/mutex.h>
> > > +
> > > +struct dma_buf;
> > > +struct drm_clip_rect;
> > > +struct drm_device;
> > > +struct drm_file;
> > > +struct drm_mode_modeinfo;
> > > +
> > > +struct drm_client_dev;
> > > +
> > > +/**
> > > + * struct drm_client_funcs - DRM client  callbacks
> > > + */
> > > +struct drm_client_funcs {
> > > +	/**
> > > +	 * @name:
> > > +	 *
> > > +	 * Name of the client.
> > > +	 */
> > > +	const char *name;
> > > +
> > > +	/**
> > > +	 * @new:
> > > +	 *
> > > +	 * Called when a client or a &drm_device is registered.
> > > +	 * If the callback returns anything but zero, then this client instance
> > > +	 * is dropped.
> > > +	 *
> > > +	 * This callback is mandatory.
> > > +	 */
> > > +	int (*new)(struct drm_client_dev *client);
> > > +
> > > +	/**
> > > +	 * @remove:
> > > +	 *
> > > +	 * Called when a &drm_device is unregistered or the client is
> > > +	 * unregistered. If zero is returned drm_client_free() is called
> > > +	 * automatically. If the client can't drop it's resources it should
> > > +	 * return non-zero and call drm_client_free() later.
> > > +	 *
> > > +	 * This callback is optional.
> > > +	 */
> > > +	int (*remove)(struct drm_client_dev *client);
> > > +
> > > +	/**
> > > +	 * @lastclose:
> > > +	 *
> > > +	 * Called on drm_lastclose(). The first client instance in the list
> > > +	 * that returns zero gets the privilege to restore and no more clients
> > > +	 * are called.
> > > +	 *
> > > +	 * This callback is optional.
> > > +	 */
> > > +	int (*lastclose)(struct drm_client_dev *client);
> > > +
> > > +	/**
> > > +	 * @hotplug:
> > > +	 *
> > > +	 * Called on drm_kms_helper_hotplug_event().
> > > +	 *
> > > +	 * This callback is optional.
> > > +	 */
> > > +	int (*hotplug)(struct drm_client_dev *client);
> > > +
> > > +// TODO
> > > +//	void (*suspend)(struct drm_client_dev *client);
> > > +//	void (*resume)(struct drm_client_dev *client);
> > > +};
> > > +
> > > +/**
> > > + * struct drm_client_dev - DRM client instance
> > > + */
> > > +struct drm_client_dev {
> > > +	struct list_head list;
> > > +	struct drm_device *dev;
> > > +	const struct drm_client_funcs *funcs;
> > > +	struct mutex lock;
> > > +	struct drm_file *file;
> > > +	unsigned int file_ref_count;
> > > +	u32 *crtcs;
> > > +	unsigned int num_crtcs;
> > > +	u32 min_width;
> > > +	u32 max_width;
> > > +	u32 min_height;
> > > +	u32 max_height;
> > > +	void *private;
> > > +};
> > > +
> > > +void drm_client_free(struct drm_client_dev *client);
> > > +int drm_client_register(const struct drm_client_funcs *funcs);
> > > +void drm_client_unregister(const struct drm_client_funcs *funcs);
> > > +
> > > +void drm_client_dev_register(struct drm_device *dev);
> > > +void drm_client_dev_unregister(struct drm_device *dev);
> > > +void drm_client_dev_hotplug(struct drm_device *dev);
> > > +void drm_client_dev_lastclose(struct drm_device *dev);
> > > +
> > > +int drm_client_get_file(struct drm_client_dev *client);
> > > +void drm_client_put_file(struct drm_client_dev *client);
> > > +struct drm_event *
> > > +drm_client_read_event(struct drm_client_dev *client, bool block);
> > > +
> > > +struct drm_client_connector {
> > > +	unsigned int conn_id;
> > > +	unsigned int status;
> > > +	unsigned int crtc_id;
> > > +	struct drm_mode_modeinfo *modes;
> > > +	unsigned int num_modes;
> > > +	bool has_tile;
> > > +	int tile_group;
> > > +	u8 tile_h_loc, tile_v_loc;
> > > +};
> > > +
> > > +struct drm_client_display {
> > > +	struct drm_client_dev *client;
> > > +
> > > +	struct drm_client_connector **connectors;
> > > +	unsigned int num_connectors;
> > > +
> > > +	struct mutex modes_lock;
> > > +	struct drm_mode_modeinfo *modes;
> > > +	unsigned int num_modes;
> > > +
> > > +	bool cloned;
> > > +	bool no_flushing;
> > > +};
> > > +
> > > +void drm_client_display_free(struct drm_client_display *display);
> > > +struct drm_client_display *
> > > +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict);
> > > +
> > > +int drm_client_display_update_modes(struct drm_client_display *display,
> > > +				    bool *mode_changed);
> > > +
> > > +static inline bool
> > > +drm_client_display_is_tiled(struct drm_client_display *display)
> > > +{
> > > +	return !display->cloned && display->num_connectors > 1;
> > > +}
> > > +
> > > +int drm_client_display_dpms(struct drm_client_display *display, int mode);
> > > +int drm_client_display_wait_vblank(struct drm_client_display *display);
> > > +
> > > +struct drm_mode_modeinfo *
> > > +drm_client_display_first_mode(struct drm_client_display *display);
> > > +struct drm_mode_modeinfo *
> > > +drm_client_display_next_mode(struct drm_client_display *display,
> > > +			     struct drm_mode_modeinfo *mode);
> > > +
> > > +#define drm_client_display_for_each_mode(display, mode) \
> > > +	for (mode = drm_client_display_first_mode(display); mode; \
> > > +	     mode = drm_client_display_next_mode(display, mode))
> > > +
> > > +unsigned int
> > > +drm_client_display_preferred_depth(struct drm_client_display *display);
> > > +
> > > +int drm_client_display_commit_mode(struct drm_client_display *display,
> > > +				   u32 fb_id, struct drm_mode_modeinfo *mode);
> > > +unsigned int drm_client_display_current_fb(struct drm_client_display *display);
> > > +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
> > > +			     struct drm_clip_rect *clips, unsigned int num_clips);
> > > +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
> > > +				 bool event);
> > > +
> > > +struct drm_client_buffer {
> > > +	struct drm_client_dev *client;
> > > +	u32 width;
> > > +	u32 height;
> > > +	u32 format;
> > > +	u32 handle;
> > > +	u32 pitch;
> > > +	u64 size;
> > > +	struct dma_buf *dma_buf;
> > > +	void *vaddr;
> > > +
> > > +	unsigned int *fb_ids;
> > > +	unsigned int num_fbs;
> > > +};
> > > +
> > > +struct drm_client_buffer *
> > > +drm_client_framebuffer_create(struct drm_client_dev *client,
> > > +			      struct drm_mode_modeinfo *mode, u32 format);
> > > +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
> > > +struct drm_client_buffer *
> > > +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
> > > +			 u32 format);
> > > +void drm_client_buffer_delete(struct drm_client_buffer *buffer);
> > > +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
> > > +			    struct drm_mode_modeinfo *mode);
> > > +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer);
> > > diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
> > > index 7c4fa32f3fc6..32dfed3d5a86 100644
> > > --- a/include/drm/drm_device.h
> > > +++ b/include/drm/drm_device.h
> > > @@ -67,6 +67,7 @@ struct drm_device {
> > >   	struct mutex filelist_mutex;
> > >   	struct list_head filelist;
> > > +	struct list_head filelist_internal;
> > >   	/** \name Memory management */
> > >   	/*@{ */
> > > diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
> > > index 5176c3797680..39af8a4be7b3 100644
> > > --- a/include/drm/drm_file.h
> > > +++ b/include/drm/drm_file.h
> > > @@ -248,6 +248,13 @@ struct drm_file {
> > >   	 */
> > >   	void *driver_priv;
> > > +	/**
> > > +	 * @user_priv:
> > > +	 *
> > > +	 * Optional pointer for user private data. Useful for in-kernel clients.
> > > +	 */
> > > +	void *user_priv;
> > > +
> > >   	/**
> > >   	 * @fbs:
> > >   	 *
> > > -- 
> > > 2.15.1
> > > 
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

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

* Re: [RFC v3 09/12] drm: Add API for in-kernel clients
  2018-03-12 16:51       ` Daniel Vetter
@ 2018-03-12 20:21         ` Noralf Trønnes
  2018-03-13 15:11           ` Daniel Vetter
  0 siblings, 1 reply; 26+ messages in thread
From: Noralf Trønnes @ 2018-03-12 20:21 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart, dh.herrmann


Den 12.03.2018 17.51, skrev Daniel Vetter:
> On Thu, Mar 08, 2018 at 06:12:11PM +0100, Noralf Tr??nnes wrote:
>> Den 06.03.2018 09.56, skrev Daniel Vetter:
>>> On Thu, Feb 22, 2018 at 09:06:50PM +0100, Noralf Tr??nnes wrote:
>>>> This adds an API for writing in-kernel clients.
>>>>
>>>> TODO:
>>>> - Flesh out and complete documentation.
>>>> - Cloned displays is not tested.
>>>> - Complete tiled display support and test it.
>>>> - Test plug/unplug different monitors.
>>>> - A runtime knob to prevent clients from attaching for debugging purposes.
>>>> - Maybe a way to unbind individual client instances.
>>>> - Maybe take the sysrq support in drm_fb_helper and move it here somehow.
>>>> - Add suspend/resume callbacks.
>>>>     Does anyone know why fbdev requires suspend/resume?
>>>>
>>>> Signed-off-by: Noralf Tr??nnes <noralf@tronnes.org>
>>> The core client api I like. Some of the opens I'm seeing:
>>>
>>> - If we go with using the internal kms api directly instead of IOCTL
>>>     wrappers then a huge pile of the functions you have here aren't needed
>>>     (e.g. all the event stuff we can just directly use vblank events instead
>>>     of all the wrapping). I'm leaning ever more into that direction, since
>>>     much less code to add.
>> Looking at drm_fb_helper once again I now see an opportunity to simplify
>> the modesetting code by nuking drm_fb_helper_connector and stop
>> maintaining an array of connectors. It looks to be possible to just
>> create an array temporarily in drm_setup_crtcs() for the duration of the
>> function. The connectors we care about are ref counted and attached to
>> modesets. This would remove the need for drm_fb_helper_add_one_connector().
>>
>> So I might be able to do struct drm_fb_helper_crtc -> drm_client_crtc
>> and let the client API take over drm_setup_crtcs(). I'll give it a try.
> I'm more wondering why we need drm_client_crtc at all, why is drm_crtc not
> good enough. Or maybe I'm missing something. Imo ioctl wrappers should be
> the exception where we really, really need them (because the backend of
> the ioctl isn't implemented in a generic way, e.g. dumb buffers), not for
> stuff where we already have a perfectly useable in-kernel abi (anything
> related to modesetting).

I was talking about moving the modesetting code from drm_fb_helper.c
to drm_client.c, which meant moving 'struct drm_fb_helper_crtc' as well.

struct drm_fb_helper_crtc {
     struct drm_mode_set mode_set;
     struct drm_display_mode *desired_mode;
     int x, y;
};

But maybe we can get rid of that struct as well.
The info it contains is also available in drm_mode_set:

static void drm_setup_crtcs(struct drm_fb_helper *fb_helper,
                 u32 width, u32 height)
{
...
     drm_fb_helper_for_each_connector(fb_helper, i) {
         struct drm_display_mode *mode = modes[i];
         struct drm_fb_helper_crtc *fb_crtc = crtcs[i];
         struct drm_fb_offset *offset = &offsets[i];

         if (mode && fb_crtc) {
             struct drm_mode_set *modeset = &fb_crtc->mode_set;

             fb_crtc->desired_mode = mode;
             fb_crtc->x = offset->x;
             fb_crtc->y = offset->y;
             modeset->mode = drm_mode_duplicate(dev,
                                fb_crtc->desired_mode);
             modeset->x = offset->x;
             modeset->y = offset->y;
         }
     }

I took the hint about my ioctl wrappers :-)

Noralf.

>
> And in a way the ioctl wrappers wouldn't really be ioctl wrappers
> conceptually, but simple share some of the same code with the ioctl call
> chain. The idea is to provide some minimal wrappar around the ->dumb*
> callbacks.
>
> Anything else is not needed, I think.
>
>> There is one challenge I see upfront and that's the i915 fb_helper
>> callback in drm_setup_crtcs().
>>
>>> - The register/unregister model needs more thought. Allowing both clients
>>>     to register whenever they want to, and drm_device instances to come and
>>>     go is what fbcon has done, and the resulting locking is a horror show.
>>>
>>>     I think if we require that all in-kernel drm_clients are registers when
>>>     loading drm.ko (and enabled/disabled only per module options and
>>>     Kconfig), then we can throw out all the locking. That avoids a lot of
>>>     the headaches.
>>>
>>>     2nd, if the list of clients is static over the lifetime of drm.ko, we
>>>     also don't need to iterate existing drivers. Which avoids me having to
>>>     review the iterator patch (that's the other aspect where fbcon totally
>>>     falls over and essentially just ignores a bunch of races).
>> Are you talking about linking the clients into drm.ko?
>>
>> drivers/gpu/drm/Makefile:
>>
>> drm-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += client/drm_bootsplash.o
>>
>> drivers/gpu/drm/drm_drv.c:
>>
>> ??static int __init drm_core_init(void)
>> ??{
>> +?????? drm_bootsplash_register();
>> +?????? drm_fbdev_register();
>> ??}
>>
>> drivers/gpu/drm/drm_internal.h:
>>
>> #ifdef DRM_CLIENT_BOOTSPLASH
>> void drm_bootsplash_register(void);
>> #else
>> static inline void drm_bootsplash_register(void)
>> {
>> }
>> #endif
>>
>> drivers/gpu/drm/client/drm_bootsplash.c:
>>
>> static const struct drm_client_funcs drm_bootsplash_funcs = {
>> ?????? .name?????? ?????? = "drm_bootsplash",
>> ?????? .new?????? ?????? = drm_bootsplash_new,
>> ?????? .remove?????? ?????? = drm_bootsplash_remove,
>> ?????? .hotplug?????? = drm_bootsplash_hotplug,
>> };
>>
>> void drm_bootsplash_register(void)
>> {
>> ?????? drm_client_register(&drm_bootsplash_funcs);
>> }
>>
>> drivers/gpu/drm/drm_client.c:
>>
>> static LIST_HEAD(drm_client_funcs_list);
>>
>> void drm_client_register(const struct drm_client_funcs *funcs)
>> {
>> ?????? struct drm_client_funcs_entry *funcs_entry;
>>
>> ?????? funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
>> ?????? if (!funcs_entry) {
>> ?????? ?????? DRM_ERROR("Failed to register: %s\n", funcs->name);
>> ?????? ?????? return;
>> ?????? }
>>
>> ?????? funcs_entry->funcs = funcs;
>>
>> ?????? list_add(&funcs_entry->list, &drm_client_funcs_list);
>>
>> ?????? DRM_DEBUG_KMS("%s\n", funcs->name);
>> }
>>
>>
>> And each client having a runtime enable/disable knob:
>>
>> drivers/gpu/drm/client/drm_bootsplash.c:
>>
>> static bool drm_bootsplash_enabled = true;
>> module_param_named(bootsplash_enabled, drm_bootsplash_enabled, bool, 0600);
>> MODULE_PARM_DESC(bootsplash_enabled, "Enable bootsplash client
>> [default=true]");
>
> Yup, pretty much.
>
>> Simple USB Display
>> A few months back while looking at the udl shmem code, I got the idea
>> that I could turn a Raspberry Pi Zero into a $5 USB to HDMI/DSI/DPI/DBI/TV
>> adapter. The host side would be a simple tinydrm driver using the kernel
>> compression lib to speed up transfers. The gadget/device side would be a
>> userspace app decompressing the buffer into an exported dumb buffer.
>>
>> While working with this client API I realized that I could use it and
>> write a kernel gadget driver instead avoiding the challenge of going
>> back and forth to userspace with the framebuffer. For such a client I
>> would have preferred it to be a loadable module not linked into drm.ko
>> to increase the chance that distributions would enable it.
> I think that we can do as a loadable client, since you probably want some
> configfs interface to define which drm driver it should control. The
> reason behind the static list of built-in clients is purely for the
> auto-register stuff that we want for fbdev emulation and other things.
> Auto-registration where both sides can be loaded in any order is real pain
> wrt locking. Explicit registration where you can load both sides is
> totally fine and I think would cover your usb gadget display driver
> use-case.
>
> btw usb gadget driver to drive drm kms drivers sounds like a really cool
> thing.
> -Daniel
>
>> Noralf.
>>
>>>> ---
>>>>    drivers/gpu/drm/Kconfig             |    2 +
>>>>    drivers/gpu/drm/Makefile            |    3 +-
>>>>    drivers/gpu/drm/client/Kconfig      |    4 +
>>>>    drivers/gpu/drm/client/Makefile     |    1 +
>>>>    drivers/gpu/drm/client/drm_client.c | 1612 +++++++++++++++++++++++++++++++++++
>>> I'd move this into main drm/ directory, it's fairly core stuff.
>>>
>>>>    drivers/gpu/drm/drm_drv.c           |    6 +
>>>>    drivers/gpu/drm/drm_file.c          |    3 +
>>>>    drivers/gpu/drm/drm_probe_helper.c  |    3 +
>>>>    include/drm/drm_client.h            |  192 +++++
>>>>    include/drm/drm_device.h            |    1 +
>>>>    include/drm/drm_file.h              |    7 +
>>>>    11 files changed, 1833 insertions(+), 1 deletion(-)
>>>>    create mode 100644 drivers/gpu/drm/client/Kconfig
>>>>    create mode 100644 drivers/gpu/drm/client/Makefile
>>>>    create mode 100644 drivers/gpu/drm/client/drm_client.c
>>>>    create mode 100644 include/drm/drm_client.h
>>>>
>>>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>>>> index deeefa7a1773..d4ae15f9ee9f 100644
>>>> --- a/drivers/gpu/drm/Kconfig
>>>> +++ b/drivers/gpu/drm/Kconfig
>>>> @@ -154,6 +154,8 @@ config DRM_SCHED
>>>>    	tristate
>>>>    	depends on DRM
>>>> +source "drivers/gpu/drm/client/Kconfig"
>>>> +
>>>>    source "drivers/gpu/drm/i2c/Kconfig"
>>>>    source "drivers/gpu/drm/arm/Kconfig"
>>>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>>>> index 50093ff4479b..8e06dc7eeca1 100644
>>>> --- a/drivers/gpu/drm/Makefile
>>>> +++ b/drivers/gpu/drm/Makefile
>>>> @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
>>>>    		drm_encoder.o drm_mode_object.o drm_property.o \
>>>>    		drm_plane.o drm_color_mgmt.o drm_print.o \
>>>>    		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
>>>> -		drm_syncobj.o drm_lease.o
>>>> +		drm_syncobj.o drm_lease.o client/drm_client.o
>>>>    drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
>>>>    drm-$(CONFIG_DRM_VM) += drm_vm.o
>>>> @@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
>>>>    obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
>>>>    obj-$(CONFIG_DRM_PL111) += pl111/
>>>>    obj-$(CONFIG_DRM_TVE200) += tve200/
>>>> +obj-y			+= client/
>>>> diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
>>>> new file mode 100644
>>>> index 000000000000..4bb8e4655ff7
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/client/Kconfig
>>>> @@ -0,0 +1,4 @@
>>>> +menu "DRM Clients"
>>>> +	depends on DRM
>>>> +
>>>> +endmenu
>>>> diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
>>>> new file mode 100644
>>>> index 000000000000..f66554cd5c45
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/client/Makefile
>>>> @@ -0,0 +1 @@
>>>> +# SPDX-License-Identifier: GPL-2.0
>>>> diff --git a/drivers/gpu/drm/client/drm_client.c b/drivers/gpu/drm/client/drm_client.c
>>>> new file mode 100644
>>>> index 000000000000..a633bf747316
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/client/drm_client.c
>>>> @@ -0,0 +1,1612 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +// Copyright 2018 Noralf Tr??nnes
>>>> +
>>>> +#include <linux/dma-buf.h>
>>>> +#include <linux/list.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/slab.h>
>>>> +
>>>> +#include <drm/drm_client.h>
>>>> +#include <drm/drm_connector.h>
>>>> +#include <drm/drm_drv.h>
>>>> +#include <drm/drm_file.h>
>>>> +#include <drm/drm_ioctl.h>
>>>> +#include <drm/drmP.h>
>>>> +
>>>> +#include "drm_crtc_internal.h"
>>>> +#include "drm_internal.h"
>>>> +
>>>> +struct drm_client_funcs_entry {
>>>> +	struct list_head list;
>>>> +	const struct drm_client_funcs *funcs;
>>>> +};
>>>> +
>>>> +static LIST_HEAD(drm_client_list);
>>> I think the client list itself should be on the drm_device, not in one
>>> global list that mixes up all the clients of all the drm_devices.
>>>
>>> I'll skip reviewing details since we have a bunch of high-level questions
>>> to figure out first.
>>> -Daniel
>>>
>>>> +static LIST_HEAD(drm_client_funcs_list);
>>>> +static DEFINE_MUTEX(drm_client_list_lock);
>>>> +
>>>> +static void drm_client_new(struct drm_device *dev,
>>>> +			   const struct drm_client_funcs *funcs)
>>>> +{
>>>> +	struct drm_client_dev *client;
>>>> +	int ret;
>>>> +
>>>> +	lockdep_assert_held(&drm_client_list_lock);
>>>> +
>>>> +	client = kzalloc(sizeof(*client), GFP_KERNEL);
>>>> +	if (!client)
>>>> +		return;
>>>> +
>>>> +	mutex_init(&client->lock);
>>>> +	client->dev = dev;
>>>> +	client->funcs = funcs;
>>>> +
>>>> +	ret = funcs->new(client);
>>>> +	DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", funcs->name, ret);
>>>> +	if (ret) {
>>>> +		drm_client_free(client);
>>>> +		return;
>>>> +	}
>>>> +
>>>> +	list_add(&client->list, &drm_client_list);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_free - Free DRM client resources
>>>> + * @client: DRM client
>>>> + *
>>>> + * This is called automatically on client removal unless the client returns
>>>> + * non-zero in the &drm_client_funcs->remove callback. The fbdev client does
>>>> + * this when it can't close &drm_file because userspace has an open fd.
>>>> + */
>>>> +void drm_client_free(struct drm_client_dev *client)
>>>> +{
>>>> +	DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
>>>> +	if (WARN_ON(client->file)) {
>>>> +		client->file_ref_count = 1;
>>>> +		drm_client_put_file(client);
>>>> +	}
>>>> +	mutex_destroy(&client->lock);
>>>> +	kfree(client->crtcs);
>>>> +	kfree(client);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_free);
>>>> +
>>>> +static void drm_client_remove(struct drm_client_dev *client)
>>>> +{
>>>> +	lockdep_assert_held(&drm_client_list_lock);
>>>> +
>>>> +	list_del(&client->list);
>>>> +
>>>> +	if (!client->funcs->remove || !client->funcs->remove(client))
>>>> +		drm_client_free(client);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_register - Register a DRM client
>>>> + * @funcs: Client callbacks
>>>> + *
>>>> + * Returns:
>>>> + * Zero on success, negative error code on failure.
>>>> + */
>>>> +int drm_client_register(const struct drm_client_funcs *funcs)
>>>> +{
>>>> +	struct drm_client_funcs_entry *funcs_entry;
>>>> +	struct drm_device_list_iter iter;
>>>> +	struct drm_device *dev;
>>>> +
>>>> +	funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
>>>> +	if (!funcs_entry)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	funcs_entry->funcs = funcs;
>>>> +
>>>> +	mutex_lock(&drm_global_mutex);
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +
>>>> +	drm_device_list_iter_begin(&iter);
>>>> +	drm_for_each_device_iter(dev, &iter)
>>>> +		if (drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +			drm_client_new(dev, funcs);
>>>> +	drm_device_list_iter_end(&iter);
>>>> +
>>>> +	list_add(&funcs_entry->list, &drm_client_funcs_list);
>>>> +
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +	mutex_unlock(&drm_global_mutex);
>>>> +
>>>> +	DRM_DEBUG_KMS("%s\n", funcs->name);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_register);
>>>> +
>>>> +/**
>>>> + * drm_client_unregister - Unregister a DRM client
>>>> + * @funcs: Client callbacks
>>>> + */
>>>> +void drm_client_unregister(const struct drm_client_funcs *funcs)
>>>> +{
>>>> +	struct drm_client_funcs_entry *funcs_entry;
>>>> +	struct drm_client_dev *client, *tmp;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +
>>>> +	list_for_each_entry_safe(client, tmp, &drm_client_list, list) {
>>>> +		if (client->funcs == funcs)
>>>> +			drm_client_remove(client);
>>>> +	}
>>>> +
>>>> +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list) {
>>>> +		if (funcs_entry->funcs == funcs) {
>>>> +			list_del(&funcs_entry->list);
>>>> +			kfree(funcs_entry);
>>>> +			break;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +
>>>> +	DRM_DEBUG_KMS("%s\n", funcs->name);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_unregister);
>>>> +
>>>> +void drm_client_dev_register(struct drm_device *dev)
>>>> +{
>>>> +	struct drm_client_funcs_entry *funcs_entry;
>>>> +
>>>> +	/*
>>>> +	 * Minors are created at the beginning of drm_dev_register(), but can
>>>> +	 * be removed again if the function fails. Since we iterate DRM devices
>>>> +	 * by walking DRM minors, we need to stay under this lock.
>>>> +	 */
>>>> +	lockdep_assert_held(&drm_global_mutex);
>>>> +
>>>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list)
>>>> +		drm_client_new(dev, funcs_entry->funcs);
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +}
>>>> +
>>>> +void drm_client_dev_unregister(struct drm_device *dev)
>>>> +{
>>>> +	struct drm_client_dev *client, *tmp;
>>>> +
>>>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +	list_for_each_entry_safe(client, tmp, &drm_client_list, list)
>>>> +		if (client->dev == dev)
>>>> +			drm_client_remove(client);
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +}
>>>> +
>>>> +void drm_client_dev_hotplug(struct drm_device *dev)
>>>> +{
>>>> +	struct drm_client_dev *client;
>>>> +	int ret;
>>>> +
>>>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +	list_for_each_entry(client, &drm_client_list, list)
>>>> +		if (client->dev == dev && client->funcs->hotplug) {
>>>> +			ret = client->funcs->hotplug(client);
>>>> +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
>>>> +					  client->funcs->name, ret);
>>>> +		}
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +}
>>>> +
>>>> +void drm_client_dev_lastclose(struct drm_device *dev)
>>>> +{
>>>> +	struct drm_client_dev *client;
>>>> +	int ret;
>>>> +
>>>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +	list_for_each_entry(client, &drm_client_list, list)
>>>> +		if (client->dev == dev && client->funcs->lastclose) {
>>>> +			ret = client->funcs->lastclose(client);
>>>> +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
>>>> +					  client->funcs->name, ret);
>>>> +		}
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +}
>>>> +
>>>> +/* Get static info */
>>>> +static int drm_client_init(struct drm_client_dev *client, struct drm_file *file)
>>>> +{
>>>> +	struct drm_mode_card_res card_res = {};
>>>> +	struct drm_device *dev = client->dev;
>>>> +	u32 *crtcs;
>>>> +	int ret;
>>>> +
>>>> +	ret = drm_mode_getresources(dev, &card_res, file, false);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +	if (!card_res.count_crtcs)
>>>> +		return -ENOENT;
>>>> +
>>>> +	crtcs = kmalloc_array(card_res.count_crtcs, sizeof(*crtcs), GFP_KERNEL);
>>>> +	if (!crtcs)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	card_res.count_fbs = 0;
>>>> +	card_res.count_connectors = 0;
>>>> +	card_res.count_encoders = 0;
>>>> +	card_res.crtc_id_ptr = (unsigned long)crtcs;
>>>> +
>>>> +	ret = drm_mode_getresources(dev, &card_res, file, false);
>>>> +	if (ret) {
>>>> +		kfree(crtcs);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	client->crtcs = crtcs;
>>>> +	client->num_crtcs = card_res.count_crtcs;
>>>> +	client->min_width = card_res.min_width;
>>>> +	client->max_width = card_res.max_width;
>>>> +	client->min_height = card_res.min_height;
>>>> +	client->max_height = card_res.max_height;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_get_file - Get a DRM file
>>>> + * @client: DRM client
>>>> + *
>>>> + * This function makes sure the client has a &drm_file available. The client
>>>> + * doesn't normally need to call this, since all client functions that depends
>>>> + * on a DRM file will call it. A matching call to drm_client_put_file() is
>>>> + * necessary.
>>>> + *
>>>> + * The reason for not opening a DRM file when a @client is created is because
>>>> + * we have to take a ref on the driver module due to &drm_driver->postclose
>>>> + * being called in drm_file_free(). Having a DRM file open for the lifetime of
>>>> + * the client instance would block driver module unload.
>>>> + *
>>>> + * Returns:
>>>> + * Zero on success, negative error code on failure.
>>>> + */
>>>> +int drm_client_get_file(struct drm_client_dev *client)
>>>> +{
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_file *file;
>>>> +	int ret = 0;
>>>> +
>>>> +	mutex_lock(&client->lock);
>>>> +
>>>> +	if (client->file_ref_count++) {
>>>> +		mutex_unlock(&client->lock);
>>>> +		return 0;
>>>> +	}
>>>> +
>>>> +	if (!try_module_get(dev->driver->fops->owner)) {
>>>> +		ret = -ENODEV;
>>>> +		goto err_unlock;
>>>> +	}
>>>> +
>>>> +	drm_dev_get(dev);
>>>> +
>>>> +	file = drm_file_alloc(dev->primary);
>>>> +	if (IS_ERR(file)) {
>>>> +		ret = PTR_ERR(file);
>>>> +		goto err_put;
>>>> +	}
>>>> +
>>>> +	if (!client->crtcs) {
>>>> +		ret = drm_client_init(client, file);
>>>> +		if (ret)
>>>> +			goto err_free;
>>>> +	}
>>>> +
>>>> +	mutex_lock(&dev->filelist_mutex);
>>>> +	list_add(&file->lhead, &dev->filelist_internal);
>>>> +	mutex_unlock(&dev->filelist_mutex);
>>>> +
>>>> +	client->file = file;
>>>> +
>>>> +	mutex_unlock(&client->lock);
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_free:
>>>> +	drm_file_free(file);
>>>> +err_put:
>>>> +	drm_dev_put(dev);
>>>> +	module_put(dev->driver->fops->owner);
>>>> +err_unlock:
>>>> +	client->file_ref_count = 0;
>>>> +	mutex_unlock(&client->lock);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_get_file);
>>>> +
>>>> +void drm_client_put_file(struct drm_client_dev *client)
>>>> +{
>>>> +	struct drm_device *dev = client->dev;
>>>> +
>>>> +	if (!client)
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&client->lock);
>>>> +
>>>> +	if (WARN_ON(!client->file_ref_count))
>>>> +		goto out_unlock;
>>>> +
>>>> +	if (--client->file_ref_count)
>>>> +		goto out_unlock;
>>>> +
>>>> +	mutex_lock(&dev->filelist_mutex);
>>>> +	list_del(&client->file->lhead);
>>>> +	mutex_unlock(&dev->filelist_mutex);
>>>> +
>>>> +	drm_file_free(client->file);
>>>> +	client->file = NULL;
>>>> +	drm_dev_put(dev);
>>>> +	module_put(dev->driver->fops->owner);
>>>> +out_unlock:
>>>> +	mutex_unlock(&client->lock);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_put_file);
>>>> +
>>>> +static struct drm_pending_event *
>>>> +drm_client_read_get_pending_event(struct drm_device *dev, struct drm_file *file)
>>>> +{
>>>> +	struct drm_pending_event *e = NULL;
>>>> +	int ret;
>>>> +
>>>> +	ret = mutex_lock_interruptible(&file->event_read_lock);
>>>> +	if (ret)
>>>> +		return ERR_PTR(ret);
>>>> +
>>>> +	spin_lock_irq(&dev->event_lock);
>>>> +	if (!list_empty(&file->event_list)) {
>>>> +		e = list_first_entry(&file->event_list,
>>>> +				     struct drm_pending_event, link);
>>>> +		file->event_space += e->event->length;
>>>> +		list_del(&e->link);
>>>> +	}
>>>> +	spin_unlock_irq(&dev->event_lock);
>>>> +
>>>> +	mutex_unlock(&file->event_read_lock);
>>>> +
>>>> +	return e;
>>>> +}
>>>> +
>>>> +struct drm_event *
>>>> +drm_client_read_event(struct drm_client_dev *client, bool block)
>>>> +{
>>>> +	struct drm_file *file = client->file;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_pending_event *e;
>>>> +	struct drm_event *event;
>>>> +	int ret;
>>>> +
>>>> +	/* Allocate so it fits all events, there's a sanity check later */
>>>> +	event = kzalloc(128, GFP_KERNEL);
>>>> +	if (!event)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	e = drm_client_read_get_pending_event(dev, file);
>>>> +	if (IS_ERR(e)) {
>>>> +		ret = PTR_ERR(e);
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	if (!e && !block) {
>>>> +		ret = 0;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	ret = wait_event_interruptible_timeout(file->event_wait,
>>>> +					       !list_empty(&file->event_list),
>>>> +					       5 * HZ);
>>>> +	if (!ret)
>>>> +		ret = -ETIMEDOUT;
>>>> +	if (ret < 0)
>>>> +		goto err_free;
>>>> +
>>>> +	e = drm_client_read_get_pending_event(dev, file);
>>>> +	if (IS_ERR_OR_NULL(e)) {
>>>> +		ret = PTR_ERR_OR_ZERO(e);
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	if (WARN_ON(e->event->length > 128)) {
>>>> +		/* Increase buffer if this happens */
>>>> +		ret = -ENOMEM;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	memcpy(event, e->event, e->event->length);
>>>> +	kfree(e);
>>>> +
>>>> +	return event;
>>>> +
>>>> +err_free:
>>>> +	kfree(event);
>>>> +
>>>> +	return ret ? ERR_PTR(ret) : NULL;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_read_event);
>>>> +
>>>> +static void drm_client_connector_free(struct drm_client_connector *connector)
>>>> +{
>>>> +	if (!connector)
>>>> +		return;
>>>> +	kfree(connector->modes);
>>>> +	kfree(connector);
>>>> +}
>>>> +
>>>> +static struct drm_client_connector *
>>>> +drm_client_get_connector(struct drm_client_dev *client, unsigned int id)
>>>> +{
>>>> +	struct drm_mode_get_connector req = {
>>>> +		.connector_id = id,
>>>> +	};
>>>> +	struct drm_client_connector *connector;
>>>> +	struct drm_mode_modeinfo *modes = NULL;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_connector *conn;
>>>> +	bool non_desktop;
>>>> +	int ret;
>>>> +
>>>> +	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
>>>> +	if (!connector)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	ret = drm_mode_getconnector(dev, &req, client->file, false);
>>>> +	if (ret)
>>>> +		goto err_free;
>>>> +
>>>> +	connector->conn_id = id;
>>>> +	connector->status = req.connection;
>>>> +
>>>> +	conn = drm_connector_lookup(dev, client->file, id);
>>>> +	if (!conn) {
>>>> +		ret = -ENOENT;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	non_desktop = conn->display_info.non_desktop;
>>>> +
>>>> +	connector->has_tile = conn->has_tile;
>>>> +	connector->tile_h_loc = conn->tile_h_loc;
>>>> +	connector->tile_v_loc = conn->tile_v_loc;
>>>> +	if (conn->tile_group)
>>>> +		connector->tile_group = conn->tile_group->id;
>>>> +
>>>> +	drm_connector_put(conn);
>>>> +
>>>> +	if (non_desktop) {
>>>> +		kfree(connector);
>>>> +		return NULL;
>>>> +	}
>>>> +
>>>> +	if (!req.count_modes)
>>>> +		return connector;
>>>> +
>>>> +	modes = kcalloc(req.count_modes, sizeof(*modes), GFP_KERNEL);
>>>> +	if (!modes) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	connector->modes = modes;
>>>> +	connector->num_modes = req.count_modes;
>>>> +
>>>> +	req.count_props = 0;
>>>> +	req.count_encoders = 0;
>>>> +	req.modes_ptr = (unsigned long)modes;
>>>> +
>>>> +	ret = drm_mode_getconnector(dev, &req, client->file, false);
>>>> +	if (ret)
>>>> +		goto err_free;
>>>> +
>>>> +	return connector;
>>>> +
>>>> +err_free:
>>>> +	kfree(modes);
>>>> +	kfree(connector);
>>>> +
>>>> +	return ERR_PTR(ret);
>>>> +}
>>>> +
>>>> +static int drm_client_get_connectors(struct drm_client_dev *client,
>>>> +				     struct drm_client_connector ***connectors)
>>>> +{
>>>> +	struct drm_mode_card_res card_res = {};
>>>> +	struct drm_device *dev = client->dev;
>>>> +	int ret, num_connectors;
>>>> +	u32 *connector_ids;
>>>> +	unsigned int i;
>>>> +
>>>> +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +	if (!card_res.count_connectors)
>>>> +		return 0;
>>>> +
>>>> +	num_connectors = card_res.count_connectors;
>>>> +	connector_ids = kcalloc(num_connectors,
>>>> +				sizeof(*connector_ids), GFP_KERNEL);
>>>> +	if (!connector_ids)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	card_res.count_fbs = 0;
>>>> +	card_res.count_crtcs = 0;
>>>> +	card_res.count_encoders = 0;
>>>> +	card_res.connector_id_ptr = (unsigned long)connector_ids;
>>>> +
>>>> +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
>>>> +	if (ret)
>>>> +		goto err_free;
>>>> +
>>>> +	*connectors = kcalloc(num_connectors, sizeof(**connectors), GFP_KERNEL);
>>>> +	if (!(*connectors)) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		struct drm_client_connector *connector;
>>>> +
>>>> +		connector = drm_client_get_connector(client, connector_ids[i]);
>>>> +		if (IS_ERR(connector)) {
>>>> +			ret = PTR_ERR(connector);
>>>> +			goto err_free;
>>>> +		}
>>>> +		if (connector)
>>>> +			(*connectors)[i] = connector;
>>>> +		else
>>>> +			num_connectors--;
>>>> +	}
>>>> +
>>>> +	if (!num_connectors) {
>>>> +		ret = 0;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	return num_connectors;
>>>> +
>>>> +err_free:
>>>> +	if (connectors)
>>>> +		for (i = 0; i < num_connectors; i++)
>>>> +			drm_client_connector_free((*connectors)[i]);
>>>> +
>>>> +	kfree(connectors);
>>>> +	kfree(connector_ids);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static bool
>>>> +drm_client_connector_is_enabled(struct drm_client_connector *connector,
>>>> +				bool strict)
>>>> +{
>>>> +	if (strict)
>>>> +		return connector->status == connector_status_connected;
>>>> +	else
>>>> +		return connector->status != connector_status_disconnected;
>>>> +}
>>>> +
>>>> +struct drm_mode_modeinfo *
>>>> +drm_client_display_first_mode(struct drm_client_display *display)
>>>> +{
>>>> +	if (!display->num_modes)
>>>> +		return NULL;
>>>> +	return display->modes;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_first_mode);
>>>> +
>>>> +struct drm_mode_modeinfo *
>>>> +drm_client_display_next_mode(struct drm_client_display *display,
>>>> +			     struct drm_mode_modeinfo *mode)
>>>> +{
>>>> +	struct drm_mode_modeinfo *modes = display->modes;
>>>> +
>>>> +	if (++mode < &modes[display->num_modes])
>>>> +		return mode;
>>>> +
>>>> +	return NULL;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_next_mode);
>>>> +
>>>> +static void
>>>> +drm_client_display_fill_tile_modes(struct drm_client_display *display,
>>>> +				   struct drm_mode_modeinfo *tile_modes)
>>>> +{
>>>> +	unsigned int i, j, num_modes = display->connectors[0]->num_modes;
>>>> +	struct drm_mode_modeinfo *tile_mode, *conn_mode;
>>>> +
>>>> +	if (!num_modes) {
>>>> +		kfree(tile_modes);
>>>> +		kfree(display->modes);
>>>> +		display->modes = NULL;
>>>> +		display->num_modes = 0;
>>>> +		return;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_modes; i++) {
>>>> +		tile_mode = &tile_modes[i];
>>>> +
>>>> +		conn_mode = &display->connectors[0]->modes[i];
>>>> +		tile_mode->clock = conn_mode->clock;
>>>> +		tile_mode->vscan = conn_mode->vscan;
>>>> +		tile_mode->vrefresh = conn_mode->vrefresh;
>>>> +		tile_mode->flags = conn_mode->flags;
>>>> +		tile_mode->type = conn_mode->type;
>>>> +
>>>> +		for (j = 0; j < display->num_connectors; j++) {
>>>> +			conn_mode = &display->connectors[j]->modes[i];
>>>> +
>>>> +			if (!display->connectors[j]->tile_h_loc) {
>>>> +				tile_mode->hdisplay += conn_mode->hdisplay;
>>>> +				tile_mode->hsync_start += conn_mode->hsync_start;
>>>> +				tile_mode->hsync_end += conn_mode->hsync_end;
>>>> +				tile_mode->htotal += conn_mode->htotal;
>>>> +			}
>>>> +
>>>> +			if (!display->connectors[j]->tile_v_loc) {
>>>> +				tile_mode->vdisplay += conn_mode->vdisplay;
>>>> +				tile_mode->vsync_start += conn_mode->vsync_start;
>>>> +				tile_mode->vsync_end += conn_mode->vsync_end;
>>>> +				tile_mode->vtotal += conn_mode->vtotal;
>>>> +			}
>>>> +		}
>>>> +	}
>>>> +
>>>> +	kfree(display->modes);
>>>> +	display->modes = tile_modes;
>>>> +	display->num_modes = num_modes;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_display_update_modes - Fetch display modes
>>>> + * @display: Client display
>>>> + * @mode_changed: Optional pointer to boolen which return whether the modes
>>>> + *                have changed or not.
>>>> + *
>>>> + * This function can be used in the client hotplug callback to check if the
>>>> + * video modes have changed and get them up-to-date.
>>>> + *
>>>> + * Returns:
>>>> + * Number of modes on success, negative error code on failure.
>>>> + */
>>>> +int drm_client_display_update_modes(struct drm_client_display *display,
>>>> +				    bool *mode_changed)
>>>> +{
>>>> +	unsigned int num_connectors = display->num_connectors;
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_mode_modeinfo *display_tile_modes;
>>>> +	struct drm_client_connector **connectors;
>>>> +	unsigned int i, num_modes = 0;
>>>> +	bool dummy_changed = false;
>>>> +	int ret;
>>>> +
>>>> +	if (mode_changed)
>>>> +		*mode_changed = false;
>>>> +	else
>>>> +		mode_changed = &dummy_changed;
>>>> +
>>>> +	if (display->cloned)
>>>> +		return 2;
>>>> +
>>>> +	ret = drm_client_get_file(client);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
>>>> +	if (!connectors) {
>>>> +		ret = -ENOMEM;
>>>> +		goto out_put_file;
>>>> +	}
>>>> +
>>>> +	/* Get a new set for comparison */
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		connectors[i] = drm_client_get_connector(client, display->connectors[i]->conn_id);
>>>> +		if (IS_ERR_OR_NULL(connectors[i])) {
>>>> +			ret = PTR_ERR_OR_ZERO(connectors[i]);
>>>> +			if (!ret)
>>>> +				ret = -ENOENT;
>>>> +			goto out_cleanup;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	/* All connectors should have the same number of modes */
>>>> +	num_modes = connectors[0]->num_modes;
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		if (num_modes != connectors[i]->num_modes) {
>>>> +			ret = -EINVAL;
>>>> +			goto out_cleanup;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	if (num_connectors > 1) {
>>>> +		display_tile_modes = kcalloc(num_modes, sizeof(*display_tile_modes), GFP_KERNEL);
>>>> +		if (!display_tile_modes) {
>>>> +			ret = -ENOMEM;
>>>> +			goto out_cleanup;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	mutex_lock(&display->modes_lock);
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		display->connectors[i]->status = connectors[i]->status;
>>>> +		if (display->connectors[i]->num_modes != connectors[i]->num_modes) {
>>>> +			display->connectors[i]->num_modes = connectors[i]->num_modes;
>>>> +			kfree(display->connectors[i]->modes);
>>>> +			display->connectors[i]->modes = connectors[i]->modes;
>>>> +			connectors[i]->modes = NULL;
>>>> +			*mode_changed = true;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	if (num_connectors > 1)
>>>> +		drm_client_display_fill_tile_modes(display, display_tile_modes);
>>>> +	else
>>>> +		display->modes = display->connectors[0]->modes;
>>>> +
>>>> +	mutex_unlock(&display->modes_lock);
>>>> +
>>>> +out_cleanup:
>>>> +	for (i = 0; i < num_connectors; i++)
>>>> +		drm_client_connector_free(connectors[i]);
>>>> +	kfree(connectors);
>>>> +out_put_file:
>>>> +	drm_client_put_file(client);
>>>> +
>>>> +	return ret ? ret : num_modes;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_update_modes);
>>>> +
>>>> +void drm_client_display_free(struct drm_client_display *display)
>>>> +{
>>>> +	unsigned int i;
>>>> +
>>>> +	if (!display)
>>>> +		return;
>>>> +
>>>> +	/* tile modes? */
>>>> +	if (display->modes != display->connectors[0]->modes)
>>>> +		kfree(display->modes);
>>>> +
>>>> +	for (i = 0; i < display->num_connectors; i++)
>>>> +		drm_client_connector_free(display->connectors[i]);
>>>> +
>>>> +	kfree(display->connectors);
>>>> +	mutex_destroy(&display->modes_lock);
>>>> +	kfree(display);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_free);
>>>> +
>>>> +static struct drm_client_display *
>>>> +drm_client_display_alloc(struct drm_client_dev *client,
>>>> +			 unsigned int num_connectors)
>>>> +{
>>>> +	struct drm_client_display *display;
>>>> +	struct drm_client_connector **connectors;
>>>> +
>>>> +	display = kzalloc(sizeof(*display), GFP_KERNEL);
>>>> +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
>>>> +	if (!display || !connectors) {
>>>> +		kfree(display);
>>>> +		kfree(connectors);
>>>> +		return NULL;
>>>> +	}
>>>> +
>>>> +	mutex_init(&display->modes_lock);
>>>> +	display->client = client;
>>>> +	display->connectors = connectors;
>>>> +	display->num_connectors = num_connectors;
>>>> +
>>>> +	return display;
>>>> +}
>>>> +
>>>> +/* Logic is from drm_fb_helper */
>>>> +static struct drm_client_display *
>>>> +drm_client_connector_pick_cloned(struct drm_client_dev *client,
>>>> +				 struct drm_client_connector **connectors,
>>>> +				 unsigned int num_connectors)
>>>> +{
>>>> +	struct drm_mode_modeinfo modes[2], udmt_mode, *mode, *tmp;
>>>> +	struct drm_display_mode *dmt_display_mode = NULL;
>>>> +	unsigned int i, j, conns[2], num_conns = 0;
>>>> +	struct drm_client_connector *connector;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_client_display *display;
>>>> +
>>>> +	/* only contemplate cloning in the single crtc case */
>>>> +	if (dev->mode_config.num_crtc > 1)
>>>> +		return NULL;
>>>> +retry:
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		connector = connectors[i];
>>>> +		if (!connector || connector->has_tile || !connector->num_modes)
>>>> +			continue;
>>>> +
>>>> +		for (j = 0; j < connector->num_modes; j++) {
>>>> +			mode = &connector->modes[j];
>>>> +			if (dmt_display_mode) {
>>>> +				if (drm_umode_equal(&udmt_mode, mode)) {
>>>> +					conns[num_conns] = i;
>>>> +					modes[num_conns++] = *mode;
>>>> +					break;
>>>> +				}
>>>> +			} else {
>>>> +				if (mode->type & DRM_MODE_TYPE_USERDEF) {
>>>> +					conns[num_conns] = i;
>>>> +					modes[num_conns++] = *mode;
>>>> +					break;
>>>> +				}
>>>> +			}
>>>> +		}
>>>> +		if (num_conns == 2)
>>>> +			break;
>>>> +	}
>>>> +
>>>> +	if (num_conns == 2)
>>>> +		goto found;
>>>> +
>>>> +	if (dmt_display_mode)
>>>> +		return NULL;
>>>> +
>>>> +	dmt_display_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
>>>> +	drm_mode_convert_to_umode(&udmt_mode, dmt_display_mode);
>>>> +	drm_mode_destroy(dev, dmt_display_mode);
>>>> +
>>>> +	goto retry;
>>>> +
>>>> +found:
>>>> +	tmp = kcalloc(2, sizeof(*tmp), GFP_KERNEL);
>>>> +	if (!tmp)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	display = drm_client_display_alloc(client, 2);
>>>> +	if (!display) {
>>>> +		kfree(tmp);
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < 2; i++) {
>>>> +		connector = connectors[conns[i]];
>>>> +		display->connectors[i] = connector;
>>>> +		connectors[conns[i]] = NULL;
>>>> +		kfree(connector->modes);
>>>> +		tmp[i] = modes[i];
>>>> +		connector->modes = &tmp[i];
>>>> +		connector->num_modes = 1;
>>>> +	}
>>>> +
>>>> +	display->cloned = true;
>>>> +	display->modes = &tmp[0];
>>>> +	display->num_modes = 1;
>>>> +
>>>> +	return display;
>>>> +}
>>>> +
>>>> +static struct drm_client_display *
>>>> +drm_client_connector_pick_tile(struct drm_client_dev *client,
>>>> +			       struct drm_client_connector **connectors,
>>>> +			       unsigned int num_connectors)
>>>> +{
>>>> +	unsigned int i, num_conns, num_modes, tile_group = 0;
>>>> +	struct drm_mode_modeinfo *tile_modes = NULL;
>>>> +	struct drm_client_connector *connector;
>>>> +	struct drm_client_display *display;
>>>> +	u16 conns[32];
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		connector = connectors[i];
>>>> +		if (!connector || !connector->tile_group)
>>>> +			continue;
>>>> +
>>>> +		if (!tile_group) {
>>>> +			tile_group = connector->tile_group;
>>>> +			num_modes = connector->num_modes;
>>>> +		}
>>>> +
>>>> +		if (connector->tile_group != tile_group)
>>>> +			continue;
>>>> +
>>>> +		if (num_modes != connector->num_modes) {
>>>> +			DRM_ERROR("Tile connectors must have the same number of modes\n");
>>>> +			return ERR_PTR(-EINVAL);
>>>> +		}
>>>> +
>>>> +		conns[num_conns++] = i;
>>>> +		if (WARN_ON(num_conns == 33))
>>>> +			return ERR_PTR(-EINVAL);
>>>> +	}
>>>> +
>>>> +	if (!num_conns)
>>>> +		return NULL;
>>>> +
>>>> +	if (num_modes) {
>>>> +		tile_modes = kcalloc(num_modes, sizeof(*tile_modes), GFP_KERNEL);
>>>> +		if (!tile_modes)
>>>> +			return ERR_PTR(-ENOMEM);
>>>> +	}
>>>> +
>>>> +	display = drm_client_display_alloc(client, num_conns);
>>>> +	if (!display) {
>>>> +		kfree(tile_modes);
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +	}
>>>> +
>>>> +	if (num_modes)
>>>> +		drm_client_display_fill_tile_modes(display, tile_modes);
>>>> +
>>>> +	return display;
>>>> +}
>>>> +
>>>> +static struct drm_client_display *
>>>> +drm_client_connector_pick_not_tile(struct drm_client_dev *client,
>>>> +				   struct drm_client_connector **connectors,
>>>> +				   unsigned int num_connectors)
>>>> +{
>>>> +	struct drm_client_display *display;
>>>> +	unsigned int i;
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		if (!connectors[i] || connectors[i]->has_tile)
>>>> +			continue;
>>>> +		break;
>>>> +	}
>>>> +
>>>> +	if (i == num_connectors)
>>>> +		return NULL;
>>>> +
>>>> +	display = drm_client_display_alloc(client, 1);
>>>> +	if (!display)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	display->connectors[0] = connectors[i];
>>>> +	connectors[i] = NULL;
>>>> +	display->modes = display->connectors[0]->modes;
>>>> +	display->num_modes = display->connectors[0]->num_modes;
>>>> +
>>>> +	return display;
>>>> +}
>>>> +
>>>> +/* Get connectors and bundle them up into displays */
>>>> +static int drm_client_get_displays(struct drm_client_dev *client,
>>>> +				   struct drm_client_display ***displays)
>>>> +{
>>>> +	int ret, num_connectors, num_displays = 0;
>>>> +	struct drm_client_connector **connectors;
>>>> +	struct drm_client_display *display;
>>>> +	unsigned int i;
>>>> +
>>>> +	ret = drm_client_get_file(client);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	num_connectors = drm_client_get_connectors(client, &connectors);
>>>> +	if (num_connectors <= 0) {
>>>> +		ret = num_connectors;
>>>> +		goto err_put_file;
>>>> +	}
>>>> +
>>>> +	*displays = kcalloc(num_connectors, sizeof(*displays), GFP_KERNEL);
>>>> +	if (!(*displays)) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	display = drm_client_connector_pick_cloned(client, connectors,
>>>> +						   num_connectors);
>>>> +	if (IS_ERR(display)) {
>>>> +		ret = PTR_ERR(display);
>>>> +		goto err_free;
>>>> +	}
>>>> +	if (display)
>>>> +		(*displays)[num_displays++] = display;
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		display = drm_client_connector_pick_tile(client, connectors,
>>>> +							 num_connectors);
>>>> +		if (IS_ERR(display)) {
>>>> +			ret = PTR_ERR(display);
>>>> +			goto err_free;
>>>> +		}
>>>> +		if (!display)
>>>> +			break;
>>>> +		(*displays)[num_displays++] = display;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		display = drm_client_connector_pick_not_tile(client, connectors,
>>>> +							     num_connectors);
>>>> +		if (IS_ERR(display)) {
>>>> +			ret = PTR_ERR(display);
>>>> +			goto err_free;
>>>> +		}
>>>> +		if (!display)
>>>> +			break;
>>>> +		(*displays)[num_displays++] = display;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		if (connectors[i]) {
>>>> +			DRM_INFO("Connector %u fell through the cracks.\n",
>>>> +				 connectors[i]->conn_id);
>>>> +			drm_client_connector_free(connectors[i]);
>>>> +		}
>>>> +	}
>>>> +
>>>> +	drm_client_put_file(client);
>>>> +	kfree(connectors);
>>>> +
>>>> +	return num_displays;
>>>> +
>>>> +err_free:
>>>> +	for (i = 0; i < num_displays; i++)
>>>> +		drm_client_display_free((*displays)[i]);
>>>> +	kfree(*displays);
>>>> +	*displays = NULL;
>>>> +	for (i = 0; i < num_connectors; i++)
>>>> +		drm_client_connector_free(connectors[i]);
>>>> +	kfree(connectors);
>>>> +err_put_file:
>>>> +	drm_client_put_file(client);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static bool
>>>> +drm_client_display_is_enabled(struct drm_client_display *display, bool strict)
>>>> +{
>>>> +	unsigned int i;
>>>> +
>>>> +	if (!display->num_modes)
>>>> +		return false;
>>>> +
>>>> +	for (i = 0; i < display->num_connectors; i++)
>>>> +		if (!drm_client_connector_is_enabled(display->connectors[i], strict))
>>>> +			return false;
>>>> +
>>>> +	return true;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_display_get_first_enabled - Get first enabled display
>>>> + * @client: DRM client
>>>> + * @strict: If true the connector(s) have to be connected, if false they can
>>>> + *          also have unknown status.
>>>> + *
>>>> + * This function gets all connectors and bundles them into displays
>>>> + * (tiled/cloned). It then picks the first one with connectors that is enabled
>>>> + * according to @strict.
>>>> + *
>>>> + * Returns:
>>>> + * Pointer to a client display if such a display was found, NULL if not found
>>>> + * or an error pointer on failure.
>>>> + */
>>>> +struct drm_client_display *
>>>> +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict)
>>>> +{
>>>> +	struct drm_client_display **displays, *display = NULL;
>>>> +	int num_displays;
>>>> +	unsigned int i;
>>>> +
>>>> +	num_displays = drm_client_get_displays(client, &displays);
>>>> +	if (num_displays < 0)
>>>> +		return ERR_PTR(num_displays);
>>>> +	if (!num_displays)
>>>> +		return NULL;
>>>> +
>>>> +	for (i = 0; i < num_displays; i++) {
>>>> +		if (!display &&
>>>> +		    drm_client_display_is_enabled(displays[i], strict)) {
>>>> +			display = displays[i];
>>>> +			continue;
>>>> +		}
>>>> +		drm_client_display_free(displays[i]);
>>>> +	}
>>>> +
>>>> +	kfree(displays);
>>>> +
>>>> +	return display;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_get_first_enabled);
>>>> +
>>>> +unsigned int
>>>> +drm_client_display_preferred_depth(struct drm_client_display *display)
>>>> +{
>>>> +	struct drm_connector *conn;
>>>> +	unsigned int ret;
>>>> +
>>>> +	conn = drm_connector_lookup(display->client->dev, NULL,
>>>> +				    display->connectors[0]->conn_id);
>>>> +	if (!conn)
>>>> +		return 0;
>>>> +
>>>> +	if (conn->cmdline_mode.bpp_specified)
>>>> +		ret = conn->cmdline_mode.bpp;
>>>> +	else
>>>> +		ret = display->client->dev->mode_config.preferred_depth;
>>>> +
>>>> +	drm_connector_put(conn);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_preferred_depth);
>>>> +
>>>> +int drm_client_display_dpms(struct drm_client_display *display, int mode)
>>>> +{
>>>> +	struct drm_mode_obj_set_property prop;
>>>> +
>>>> +	prop.value = mode;
>>>> +	prop.prop_id = display->client->dev->mode_config.dpms_property->base.id;
>>>> +	prop.obj_id = display->connectors[0]->conn_id;
>>>> +	prop.obj_type = DRM_MODE_OBJECT_CONNECTOR;
>>>> +
>>>> +	return drm_mode_obj_set_property(display->client->dev, &prop,
>>>> +					 display->client->file);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_dpms);
>>>> +
>>>> +int drm_client_display_wait_vblank(struct drm_client_display *display)
>>>> +{
>>>> +	struct drm_crtc *crtc;
>>>> +	union drm_wait_vblank vblank_req = {
>>>> +		.request = {
>>>> +			.type = _DRM_VBLANK_RELATIVE,
>>>> +			.sequence = 1,
>>>> +		},
>>>> +	};
>>>> +
>>>> +	crtc = drm_crtc_find(display->client->dev, display->client->file,
>>>> +			     display->connectors[0]->crtc_id);
>>>> +	if (!crtc)
>>>> +		return -ENOENT;
>>>> +
>>>> +	vblank_req.request.type |= drm_crtc_index(crtc) << _DRM_VBLANK_HIGH_CRTC_SHIFT;
>>>> +
>>>> +	return drm_wait_vblank(display->client->dev, &vblank_req,
>>>> +			       display->client->file);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_wait_vblank);
>>>> +
>>>> +static int drm_client_get_crtc_index(struct drm_client_dev *client, u32 id)
>>>> +{
>>>> +	int i;
>>>> +
>>>> +	for (i = 0; i < client->num_crtcs; i++)
>>>> +		if (client->crtcs[i] == id)
>>>> +			return i;
>>>> +
>>>> +	return -ENOENT;
>>>> +}
>>>> +
>>>> +static int drm_client_display_find_crtcs(struct drm_client_display *display)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_file *file = client->file;
>>>> +	u32 encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
>>>> +	unsigned int i, j, available_crtcs = ~0;
>>>> +	struct drm_mode_get_connector conn_req;
>>>> +	struct drm_mode_get_encoder enc_req;
>>>> +	int ret;
>>>> +
>>>> +	/* Already done? */
>>>> +	if (display->connectors[0]->crtc_id)
>>>> +		return 0;
>>>> +
>>>> +	for (i = 0; i < display->num_connectors; i++) {
>>>> +		u32 active_crtcs = 0, crtcs_for_connector = 0;
>>>> +		int crtc_idx;
>>>> +
>>>> +		memset(&conn_req, 0, sizeof(conn_req));
>>>> +		conn_req.connector_id = display->connectors[i]->conn_id;
>>>> +		conn_req.encoders_ptr = (unsigned long)(encoder_ids);
>>>> +		conn_req.count_encoders = DRM_CONNECTOR_MAX_ENCODER;
>>>> +		ret = drm_mode_getconnector(dev, &conn_req, file, false);
>>>> +		if (ret)
>>>> +			return ret;
>>>> +
>>>> +		if (conn_req.encoder_id) {
>>>> +			memset(&enc_req, 0, sizeof(enc_req));
>>>> +			enc_req.encoder_id = conn_req.encoder_id;
>>>> +			ret = drm_mode_getencoder(dev, &enc_req, file);
>>>> +			if (ret)
>>>> +				return ret;
>>>> +			crtcs_for_connector |= enc_req.possible_crtcs;
>>>> +			if (crtcs_for_connector & available_crtcs)
>>>> +				goto found;
>>>> +		}
>>>> +
>>>> +		for (j = 0; j < conn_req.count_encoders; j++) {
>>>> +			memset(&enc_req, 0, sizeof(enc_req));
>>>> +			enc_req.encoder_id = encoder_ids[j];
>>>> +			ret = drm_mode_getencoder(dev, &enc_req, file);
>>>> +			if (ret)
>>>> +				return ret;
>>>> +
>>>> +			crtcs_for_connector |= enc_req.possible_crtcs;
>>>> +
>>>> +			if (enc_req.crtc_id) {
>>>> +				crtc_idx = drm_client_get_crtc_index(client, enc_req.crtc_id);
>>>> +				if (crtc_idx >= 0)
>>>> +					active_crtcs |= 1 << crtc_idx;
>>>> +			}
>>>> +		}
>>>> +
>>>> +found:
>>>> +		crtcs_for_connector &= available_crtcs;
>>>> +		active_crtcs &= available_crtcs;
>>>> +
>>>> +		if (!crtcs_for_connector)
>>>> +			return -ENOENT;
>>>> +
>>>> +		if (active_crtcs)
>>>> +			crtc_idx = ffs(active_crtcs) - 1;
>>>> +		else
>>>> +			crtc_idx = ffs(crtcs_for_connector) - 1;
>>>> +
>>>> +		if (crtc_idx >= client->num_crtcs)
>>>> +			return -EINVAL;
>>>> +
>>>> +		display->connectors[i]->crtc_id = client->crtcs[crtc_idx];
>>>> +		available_crtcs &= ~BIT(crtc_idx);
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_display_commit_mode - Commit a mode to the crtc(s)
>>>> + * @display: Client display
>>>> + * @fb_id: Framebuffer id
>>>> + * @mode: Video mode
>>>> + *
>>>> + * Returns:
>>>> + * Zero on success, negative error code on failure.
>>>> + */
>>>> +int drm_client_display_commit_mode(struct drm_client_display *display,
>>>> +				   u32 fb_id, struct drm_mode_modeinfo *mode)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	unsigned int num_crtcs = client->num_crtcs;
>>>> +	struct drm_file *file = client->file;
>>>> +	unsigned int *xoffsets = NULL, *yoffsets = NULL;
>>>> +	struct drm_mode_crtc *crtc_reqs, *req;
>>>> +	u32 cloned_conn_ids[2];
>>>> +	unsigned int i;
>>>> +	int idx, ret;
>>>> +
>>>> +	ret = drm_client_display_find_crtcs(display);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	crtc_reqs = kcalloc(num_crtcs, sizeof(*crtc_reqs), GFP_KERNEL);
>>>> +	if (!crtc_reqs)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	for (i = 0; i < num_crtcs; i++)
>>>> +		crtc_reqs[i].crtc_id = client->crtcs[i];
>>>> +
>>>> +	if (drm_client_display_is_tiled(display)) {
>>>> +		/* TODO calculate tile crtc offsets */
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < display->num_connectors; i++) {
>>>> +		idx = drm_client_get_crtc_index(client, display->connectors[i]->crtc_id);
>>>> +		if (idx < 0)
>>>> +			return -ENOENT;
>>>> +
>>>> +		req = &crtc_reqs[idx];
>>>> +
>>>> +		req->fb_id = fb_id;
>>>> +		if (xoffsets) {
>>>> +			req->x = xoffsets[i];
>>>> +			req->y = yoffsets[i];
>>>> +		}
>>>> +		req->mode_valid = 1;
>>>> +		req->mode = *mode;
>>>> +
>>>> +		if (display->cloned) {
>>>> +			cloned_conn_ids[0] = display->connectors[0]->conn_id;
>>>> +			cloned_conn_ids[1] = display->connectors[1]->conn_id;
>>>> +			req->set_connectors_ptr = (unsigned long)(cloned_conn_ids);
>>>> +			req->count_connectors = 2;
>>>> +			break;
>>>> +		}
>>>> +
>>>> +		req->set_connectors_ptr = (unsigned long)(&display->connectors[i]->conn_id);
>>>> +		req->count_connectors = 1;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_crtcs; i++) {
>>>> +		ret = drm_mode_setcrtc(dev, &crtc_reqs[i], file, false);
>>>> +		if (ret)
>>>> +			break;
>>>> +	}
>>>> +
>>>> +	kfree(xoffsets);
>>>> +	kfree(yoffsets);
>>>> +	kfree(crtc_reqs);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_commit_mode);
>>>> +
>>>> +unsigned int drm_client_display_current_fb(struct drm_client_display *display)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	int ret;
>>>> +	struct drm_mode_crtc crtc_req = {
>>>> +		.crtc_id = display->connectors[0]->crtc_id,
>>>> +	};
>>>> +
>>>> +	ret = drm_mode_getcrtc(client->dev, &crtc_req, client->file);
>>>> +	if (ret)
>>>> +		return 0;
>>>> +
>>>> +	return crtc_req.fb_id;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_current_fb);
>>>> +
>>>> +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
>>>> +			     struct drm_clip_rect *clips, unsigned int num_clips)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_mode_fb_dirty_cmd dirty_req = {
>>>> +		.fb_id = fb_id,
>>>> +		.clips_ptr = (unsigned long)clips,
>>>> +		.num_clips = num_clips,
>>>> +	};
>>>> +	int ret;
>>>> +
>>>> +	if (display->no_flushing)
>>>> +		return 0;
>>>> +
>>>> +	ret = drm_mode_dirtyfb(client->dev, &dirty_req, client->file, false);
>>>> +	if (ret == -ENOSYS) {
>>>> +		ret = 0;
>>>> +		display->no_flushing = true;
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_flush);
>>>> +
>>>> +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
>>>> +				 bool event)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_mode_crtc_page_flip_target page_flip_req = {
>>>> +		.crtc_id = display->connectors[0]->crtc_id,
>>>> +		.fb_id = fb_id,
>>>> +	};
>>>> +
>>>> +	if (event)
>>>> +		page_flip_req.flags = DRM_MODE_PAGE_FLIP_EVENT;
>>>> +
>>>> +	return drm_mode_page_flip(client->dev, &page_flip_req, client->file);
>>>> +	/*
>>>> +	 * TODO:
>>>> +	 * Where do we flush on page flip? Should the driver handle that?
>>>> +	 */
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_page_flip);
>>>> +
>>>> +/**
>>>> + * drm_client_framebuffer_create - Create a client framebuffer
>>>> + * @client: DRM client
>>>> + * @mode: Display mode to create a buffer for
>>>> + * @format: Buffer format
>>>> + *
>>>> + * This function creates a &drm_client_buffer which consists of a
>>>> + * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf
>>>> + * exported to aquire a virtual address which is stored in
>>>> + * &drm_client_buffer->vaddr.
>>>> + * Call drm_client_framebuffer_delete() to free the buffer.
>>>> + *
>>>> + * Returns:
>>>> + * Pointer to a client buffer or an error pointer on failure.
>>>> + */
>>>> +struct drm_client_buffer *
>>>> +drm_client_framebuffer_create(struct drm_client_dev *client,
>>>> +			      struct drm_mode_modeinfo *mode, u32 format)
>>>> +{
>>>> +	struct drm_client_buffer *buffer;
>>>> +	int ret;
>>>> +
>>>> +	buffer = drm_client_buffer_create(client, mode->hdisplay,
>>>> +					  mode->vdisplay, format);
>>>> +	if (IS_ERR(buffer))
>>>> +		return buffer;
>>>> +
>>>> +	ret = drm_client_buffer_addfb(buffer, mode);
>>>> +	if (ret) {
>>>> +		drm_client_buffer_delete(buffer);
>>>> +		return ERR_PTR(ret);
>>>> +	}
>>>> +
>>>> +	return buffer;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_framebuffer_create);
>>>> +
>>>> +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
>>>> +{
>>>> +	drm_client_buffer_rmfb(buffer);
>>>> +	drm_client_buffer_delete(buffer);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_framebuffer_delete);
>>>> +
>>>> +struct drm_client_buffer *
>>>> +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
>>>> +			 u32 format)
>>>> +{
>>>> +	struct drm_mode_create_dumb dumb_args = { 0 };
>>>> +	struct drm_prime_handle prime_args = { 0 };
>>>> +	struct drm_client_buffer *buffer;
>>>> +	struct dma_buf *dma_buf;
>>>> +	void *vaddr;
>>>> +	int ret;
>>>> +
>>>> +	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
>>>> +	if (!buffer)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	ret = drm_client_get_file(client);
>>>> +	if (ret)
>>>> +		goto err_free;
>>>> +
>>>> +	buffer->client = client;
>>>> +	buffer->width = width;
>>>> +	buffer->height = height;
>>>> +	buffer->format = format;
>>>> +
>>>> +	dumb_args.width = buffer->width;
>>>> +	dumb_args.height = buffer->height;
>>>> +	dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
>>>> +	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
>>>> +	if (ret)
>>>> +		goto err_put_file;
>>>> +
>>>> +	buffer->handle = dumb_args.handle;
>>>> +	buffer->pitch = dumb_args.pitch;
>>>> +	buffer->size = dumb_args.size;
>>>> +
>>>> +	prime_args.handle = dumb_args.handle;
>>>> +	ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file);
>>>> +	if (ret)
>>>> +		goto err_delete;
>>>> +
>>>> +	dma_buf = dma_buf_get(prime_args.fd);
>>>> +	if (IS_ERR(dma_buf)) {
>>>> +		ret = PTR_ERR(dma_buf);
>>>> +		goto err_delete;
>>>> +	}
>>>> +
>>>> +	buffer->dma_buf = dma_buf;
>>>> +
>>>> +	vaddr = dma_buf_vmap(dma_buf);
>>>> +	if (!vaddr) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err_delete;
>>>> +	}
>>>> +
>>>> +	buffer->vaddr = vaddr;
>>>> +
>>>> +	return buffer;
>>>> +
>>>> +err_delete:
>>>> +	drm_client_buffer_delete(buffer);
>>>> +err_put_file:
>>>> +	drm_client_put_file(client);
>>>> +err_free:
>>>> +	kfree(buffer);
>>>> +
>>>> +	return ERR_PTR(ret);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_buffer_create);
>>>> +
>>>> +void drm_client_buffer_delete(struct drm_client_buffer *buffer)
>>>> +{
>>>> +	if (!buffer)
>>>> +		return;
>>>> +
>>>> +	if (buffer->vaddr)
>>>> +		dma_buf_vunmap(buffer->dma_buf, buffer->vaddr);
>>>> +
>>>> +	if (buffer->dma_buf)
>>>> +		dma_buf_put(buffer->dma_buf);
>>>> +
>>>> +	drm_mode_destroy_dumb(buffer->client->dev, buffer->handle,
>>>> +			      buffer->client->file);
>>>> +	drm_client_put_file(buffer->client);
>>>> +	kfree(buffer);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_buffer_delete);
>>>> +
>>>> +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
>>>> +			    struct drm_mode_modeinfo *mode)
>>>> +{
>>>> +	struct drm_client_dev *client = buffer->client;
>>>> +	struct drm_mode_fb_cmd2 fb_req = { };
>>>> +	unsigned int num_fbs, *fb_ids;
>>>> +	int i, ret;
>>>> +
>>>> +	if (buffer->num_fbs)
>>>> +		return -EINVAL;
>>>> +
>>>> +	if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
>>>> +		return -EINVAL;
>>>> +
>>>> +	num_fbs = buffer->height / mode->vdisplay;
>>>> +	fb_ids = kcalloc(num_fbs, sizeof(*fb_ids), GFP_KERNEL);
>>>> +	if (!fb_ids)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	fb_req.width = mode->hdisplay;
>>>> +	fb_req.height = mode->vdisplay;
>>>> +	fb_req.pixel_format = buffer->format;
>>>> +	fb_req.handles[0] = buffer->handle;
>>>> +	fb_req.pitches[0] = buffer->pitch;
>>>> +
>>>> +	for (i = 0; i < num_fbs; i++) {
>>>> +		fb_req.offsets[0] = i * mode->vdisplay * buffer->pitch;
>>>> +		ret = drm_mode_addfb2(client->dev, &fb_req, client->file,
>>>> +				      client->funcs->name);
>>>> +		if (ret)
>>>> +			goto err_remove;
>>>> +		fb_ids[i] = fb_req.fb_id;
>>>> +	}
>>>> +
>>>> +	buffer->fb_ids = fb_ids;
>>>> +	buffer->num_fbs = num_fbs;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_remove:
>>>> +	for (i--; i >= 0; i--)
>>>> +		drm_mode_rmfb(client->dev, fb_ids[i], client->file);
>>>> +	kfree(fb_ids);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_buffer_addfb);
>>>> +
>>>> +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
>>>> +{
>>>> +	unsigned int i;
>>>> +	int ret;
>>>> +
>>>> +	if (!buffer || !buffer->num_fbs)
>>>> +		return 0;
>>>> +
>>>> +	for (i = 0; i < buffer->num_fbs; i++) {
>>>> +		ret = drm_mode_rmfb(buffer->client->dev, buffer->fb_ids[i],
>>>> +				    buffer->client->file);
>>>> +		if (ret)
>>>> +			DRM_DEV_ERROR(buffer->client->dev->dev,
>>>> +				      "Error removing FB:%u (%d)\n",
>>>> +				      buffer->fb_ids[i], ret);
>>>> +	}
>>>> +
>>>> +	kfree(buffer->fb_ids);
>>>> +	buffer->fb_ids = NULL;
>>>> +	buffer->num_fbs = 0;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_buffer_rmfb);
>>>> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
>>>> index f869de185986..db161337d87c 100644
>>>> --- a/drivers/gpu/drm/drm_drv.c
>>>> +++ b/drivers/gpu/drm/drm_drv.c
>>>> @@ -33,6 +33,7 @@
>>>>    #include <linux/mount.h>
>>>>    #include <linux/slab.h>
>>>> +#include <drm/drm_client.h>
>>>>    #include <drm/drm_drv.h>
>>>>    #include <drm/drmP.h>
>>>> @@ -463,6 +464,7 @@ int drm_dev_init(struct drm_device *dev,
>>>>    	dev->driver = driver;
>>>>    	INIT_LIST_HEAD(&dev->filelist);
>>>> +	INIT_LIST_HEAD(&dev->filelist_internal);
>>>>    	INIT_LIST_HEAD(&dev->ctxlist);
>>>>    	INIT_LIST_HEAD(&dev->vmalist);
>>>>    	INIT_LIST_HEAD(&dev->maplist);
>>>> @@ -787,6 +789,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
>>>>    		 dev->dev ? dev_name(dev->dev) : "virtual device",
>>>>    		 dev->primary->index);
>>>> +	drm_client_dev_register(dev);
>>>> +
>>>>    	goto out_unlock;
>>>>    err_minors:
>>>> @@ -839,6 +843,8 @@ void drm_dev_unregister(struct drm_device *dev)
>>>>    	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
>>>>    	drm_minor_unregister(dev, DRM_MINOR_RENDER);
>>>>    	drm_minor_unregister(dev, DRM_MINOR_CONTROL);
>>>> +
>>>> +	drm_client_dev_unregister(dev);
>>>>    }
>>>>    EXPORT_SYMBOL(drm_dev_unregister);
>>>> diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
>>>> index 55505378df47..bcc688e58776 100644
>>>> --- a/drivers/gpu/drm/drm_file.c
>>>> +++ b/drivers/gpu/drm/drm_file.c
>>>> @@ -35,6 +35,7 @@
>>>>    #include <linux/slab.h>
>>>>    #include <linux/module.h>
>>>> +#include <drm/drm_client.h>
>>>>    #include <drm/drm_file.h>
>>>>    #include <drm/drmP.h>
>>>> @@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
>>>>    	if (drm_core_check_feature(dev, DRIVER_LEGACY))
>>>>    		drm_legacy_dev_reinit(dev);
>>>> +
>>>> +	drm_client_dev_lastclose(dev);
>>>>    }
>>>>    /**
>>>> diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c
>>>> index 2d1643bdae78..5d2a6c6717f5 100644
>>>> --- a/drivers/gpu/drm/drm_probe_helper.c
>>>> +++ b/drivers/gpu/drm/drm_probe_helper.c
>>>> @@ -33,6 +33,7 @@
>>>>    #include <linux/moduleparam.h>
>>>>    #include <drm/drmP.h>
>>>> +#include <drm/drm_client.h>
>>>>    #include <drm/drm_crtc.h>
>>>>    #include <drm/drm_fourcc.h>
>>>>    #include <drm/drm_crtc_helper.h>
>>>> @@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
>>>>    	drm_sysfs_hotplug_event(dev);
>>>>    	if (dev->mode_config.funcs->output_poll_changed)
>>>>    		dev->mode_config.funcs->output_poll_changed(dev);
>>>> +
>>>> +	drm_client_dev_hotplug(dev);
>>>>    }
>>>>    EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
>>>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>>>> new file mode 100644
>>>> index 000000000000..88f6f87919c5
>>>> --- /dev/null
>>>> +++ b/include/drm/drm_client.h
>>>> @@ -0,0 +1,192 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>> +
>>>> +#include <linux/mutex.h>
>>>> +
>>>> +struct dma_buf;
>>>> +struct drm_clip_rect;
>>>> +struct drm_device;
>>>> +struct drm_file;
>>>> +struct drm_mode_modeinfo;
>>>> +
>>>> +struct drm_client_dev;
>>>> +
>>>> +/**
>>>> + * struct drm_client_funcs - DRM client  callbacks
>>>> + */
>>>> +struct drm_client_funcs {
>>>> +	/**
>>>> +	 * @name:
>>>> +	 *
>>>> +	 * Name of the client.
>>>> +	 */
>>>> +	const char *name;
>>>> +
>>>> +	/**
>>>> +	 * @new:
>>>> +	 *
>>>> +	 * Called when a client or a &drm_device is registered.
>>>> +	 * If the callback returns anything but zero, then this client instance
>>>> +	 * is dropped.
>>>> +	 *
>>>> +	 * This callback is mandatory.
>>>> +	 */
>>>> +	int (*new)(struct drm_client_dev *client);
>>>> +
>>>> +	/**
>>>> +	 * @remove:
>>>> +	 *
>>>> +	 * Called when a &drm_device is unregistered or the client is
>>>> +	 * unregistered. If zero is returned drm_client_free() is called
>>>> +	 * automatically. If the client can't drop it's resources it should
>>>> +	 * return non-zero and call drm_client_free() later.
>>>> +	 *
>>>> +	 * This callback is optional.
>>>> +	 */
>>>> +	int (*remove)(struct drm_client_dev *client);
>>>> +
>>>> +	/**
>>>> +	 * @lastclose:
>>>> +	 *
>>>> +	 * Called on drm_lastclose(). The first client instance in the list
>>>> +	 * that returns zero gets the privilege to restore and no more clients
>>>> +	 * are called.
>>>> +	 *
>>>> +	 * This callback is optional.
>>>> +	 */
>>>> +	int (*lastclose)(struct drm_client_dev *client);
>>>> +
>>>> +	/**
>>>> +	 * @hotplug:
>>>> +	 *
>>>> +	 * Called on drm_kms_helper_hotplug_event().
>>>> +	 *
>>>> +	 * This callback is optional.
>>>> +	 */
>>>> +	int (*hotplug)(struct drm_client_dev *client);
>>>> +
>>>> +// TODO
>>>> +//	void (*suspend)(struct drm_client_dev *client);
>>>> +//	void (*resume)(struct drm_client_dev *client);
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct drm_client_dev - DRM client instance
>>>> + */
>>>> +struct drm_client_dev {
>>>> +	struct list_head list;
>>>> +	struct drm_device *dev;
>>>> +	const struct drm_client_funcs *funcs;
>>>> +	struct mutex lock;
>>>> +	struct drm_file *file;
>>>> +	unsigned int file_ref_count;
>>>> +	u32 *crtcs;
>>>> +	unsigned int num_crtcs;
>>>> +	u32 min_width;
>>>> +	u32 max_width;
>>>> +	u32 min_height;
>>>> +	u32 max_height;
>>>> +	void *private;
>>>> +};
>>>> +
>>>> +void drm_client_free(struct drm_client_dev *client);
>>>> +int drm_client_register(const struct drm_client_funcs *funcs);
>>>> +void drm_client_unregister(const struct drm_client_funcs *funcs);
>>>> +
>>>> +void drm_client_dev_register(struct drm_device *dev);
>>>> +void drm_client_dev_unregister(struct drm_device *dev);
>>>> +void drm_client_dev_hotplug(struct drm_device *dev);
>>>> +void drm_client_dev_lastclose(struct drm_device *dev);
>>>> +
>>>> +int drm_client_get_file(struct drm_client_dev *client);
>>>> +void drm_client_put_file(struct drm_client_dev *client);
>>>> +struct drm_event *
>>>> +drm_client_read_event(struct drm_client_dev *client, bool block);
>>>> +
>>>> +struct drm_client_connector {
>>>> +	unsigned int conn_id;
>>>> +	unsigned int status;
>>>> +	unsigned int crtc_id;
>>>> +	struct drm_mode_modeinfo *modes;
>>>> +	unsigned int num_modes;
>>>> +	bool has_tile;
>>>> +	int tile_group;
>>>> +	u8 tile_h_loc, tile_v_loc;
>>>> +};
>>>> +
>>>> +struct drm_client_display {
>>>> +	struct drm_client_dev *client;
>>>> +
>>>> +	struct drm_client_connector **connectors;
>>>> +	unsigned int num_connectors;
>>>> +
>>>> +	struct mutex modes_lock;
>>>> +	struct drm_mode_modeinfo *modes;
>>>> +	unsigned int num_modes;
>>>> +
>>>> +	bool cloned;
>>>> +	bool no_flushing;
>>>> +};
>>>> +
>>>> +void drm_client_display_free(struct drm_client_display *display);
>>>> +struct drm_client_display *
>>>> +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict);
>>>> +
>>>> +int drm_client_display_update_modes(struct drm_client_display *display,
>>>> +				    bool *mode_changed);
>>>> +
>>>> +static inline bool
>>>> +drm_client_display_is_tiled(struct drm_client_display *display)
>>>> +{
>>>> +	return !display->cloned && display->num_connectors > 1;
>>>> +}
>>>> +
>>>> +int drm_client_display_dpms(struct drm_client_display *display, int mode);
>>>> +int drm_client_display_wait_vblank(struct drm_client_display *display);
>>>> +
>>>> +struct drm_mode_modeinfo *
>>>> +drm_client_display_first_mode(struct drm_client_display *display);
>>>> +struct drm_mode_modeinfo *
>>>> +drm_client_display_next_mode(struct drm_client_display *display,
>>>> +			     struct drm_mode_modeinfo *mode);
>>>> +
>>>> +#define drm_client_display_for_each_mode(display, mode) \
>>>> +	for (mode = drm_client_display_first_mode(display); mode; \
>>>> +	     mode = drm_client_display_next_mode(display, mode))
>>>> +
>>>> +unsigned int
>>>> +drm_client_display_preferred_depth(struct drm_client_display *display);
>>>> +
>>>> +int drm_client_display_commit_mode(struct drm_client_display *display,
>>>> +				   u32 fb_id, struct drm_mode_modeinfo *mode);
>>>> +unsigned int drm_client_display_current_fb(struct drm_client_display *display);
>>>> +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
>>>> +			     struct drm_clip_rect *clips, unsigned int num_clips);
>>>> +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
>>>> +				 bool event);
>>>> +
>>>> +struct drm_client_buffer {
>>>> +	struct drm_client_dev *client;
>>>> +	u32 width;
>>>> +	u32 height;
>>>> +	u32 format;
>>>> +	u32 handle;
>>>> +	u32 pitch;
>>>> +	u64 size;
>>>> +	struct dma_buf *dma_buf;
>>>> +	void *vaddr;
>>>> +
>>>> +	unsigned int *fb_ids;
>>>> +	unsigned int num_fbs;
>>>> +};
>>>> +
>>>> +struct drm_client_buffer *
>>>> +drm_client_framebuffer_create(struct drm_client_dev *client,
>>>> +			      struct drm_mode_modeinfo *mode, u32 format);
>>>> +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
>>>> +struct drm_client_buffer *
>>>> +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
>>>> +			 u32 format);
>>>> +void drm_client_buffer_delete(struct drm_client_buffer *buffer);
>>>> +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
>>>> +			    struct drm_mode_modeinfo *mode);
>>>> +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer);
>>>> diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
>>>> index 7c4fa32f3fc6..32dfed3d5a86 100644
>>>> --- a/include/drm/drm_device.h
>>>> +++ b/include/drm/drm_device.h
>>>> @@ -67,6 +67,7 @@ struct drm_device {
>>>>    	struct mutex filelist_mutex;
>>>>    	struct list_head filelist;
>>>> +	struct list_head filelist_internal;
>>>>    	/** \name Memory management */
>>>>    	/*@{ */
>>>> diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
>>>> index 5176c3797680..39af8a4be7b3 100644
>>>> --- a/include/drm/drm_file.h
>>>> +++ b/include/drm/drm_file.h
>>>> @@ -248,6 +248,13 @@ struct drm_file {
>>>>    	 */
>>>>    	void *driver_priv;
>>>> +	/**
>>>> +	 * @user_priv:
>>>> +	 *
>>>> +	 * Optional pointer for user private data. Useful for in-kernel clients.
>>>> +	 */
>>>> +	void *user_priv;
>>>> +
>>>>    	/**
>>>>    	 * @fbs:
>>>>    	 *
>>>> -- 
>>>> 2.15.1
>>>>

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

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

* Re: [RFC v3 09/12] drm: Add API for in-kernel clients
  2018-03-12 20:21         ` Noralf Trønnes
@ 2018-03-13 15:11           ` Daniel Vetter
  0 siblings, 0 replies; 26+ messages in thread
From: Daniel Vetter @ 2018-03-13 15:11 UTC (permalink / raw)
  To: Noralf Tr??nnes
  Cc: daniel.vetter, intel-gfx, dri-devel, laurent.pinchart, dh.herrmann

On Mon, Mar 12, 2018 at 09:21:40PM +0100, Noralf Tr??nnes wrote:
> 
> Den 12.03.2018 17.51, skrev Daniel Vetter:
> > On Thu, Mar 08, 2018 at 06:12:11PM +0100, Noralf Tr??nnes wrote:
> > > Den 06.03.2018 09.56, skrev Daniel Vetter:
> > > > On Thu, Feb 22, 2018 at 09:06:50PM +0100, Noralf Tr??nnes wrote:
> > > > > This adds an API for writing in-kernel clients.
> > > > > 
> > > > > TODO:
> > > > > - Flesh out and complete documentation.
> > > > > - Cloned displays is not tested.
> > > > > - Complete tiled display support and test it.
> > > > > - Test plug/unplug different monitors.
> > > > > - A runtime knob to prevent clients from attaching for debugging purposes.
> > > > > - Maybe a way to unbind individual client instances.
> > > > > - Maybe take the sysrq support in drm_fb_helper and move it here somehow.
> > > > > - Add suspend/resume callbacks.
> > > > >     Does anyone know why fbdev requires suspend/resume?
> > > > > 
> > > > > Signed-off-by: Noralf Tr??nnes <noralf@tronnes.org>
> > > > The core client api I like. Some of the opens I'm seeing:
> > > > 
> > > > - If we go with using the internal kms api directly instead of IOCTL
> > > >     wrappers then a huge pile of the functions you have here aren't needed
> > > >     (e.g. all the event stuff we can just directly use vblank events instead
> > > >     of all the wrapping). I'm leaning ever more into that direction, since
> > > >     much less code to add.
> > > Looking at drm_fb_helper once again I now see an opportunity to simplify
> > > the modesetting code by nuking drm_fb_helper_connector and stop
> > > maintaining an array of connectors. It looks to be possible to just
> > > create an array temporarily in drm_setup_crtcs() for the duration of the
> > > function. The connectors we care about are ref counted and attached to
> > > modesets. This would remove the need for drm_fb_helper_add_one_connector().
> > > 
> > > So I might be able to do struct drm_fb_helper_crtc -> drm_client_crtc
> > > and let the client API take over drm_setup_crtcs(). I'll give it a try.
> > I'm more wondering why we need drm_client_crtc at all, why is drm_crtc not
> > good enough. Or maybe I'm missing something. Imo ioctl wrappers should be
> > the exception where we really, really need them (because the backend of
> > the ioctl isn't implemented in a generic way, e.g. dumb buffers), not for
> > stuff where we already have a perfectly useable in-kernel abi (anything
> > related to modesetting).
> 
> I was talking about moving the modesetting code from drm_fb_helper.c
> to drm_client.c, which meant moving 'struct drm_fb_helper_crtc' as well.
> 
> struct drm_fb_helper_crtc {
>     struct drm_mode_set mode_set;
>     struct drm_display_mode *desired_mode;
>     int x, y;
> };
> 
> But maybe we can get rid of that struct as well.
> The info it contains is also available in drm_mode_set:

drm_mode_set is the uapi struct for the legacy ioctl, but yeah I guess we
can recycle that. And I didn't understand what you wanted to do,
extracting the fbdev helper might make sense. But most of the code seems
fbdev specific, so not sure how much use that would be in other clients.

There's also a bit the problem that it's crufty (it started out as
something that was fully integrated into drivers and digging around in
driver internals directly, hence also the convoluted allocation logic).
-Daniel

> 
> static void drm_setup_crtcs(struct drm_fb_helper *fb_helper,
>                 u32 width, u32 height)
> {
> ...
>     drm_fb_helper_for_each_connector(fb_helper, i) {
>         struct drm_display_mode *mode = modes[i];
>         struct drm_fb_helper_crtc *fb_crtc = crtcs[i];
>         struct drm_fb_offset *offset = &offsets[i];
> 
>         if (mode && fb_crtc) {
>             struct drm_mode_set *modeset = &fb_crtc->mode_set;
> 
>             fb_crtc->desired_mode = mode;
>             fb_crtc->x = offset->x;
>             fb_crtc->y = offset->y;
>             modeset->mode = drm_mode_duplicate(dev,
>                                fb_crtc->desired_mode);
>             modeset->x = offset->x;
>             modeset->y = offset->y;
>         }
>     }
> 
> I took the hint about my ioctl wrappers :-)
> 
> Noralf.
> 
> > 
> > And in a way the ioctl wrappers wouldn't really be ioctl wrappers
> > conceptually, but simple share some of the same code with the ioctl call
> > chain. The idea is to provide some minimal wrappar around the ->dumb*
> > callbacks.
> > 
> > Anything else is not needed, I think.
> > 
> > > There is one challenge I see upfront and that's the i915 fb_helper
> > > callback in drm_setup_crtcs().
> > > 
> > > > - The register/unregister model needs more thought. Allowing both clients
> > > >     to register whenever they want to, and drm_device instances to come and
> > > >     go is what fbcon has done, and the resulting locking is a horror show.
> > > > 
> > > >     I think if we require that all in-kernel drm_clients are registers when
> > > >     loading drm.ko (and enabled/disabled only per module options and
> > > >     Kconfig), then we can throw out all the locking. That avoids a lot of
> > > >     the headaches.
> > > > 
> > > >     2nd, if the list of clients is static over the lifetime of drm.ko, we
> > > >     also don't need to iterate existing drivers. Which avoids me having to
> > > >     review the iterator patch (that's the other aspect where fbcon totally
> > > >     falls over and essentially just ignores a bunch of races).
> > > Are you talking about linking the clients into drm.ko?
> > > 
> > > drivers/gpu/drm/Makefile:
> > > 
> > > drm-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += client/drm_bootsplash.o
> > > 
> > > drivers/gpu/drm/drm_drv.c:
> > > 
> > > ??static int __init drm_core_init(void)
> > > ??{
> > > +?????? drm_bootsplash_register();
> > > +?????? drm_fbdev_register();
> > > ??}
> > > 
> > > drivers/gpu/drm/drm_internal.h:
> > > 
> > > #ifdef DRM_CLIENT_BOOTSPLASH
> > > void drm_bootsplash_register(void);
> > > #else
> > > static inline void drm_bootsplash_register(void)
> > > {
> > > }
> > > #endif
> > > 
> > > drivers/gpu/drm/client/drm_bootsplash.c:
> > > 
> > > static const struct drm_client_funcs drm_bootsplash_funcs = {
> > > ?????? .name?????? ?????? = "drm_bootsplash",
> > > ?????? .new?????? ?????? = drm_bootsplash_new,
> > > ?????? .remove?????? ?????? = drm_bootsplash_remove,
> > > ?????? .hotplug?????? = drm_bootsplash_hotplug,
> > > };
> > > 
> > > void drm_bootsplash_register(void)
> > > {
> > > ?????? drm_client_register(&drm_bootsplash_funcs);
> > > }
> > > 
> > > drivers/gpu/drm/drm_client.c:
> > > 
> > > static LIST_HEAD(drm_client_funcs_list);
> > > 
> > > void drm_client_register(const struct drm_client_funcs *funcs)
> > > {
> > > ?????? struct drm_client_funcs_entry *funcs_entry;
> > > 
> > > ?????? funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
> > > ?????? if (!funcs_entry) {
> > > ?????? ?????? DRM_ERROR("Failed to register: %s\n", funcs->name);
> > > ?????? ?????? return;
> > > ?????? }
> > > 
> > > ?????? funcs_entry->funcs = funcs;
> > > 
> > > ?????? list_add(&funcs_entry->list, &drm_client_funcs_list);
> > > 
> > > ?????? DRM_DEBUG_KMS("%s\n", funcs->name);
> > > }
> > > 
> > > 
> > > And each client having a runtime enable/disable knob:
> > > 
> > > drivers/gpu/drm/client/drm_bootsplash.c:
> > > 
> > > static bool drm_bootsplash_enabled = true;
> > > module_param_named(bootsplash_enabled, drm_bootsplash_enabled, bool, 0600);
> > > MODULE_PARM_DESC(bootsplash_enabled, "Enable bootsplash client
> > > [default=true]");
> > 
> > Yup, pretty much.
> > 
> > > Simple USB Display
> > > A few months back while looking at the udl shmem code, I got the idea
> > > that I could turn a Raspberry Pi Zero into a $5 USB to HDMI/DSI/DPI/DBI/TV
> > > adapter. The host side would be a simple tinydrm driver using the kernel
> > > compression lib to speed up transfers. The gadget/device side would be a
> > > userspace app decompressing the buffer into an exported dumb buffer.
> > > 
> > > While working with this client API I realized that I could use it and
> > > write a kernel gadget driver instead avoiding the challenge of going
> > > back and forth to userspace with the framebuffer. For such a client I
> > > would have preferred it to be a loadable module not linked into drm.ko
> > > to increase the chance that distributions would enable it.
> > I think that we can do as a loadable client, since you probably want some
> > configfs interface to define which drm driver it should control. The
> > reason behind the static list of built-in clients is purely for the
> > auto-register stuff that we want for fbdev emulation and other things.
> > Auto-registration where both sides can be loaded in any order is real pain
> > wrt locking. Explicit registration where you can load both sides is
> > totally fine and I think would cover your usb gadget display driver
> > use-case.
> > 
> > btw usb gadget driver to drive drm kms drivers sounds like a really cool
> > thing.
> > -Daniel
> > 
> > > Noralf.
> > > 
> > > > > ---
> > > > >    drivers/gpu/drm/Kconfig             |    2 +
> > > > >    drivers/gpu/drm/Makefile            |    3 +-
> > > > >    drivers/gpu/drm/client/Kconfig      |    4 +
> > > > >    drivers/gpu/drm/client/Makefile     |    1 +
> > > > >    drivers/gpu/drm/client/drm_client.c | 1612 +++++++++++++++++++++++++++++++++++
> > > > I'd move this into main drm/ directory, it's fairly core stuff.
> > > > 
> > > > >    drivers/gpu/drm/drm_drv.c           |    6 +
> > > > >    drivers/gpu/drm/drm_file.c          |    3 +
> > > > >    drivers/gpu/drm/drm_probe_helper.c  |    3 +
> > > > >    include/drm/drm_client.h            |  192 +++++
> > > > >    include/drm/drm_device.h            |    1 +
> > > > >    include/drm/drm_file.h              |    7 +
> > > > >    11 files changed, 1833 insertions(+), 1 deletion(-)
> > > > >    create mode 100644 drivers/gpu/drm/client/Kconfig
> > > > >    create mode 100644 drivers/gpu/drm/client/Makefile
> > > > >    create mode 100644 drivers/gpu/drm/client/drm_client.c
> > > > >    create mode 100644 include/drm/drm_client.h
> > > > > 
> > > > > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> > > > > index deeefa7a1773..d4ae15f9ee9f 100644
> > > > > --- a/drivers/gpu/drm/Kconfig
> > > > > +++ b/drivers/gpu/drm/Kconfig
> > > > > @@ -154,6 +154,8 @@ config DRM_SCHED
> > > > >    	tristate
> > > > >    	depends on DRM
> > > > > +source "drivers/gpu/drm/client/Kconfig"
> > > > > +
> > > > >    source "drivers/gpu/drm/i2c/Kconfig"
> > > > >    source "drivers/gpu/drm/arm/Kconfig"
> > > > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > > > index 50093ff4479b..8e06dc7eeca1 100644
> > > > > --- a/drivers/gpu/drm/Makefile
> > > > > +++ b/drivers/gpu/drm/Makefile
> > > > > @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
> > > > >    		drm_encoder.o drm_mode_object.o drm_property.o \
> > > > >    		drm_plane.o drm_color_mgmt.o drm_print.o \
> > > > >    		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> > > > > -		drm_syncobj.o drm_lease.o
> > > > > +		drm_syncobj.o drm_lease.o client/drm_client.o
> > > > >    drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
> > > > >    drm-$(CONFIG_DRM_VM) += drm_vm.o
> > > > > @@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
> > > > >    obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
> > > > >    obj-$(CONFIG_DRM_PL111) += pl111/
> > > > >    obj-$(CONFIG_DRM_TVE200) += tve200/
> > > > > +obj-y			+= client/
> > > > > diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
> > > > > new file mode 100644
> > > > > index 000000000000..4bb8e4655ff7
> > > > > --- /dev/null
> > > > > +++ b/drivers/gpu/drm/client/Kconfig
> > > > > @@ -0,0 +1,4 @@
> > > > > +menu "DRM Clients"
> > > > > +	depends on DRM
> > > > > +
> > > > > +endmenu
> > > > > diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
> > > > > new file mode 100644
> > > > > index 000000000000..f66554cd5c45
> > > > > --- /dev/null
> > > > > +++ b/drivers/gpu/drm/client/Makefile
> > > > > @@ -0,0 +1 @@
> > > > > +# SPDX-License-Identifier: GPL-2.0
> > > > > diff --git a/drivers/gpu/drm/client/drm_client.c b/drivers/gpu/drm/client/drm_client.c
> > > > > new file mode 100644
> > > > > index 000000000000..a633bf747316
> > > > > --- /dev/null
> > > > > +++ b/drivers/gpu/drm/client/drm_client.c
> > > > > @@ -0,0 +1,1612 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0
> > > > > +// Copyright 2018 Noralf Tr??nnes
> > > > > +
> > > > > +#include <linux/dma-buf.h>
> > > > > +#include <linux/list.h>
> > > > > +#include <linux/mutex.h>
> > > > > +#include <linux/module.h>
> > > > > +#include <linux/slab.h>
> > > > > +
> > > > > +#include <drm/drm_client.h>
> > > > > +#include <drm/drm_connector.h>
> > > > > +#include <drm/drm_drv.h>
> > > > > +#include <drm/drm_file.h>
> > > > > +#include <drm/drm_ioctl.h>
> > > > > +#include <drm/drmP.h>
> > > > > +
> > > > > +#include "drm_crtc_internal.h"
> > > > > +#include "drm_internal.h"
> > > > > +
> > > > > +struct drm_client_funcs_entry {
> > > > > +	struct list_head list;
> > > > > +	const struct drm_client_funcs *funcs;
> > > > > +};
> > > > > +
> > > > > +static LIST_HEAD(drm_client_list);
> > > > I think the client list itself should be on the drm_device, not in one
> > > > global list that mixes up all the clients of all the drm_devices.
> > > > 
> > > > I'll skip reviewing details since we have a bunch of high-level questions
> > > > to figure out first.
> > > > -Daniel
> > > > 
> > > > > +static LIST_HEAD(drm_client_funcs_list);
> > > > > +static DEFINE_MUTEX(drm_client_list_lock);
> > > > > +
> > > > > +static void drm_client_new(struct drm_device *dev,
> > > > > +			   const struct drm_client_funcs *funcs)
> > > > > +{
> > > > > +	struct drm_client_dev *client;
> > > > > +	int ret;
> > > > > +
> > > > > +	lockdep_assert_held(&drm_client_list_lock);
> > > > > +
> > > > > +	client = kzalloc(sizeof(*client), GFP_KERNEL);
> > > > > +	if (!client)
> > > > > +		return;
> > > > > +
> > > > > +	mutex_init(&client->lock);
> > > > > +	client->dev = dev;
> > > > > +	client->funcs = funcs;
> > > > > +
> > > > > +	ret = funcs->new(client);
> > > > > +	DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", funcs->name, ret);
> > > > > +	if (ret) {
> > > > > +		drm_client_free(client);
> > > > > +		return;
> > > > > +	}
> > > > > +
> > > > > +	list_add(&client->list, &drm_client_list);
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_client_free - Free DRM client resources
> > > > > + * @client: DRM client
> > > > > + *
> > > > > + * This is called automatically on client removal unless the client returns
> > > > > + * non-zero in the &drm_client_funcs->remove callback. The fbdev client does
> > > > > + * this when it can't close &drm_file because userspace has an open fd.
> > > > > + */
> > > > > +void drm_client_free(struct drm_client_dev *client)
> > > > > +{
> > > > > +	DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
> > > > > +	if (WARN_ON(client->file)) {
> > > > > +		client->file_ref_count = 1;
> > > > > +		drm_client_put_file(client);
> > > > > +	}
> > > > > +	mutex_destroy(&client->lock);
> > > > > +	kfree(client->crtcs);
> > > > > +	kfree(client);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_free);
> > > > > +
> > > > > +static void drm_client_remove(struct drm_client_dev *client)
> > > > > +{
> > > > > +	lockdep_assert_held(&drm_client_list_lock);
> > > > > +
> > > > > +	list_del(&client->list);
> > > > > +
> > > > > +	if (!client->funcs->remove || !client->funcs->remove(client))
> > > > > +		drm_client_free(client);
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_client_register - Register a DRM client
> > > > > + * @funcs: Client callbacks
> > > > > + *
> > > > > + * Returns:
> > > > > + * Zero on success, negative error code on failure.
> > > > > + */
> > > > > +int drm_client_register(const struct drm_client_funcs *funcs)
> > > > > +{
> > > > > +	struct drm_client_funcs_entry *funcs_entry;
> > > > > +	struct drm_device_list_iter iter;
> > > > > +	struct drm_device *dev;
> > > > > +
> > > > > +	funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
> > > > > +	if (!funcs_entry)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	funcs_entry->funcs = funcs;
> > > > > +
> > > > > +	mutex_lock(&drm_global_mutex);
> > > > > +	mutex_lock(&drm_client_list_lock);
> > > > > +
> > > > > +	drm_device_list_iter_begin(&iter);
> > > > > +	drm_for_each_device_iter(dev, &iter)
> > > > > +		if (drm_core_check_feature(dev, DRIVER_MODESET))
> > > > > +			drm_client_new(dev, funcs);
> > > > > +	drm_device_list_iter_end(&iter);
> > > > > +
> > > > > +	list_add(&funcs_entry->list, &drm_client_funcs_list);
> > > > > +
> > > > > +	mutex_unlock(&drm_client_list_lock);
> > > > > +	mutex_unlock(&drm_global_mutex);
> > > > > +
> > > > > +	DRM_DEBUG_KMS("%s\n", funcs->name);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_register);
> > > > > +
> > > > > +/**
> > > > > + * drm_client_unregister - Unregister a DRM client
> > > > > + * @funcs: Client callbacks
> > > > > + */
> > > > > +void drm_client_unregister(const struct drm_client_funcs *funcs)
> > > > > +{
> > > > > +	struct drm_client_funcs_entry *funcs_entry;
> > > > > +	struct drm_client_dev *client, *tmp;
> > > > > +
> > > > > +	mutex_lock(&drm_client_list_lock);
> > > > > +
> > > > > +	list_for_each_entry_safe(client, tmp, &drm_client_list, list) {
> > > > > +		if (client->funcs == funcs)
> > > > > +			drm_client_remove(client);
> > > > > +	}
> > > > > +
> > > > > +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list) {
> > > > > +		if (funcs_entry->funcs == funcs) {
> > > > > +			list_del(&funcs_entry->list);
> > > > > +			kfree(funcs_entry);
> > > > > +			break;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	mutex_unlock(&drm_client_list_lock);
> > > > > +
> > > > > +	DRM_DEBUG_KMS("%s\n", funcs->name);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_unregister);
> > > > > +
> > > > > +void drm_client_dev_register(struct drm_device *dev)
> > > > > +{
> > > > > +	struct drm_client_funcs_entry *funcs_entry;
> > > > > +
> > > > > +	/*
> > > > > +	 * Minors are created at the beginning of drm_dev_register(), but can
> > > > > +	 * be removed again if the function fails. Since we iterate DRM devices
> > > > > +	 * by walking DRM minors, we need to stay under this lock.
> > > > > +	 */
> > > > > +	lockdep_assert_held(&drm_global_mutex);
> > > > > +
> > > > > +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> > > > > +		return;
> > > > > +
> > > > > +	mutex_lock(&drm_client_list_lock);
> > > > > +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list)
> > > > > +		drm_client_new(dev, funcs_entry->funcs);
> > > > > +	mutex_unlock(&drm_client_list_lock);
> > > > > +}
> > > > > +
> > > > > +void drm_client_dev_unregister(struct drm_device *dev)
> > > > > +{
> > > > > +	struct drm_client_dev *client, *tmp;
> > > > > +
> > > > > +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> > > > > +		return;
> > > > > +
> > > > > +	mutex_lock(&drm_client_list_lock);
> > > > > +	list_for_each_entry_safe(client, tmp, &drm_client_list, list)
> > > > > +		if (client->dev == dev)
> > > > > +			drm_client_remove(client);
> > > > > +	mutex_unlock(&drm_client_list_lock);
> > > > > +}
> > > > > +
> > > > > +void drm_client_dev_hotplug(struct drm_device *dev)
> > > > > +{
> > > > > +	struct drm_client_dev *client;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> > > > > +		return;
> > > > > +
> > > > > +	mutex_lock(&drm_client_list_lock);
> > > > > +	list_for_each_entry(client, &drm_client_list, list)
> > > > > +		if (client->dev == dev && client->funcs->hotplug) {
> > > > > +			ret = client->funcs->hotplug(client);
> > > > > +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
> > > > > +					  client->funcs->name, ret);
> > > > > +		}
> > > > > +	mutex_unlock(&drm_client_list_lock);
> > > > > +}
> > > > > +
> > > > > +void drm_client_dev_lastclose(struct drm_device *dev)
> > > > > +{
> > > > > +	struct drm_client_dev *client;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
> > > > > +		return;
> > > > > +
> > > > > +	mutex_lock(&drm_client_list_lock);
> > > > > +	list_for_each_entry(client, &drm_client_list, list)
> > > > > +		if (client->dev == dev && client->funcs->lastclose) {
> > > > > +			ret = client->funcs->lastclose(client);
> > > > > +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
> > > > > +					  client->funcs->name, ret);
> > > > > +		}
> > > > > +	mutex_unlock(&drm_client_list_lock);
> > > > > +}
> > > > > +
> > > > > +/* Get static info */
> > > > > +static int drm_client_init(struct drm_client_dev *client, struct drm_file *file)
> > > > > +{
> > > > > +	struct drm_mode_card_res card_res = {};
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +	u32 *crtcs;
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = drm_mode_getresources(dev, &card_res, file, false);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +	if (!card_res.count_crtcs)
> > > > > +		return -ENOENT;
> > > > > +
> > > > > +	crtcs = kmalloc_array(card_res.count_crtcs, sizeof(*crtcs), GFP_KERNEL);
> > > > > +	if (!crtcs)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	card_res.count_fbs = 0;
> > > > > +	card_res.count_connectors = 0;
> > > > > +	card_res.count_encoders = 0;
> > > > > +	card_res.crtc_id_ptr = (unsigned long)crtcs;
> > > > > +
> > > > > +	ret = drm_mode_getresources(dev, &card_res, file, false);
> > > > > +	if (ret) {
> > > > > +		kfree(crtcs);
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	client->crtcs = crtcs;
> > > > > +	client->num_crtcs = card_res.count_crtcs;
> > > > > +	client->min_width = card_res.min_width;
> > > > > +	client->max_width = card_res.max_width;
> > > > > +	client->min_height = card_res.min_height;
> > > > > +	client->max_height = card_res.max_height;
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_client_get_file - Get a DRM file
> > > > > + * @client: DRM client
> > > > > + *
> > > > > + * This function makes sure the client has a &drm_file available. The client
> > > > > + * doesn't normally need to call this, since all client functions that depends
> > > > > + * on a DRM file will call it. A matching call to drm_client_put_file() is
> > > > > + * necessary.
> > > > > + *
> > > > > + * The reason for not opening a DRM file when a @client is created is because
> > > > > + * we have to take a ref on the driver module due to &drm_driver->postclose
> > > > > + * being called in drm_file_free(). Having a DRM file open for the lifetime of
> > > > > + * the client instance would block driver module unload.
> > > > > + *
> > > > > + * Returns:
> > > > > + * Zero on success, negative error code on failure.
> > > > > + */
> > > > > +int drm_client_get_file(struct drm_client_dev *client)
> > > > > +{
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +	struct drm_file *file;
> > > > > +	int ret = 0;
> > > > > +
> > > > > +	mutex_lock(&client->lock);
> > > > > +
> > > > > +	if (client->file_ref_count++) {
> > > > > +		mutex_unlock(&client->lock);
> > > > > +		return 0;
> > > > > +	}
> > > > > +
> > > > > +	if (!try_module_get(dev->driver->fops->owner)) {
> > > > > +		ret = -ENODEV;
> > > > > +		goto err_unlock;
> > > > > +	}
> > > > > +
> > > > > +	drm_dev_get(dev);
> > > > > +
> > > > > +	file = drm_file_alloc(dev->primary);
> > > > > +	if (IS_ERR(file)) {
> > > > > +		ret = PTR_ERR(file);
> > > > > +		goto err_put;
> > > > > +	}
> > > > > +
> > > > > +	if (!client->crtcs) {
> > > > > +		ret = drm_client_init(client, file);
> > > > > +		if (ret)
> > > > > +			goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	mutex_lock(&dev->filelist_mutex);
> > > > > +	list_add(&file->lhead, &dev->filelist_internal);
> > > > > +	mutex_unlock(&dev->filelist_mutex);
> > > > > +
> > > > > +	client->file = file;
> > > > > +
> > > > > +	mutex_unlock(&client->lock);
> > > > > +
> > > > > +	return 0;
> > > > > +
> > > > > +err_free:
> > > > > +	drm_file_free(file);
> > > > > +err_put:
> > > > > +	drm_dev_put(dev);
> > > > > +	module_put(dev->driver->fops->owner);
> > > > > +err_unlock:
> > > > > +	client->file_ref_count = 0;
> > > > > +	mutex_unlock(&client->lock);
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_get_file);
> > > > > +
> > > > > +void drm_client_put_file(struct drm_client_dev *client)
> > > > > +{
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +
> > > > > +	if (!client)
> > > > > +		return;
> > > > > +
> > > > > +	mutex_lock(&client->lock);
> > > > > +
> > > > > +	if (WARN_ON(!client->file_ref_count))
> > > > > +		goto out_unlock;
> > > > > +
> > > > > +	if (--client->file_ref_count)
> > > > > +		goto out_unlock;
> > > > > +
> > > > > +	mutex_lock(&dev->filelist_mutex);
> > > > > +	list_del(&client->file->lhead);
> > > > > +	mutex_unlock(&dev->filelist_mutex);
> > > > > +
> > > > > +	drm_file_free(client->file);
> > > > > +	client->file = NULL;
> > > > > +	drm_dev_put(dev);
> > > > > +	module_put(dev->driver->fops->owner);
> > > > > +out_unlock:
> > > > > +	mutex_unlock(&client->lock);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_put_file);
> > > > > +
> > > > > +static struct drm_pending_event *
> > > > > +drm_client_read_get_pending_event(struct drm_device *dev, struct drm_file *file)
> > > > > +{
> > > > > +	struct drm_pending_event *e = NULL;
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = mutex_lock_interruptible(&file->event_read_lock);
> > > > > +	if (ret)
> > > > > +		return ERR_PTR(ret);
> > > > > +
> > > > > +	spin_lock_irq(&dev->event_lock);
> > > > > +	if (!list_empty(&file->event_list)) {
> > > > > +		e = list_first_entry(&file->event_list,
> > > > > +				     struct drm_pending_event, link);
> > > > > +		file->event_space += e->event->length;
> > > > > +		list_del(&e->link);
> > > > > +	}
> > > > > +	spin_unlock_irq(&dev->event_lock);
> > > > > +
> > > > > +	mutex_unlock(&file->event_read_lock);
> > > > > +
> > > > > +	return e;
> > > > > +}
> > > > > +
> > > > > +struct drm_event *
> > > > > +drm_client_read_event(struct drm_client_dev *client, bool block)
> > > > > +{
> > > > > +	struct drm_file *file = client->file;
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +	struct drm_pending_event *e;
> > > > > +	struct drm_event *event;
> > > > > +	int ret;
> > > > > +
> > > > > +	/* Allocate so it fits all events, there's a sanity check later */
> > > > > +	event = kzalloc(128, GFP_KERNEL);
> > > > > +	if (!event)
> > > > > +		return ERR_PTR(-ENOMEM);
> > > > > +
> > > > > +	e = drm_client_read_get_pending_event(dev, file);
> > > > > +	if (IS_ERR(e)) {
> > > > > +		ret = PTR_ERR(e);
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	if (!e && !block) {
> > > > > +		ret = 0;
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	ret = wait_event_interruptible_timeout(file->event_wait,
> > > > > +					       !list_empty(&file->event_list),
> > > > > +					       5 * HZ);
> > > > > +	if (!ret)
> > > > > +		ret = -ETIMEDOUT;
> > > > > +	if (ret < 0)
> > > > > +		goto err_free;
> > > > > +
> > > > > +	e = drm_client_read_get_pending_event(dev, file);
> > > > > +	if (IS_ERR_OR_NULL(e)) {
> > > > > +		ret = PTR_ERR_OR_ZERO(e);
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	if (WARN_ON(e->event->length > 128)) {
> > > > > +		/* Increase buffer if this happens */
> > > > > +		ret = -ENOMEM;
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	memcpy(event, e->event, e->event->length);
> > > > > +	kfree(e);
> > > > > +
> > > > > +	return event;
> > > > > +
> > > > > +err_free:
> > > > > +	kfree(event);
> > > > > +
> > > > > +	return ret ? ERR_PTR(ret) : NULL;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_read_event);
> > > > > +
> > > > > +static void drm_client_connector_free(struct drm_client_connector *connector)
> > > > > +{
> > > > > +	if (!connector)
> > > > > +		return;
> > > > > +	kfree(connector->modes);
> > > > > +	kfree(connector);
> > > > > +}
> > > > > +
> > > > > +static struct drm_client_connector *
> > > > > +drm_client_get_connector(struct drm_client_dev *client, unsigned int id)
> > > > > +{
> > > > > +	struct drm_mode_get_connector req = {
> > > > > +		.connector_id = id,
> > > > > +	};
> > > > > +	struct drm_client_connector *connector;
> > > > > +	struct drm_mode_modeinfo *modes = NULL;
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +	struct drm_connector *conn;
> > > > > +	bool non_desktop;
> > > > > +	int ret;
> > > > > +
> > > > > +	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
> > > > > +	if (!connector)
> > > > > +		return ERR_PTR(-ENOMEM);
> > > > > +
> > > > > +	ret = drm_mode_getconnector(dev, &req, client->file, false);
> > > > > +	if (ret)
> > > > > +		goto err_free;
> > > > > +
> > > > > +	connector->conn_id = id;
> > > > > +	connector->status = req.connection;
> > > > > +
> > > > > +	conn = drm_connector_lookup(dev, client->file, id);
> > > > > +	if (!conn) {
> > > > > +		ret = -ENOENT;
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	non_desktop = conn->display_info.non_desktop;
> > > > > +
> > > > > +	connector->has_tile = conn->has_tile;
> > > > > +	connector->tile_h_loc = conn->tile_h_loc;
> > > > > +	connector->tile_v_loc = conn->tile_v_loc;
> > > > > +	if (conn->tile_group)
> > > > > +		connector->tile_group = conn->tile_group->id;
> > > > > +
> > > > > +	drm_connector_put(conn);
> > > > > +
> > > > > +	if (non_desktop) {
> > > > > +		kfree(connector);
> > > > > +		return NULL;
> > > > > +	}
> > > > > +
> > > > > +	if (!req.count_modes)
> > > > > +		return connector;
> > > > > +
> > > > > +	modes = kcalloc(req.count_modes, sizeof(*modes), GFP_KERNEL);
> > > > > +	if (!modes) {
> > > > > +		ret = -ENOMEM;
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	connector->modes = modes;
> > > > > +	connector->num_modes = req.count_modes;
> > > > > +
> > > > > +	req.count_props = 0;
> > > > > +	req.count_encoders = 0;
> > > > > +	req.modes_ptr = (unsigned long)modes;
> > > > > +
> > > > > +	ret = drm_mode_getconnector(dev, &req, client->file, false);
> > > > > +	if (ret)
> > > > > +		goto err_free;
> > > > > +
> > > > > +	return connector;
> > > > > +
> > > > > +err_free:
> > > > > +	kfree(modes);
> > > > > +	kfree(connector);
> > > > > +
> > > > > +	return ERR_PTR(ret);
> > > > > +}
> > > > > +
> > > > > +static int drm_client_get_connectors(struct drm_client_dev *client,
> > > > > +				     struct drm_client_connector ***connectors)
> > > > > +{
> > > > > +	struct drm_mode_card_res card_res = {};
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +	int ret, num_connectors;
> > > > > +	u32 *connector_ids;
> > > > > +	unsigned int i;
> > > > > +
> > > > > +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +	if (!card_res.count_connectors)
> > > > > +		return 0;
> > > > > +
> > > > > +	num_connectors = card_res.count_connectors;
> > > > > +	connector_ids = kcalloc(num_connectors,
> > > > > +				sizeof(*connector_ids), GFP_KERNEL);
> > > > > +	if (!connector_ids)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	card_res.count_fbs = 0;
> > > > > +	card_res.count_crtcs = 0;
> > > > > +	card_res.count_encoders = 0;
> > > > > +	card_res.connector_id_ptr = (unsigned long)connector_ids;
> > > > > +
> > > > > +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
> > > > > +	if (ret)
> > > > > +		goto err_free;
> > > > > +
> > > > > +	*connectors = kcalloc(num_connectors, sizeof(**connectors), GFP_KERNEL);
> > > > > +	if (!(*connectors)) {
> > > > > +		ret = -ENOMEM;
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		struct drm_client_connector *connector;
> > > > > +
> > > > > +		connector = drm_client_get_connector(client, connector_ids[i]);
> > > > > +		if (IS_ERR(connector)) {
> > > > > +			ret = PTR_ERR(connector);
> > > > > +			goto err_free;
> > > > > +		}
> > > > > +		if (connector)
> > > > > +			(*connectors)[i] = connector;
> > > > > +		else
> > > > > +			num_connectors--;
> > > > > +	}
> > > > > +
> > > > > +	if (!num_connectors) {
> > > > > +		ret = 0;
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	return num_connectors;
> > > > > +
> > > > > +err_free:
> > > > > +	if (connectors)
> > > > > +		for (i = 0; i < num_connectors; i++)
> > > > > +			drm_client_connector_free((*connectors)[i]);
> > > > > +
> > > > > +	kfree(connectors);
> > > > > +	kfree(connector_ids);
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +static bool
> > > > > +drm_client_connector_is_enabled(struct drm_client_connector *connector,
> > > > > +				bool strict)
> > > > > +{
> > > > > +	if (strict)
> > > > > +		return connector->status == connector_status_connected;
> > > > > +	else
> > > > > +		return connector->status != connector_status_disconnected;
> > > > > +}
> > > > > +
> > > > > +struct drm_mode_modeinfo *
> > > > > +drm_client_display_first_mode(struct drm_client_display *display)
> > > > > +{
> > > > > +	if (!display->num_modes)
> > > > > +		return NULL;
> > > > > +	return display->modes;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_first_mode);
> > > > > +
> > > > > +struct drm_mode_modeinfo *
> > > > > +drm_client_display_next_mode(struct drm_client_display *display,
> > > > > +			     struct drm_mode_modeinfo *mode)
> > > > > +{
> > > > > +	struct drm_mode_modeinfo *modes = display->modes;
> > > > > +
> > > > > +	if (++mode < &modes[display->num_modes])
> > > > > +		return mode;
> > > > > +
> > > > > +	return NULL;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_next_mode);
> > > > > +
> > > > > +static void
> > > > > +drm_client_display_fill_tile_modes(struct drm_client_display *display,
> > > > > +				   struct drm_mode_modeinfo *tile_modes)
> > > > > +{
> > > > > +	unsigned int i, j, num_modes = display->connectors[0]->num_modes;
> > > > > +	struct drm_mode_modeinfo *tile_mode, *conn_mode;
> > > > > +
> > > > > +	if (!num_modes) {
> > > > > +		kfree(tile_modes);
> > > > > +		kfree(display->modes);
> > > > > +		display->modes = NULL;
> > > > > +		display->num_modes = 0;
> > > > > +		return;
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < num_modes; i++) {
> > > > > +		tile_mode = &tile_modes[i];
> > > > > +
> > > > > +		conn_mode = &display->connectors[0]->modes[i];
> > > > > +		tile_mode->clock = conn_mode->clock;
> > > > > +		tile_mode->vscan = conn_mode->vscan;
> > > > > +		tile_mode->vrefresh = conn_mode->vrefresh;
> > > > > +		tile_mode->flags = conn_mode->flags;
> > > > > +		tile_mode->type = conn_mode->type;
> > > > > +
> > > > > +		for (j = 0; j < display->num_connectors; j++) {
> > > > > +			conn_mode = &display->connectors[j]->modes[i];
> > > > > +
> > > > > +			if (!display->connectors[j]->tile_h_loc) {
> > > > > +				tile_mode->hdisplay += conn_mode->hdisplay;
> > > > > +				tile_mode->hsync_start += conn_mode->hsync_start;
> > > > > +				tile_mode->hsync_end += conn_mode->hsync_end;
> > > > > +				tile_mode->htotal += conn_mode->htotal;
> > > > > +			}
> > > > > +
> > > > > +			if (!display->connectors[j]->tile_v_loc) {
> > > > > +				tile_mode->vdisplay += conn_mode->vdisplay;
> > > > > +				tile_mode->vsync_start += conn_mode->vsync_start;
> > > > > +				tile_mode->vsync_end += conn_mode->vsync_end;
> > > > > +				tile_mode->vtotal += conn_mode->vtotal;
> > > > > +			}
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	kfree(display->modes);
> > > > > +	display->modes = tile_modes;
> > > > > +	display->num_modes = num_modes;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_client_display_update_modes - Fetch display modes
> > > > > + * @display: Client display
> > > > > + * @mode_changed: Optional pointer to boolen which return whether the modes
> > > > > + *                have changed or not.
> > > > > + *
> > > > > + * This function can be used in the client hotplug callback to check if the
> > > > > + * video modes have changed and get them up-to-date.
> > > > > + *
> > > > > + * Returns:
> > > > > + * Number of modes on success, negative error code on failure.
> > > > > + */
> > > > > +int drm_client_display_update_modes(struct drm_client_display *display,
> > > > > +				    bool *mode_changed)
> > > > > +{
> > > > > +	unsigned int num_connectors = display->num_connectors;
> > > > > +	struct drm_client_dev *client = display->client;
> > > > > +	struct drm_mode_modeinfo *display_tile_modes;
> > > > > +	struct drm_client_connector **connectors;
> > > > > +	unsigned int i, num_modes = 0;
> > > > > +	bool dummy_changed = false;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (mode_changed)
> > > > > +		*mode_changed = false;
> > > > > +	else
> > > > > +		mode_changed = &dummy_changed;
> > > > > +
> > > > > +	if (display->cloned)
> > > > > +		return 2;
> > > > > +
> > > > > +	ret = drm_client_get_file(client);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
> > > > > +	if (!connectors) {
> > > > > +		ret = -ENOMEM;
> > > > > +		goto out_put_file;
> > > > > +	}
> > > > > +
> > > > > +	/* Get a new set for comparison */
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		connectors[i] = drm_client_get_connector(client, display->connectors[i]->conn_id);
> > > > > +		if (IS_ERR_OR_NULL(connectors[i])) {
> > > > > +			ret = PTR_ERR_OR_ZERO(connectors[i]);
> > > > > +			if (!ret)
> > > > > +				ret = -ENOENT;
> > > > > +			goto out_cleanup;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	/* All connectors should have the same number of modes */
> > > > > +	num_modes = connectors[0]->num_modes;
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		if (num_modes != connectors[i]->num_modes) {
> > > > > +			ret = -EINVAL;
> > > > > +			goto out_cleanup;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	if (num_connectors > 1) {
> > > > > +		display_tile_modes = kcalloc(num_modes, sizeof(*display_tile_modes), GFP_KERNEL);
> > > > > +		if (!display_tile_modes) {
> > > > > +			ret = -ENOMEM;
> > > > > +			goto out_cleanup;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	mutex_lock(&display->modes_lock);
> > > > > +
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		display->connectors[i]->status = connectors[i]->status;
> > > > > +		if (display->connectors[i]->num_modes != connectors[i]->num_modes) {
> > > > > +			display->connectors[i]->num_modes = connectors[i]->num_modes;
> > > > > +			kfree(display->connectors[i]->modes);
> > > > > +			display->connectors[i]->modes = connectors[i]->modes;
> > > > > +			connectors[i]->modes = NULL;
> > > > > +			*mode_changed = true;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	if (num_connectors > 1)
> > > > > +		drm_client_display_fill_tile_modes(display, display_tile_modes);
> > > > > +	else
> > > > > +		display->modes = display->connectors[0]->modes;
> > > > > +
> > > > > +	mutex_unlock(&display->modes_lock);
> > > > > +
> > > > > +out_cleanup:
> > > > > +	for (i = 0; i < num_connectors; i++)
> > > > > +		drm_client_connector_free(connectors[i]);
> > > > > +	kfree(connectors);
> > > > > +out_put_file:
> > > > > +	drm_client_put_file(client);
> > > > > +
> > > > > +	return ret ? ret : num_modes;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_update_modes);
> > > > > +
> > > > > +void drm_client_display_free(struct drm_client_display *display)
> > > > > +{
> > > > > +	unsigned int i;
> > > > > +
> > > > > +	if (!display)
> > > > > +		return;
> > > > > +
> > > > > +	/* tile modes? */
> > > > > +	if (display->modes != display->connectors[0]->modes)
> > > > > +		kfree(display->modes);
> > > > > +
> > > > > +	for (i = 0; i < display->num_connectors; i++)
> > > > > +		drm_client_connector_free(display->connectors[i]);
> > > > > +
> > > > > +	kfree(display->connectors);
> > > > > +	mutex_destroy(&display->modes_lock);
> > > > > +	kfree(display);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_free);
> > > > > +
> > > > > +static struct drm_client_display *
> > > > > +drm_client_display_alloc(struct drm_client_dev *client,
> > > > > +			 unsigned int num_connectors)
> > > > > +{
> > > > > +	struct drm_client_display *display;
> > > > > +	struct drm_client_connector **connectors;
> > > > > +
> > > > > +	display = kzalloc(sizeof(*display), GFP_KERNEL);
> > > > > +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
> > > > > +	if (!display || !connectors) {
> > > > > +		kfree(display);
> > > > > +		kfree(connectors);
> > > > > +		return NULL;
> > > > > +	}
> > > > > +
> > > > > +	mutex_init(&display->modes_lock);
> > > > > +	display->client = client;
> > > > > +	display->connectors = connectors;
> > > > > +	display->num_connectors = num_connectors;
> > > > > +
> > > > > +	return display;
> > > > > +}
> > > > > +
> > > > > +/* Logic is from drm_fb_helper */
> > > > > +static struct drm_client_display *
> > > > > +drm_client_connector_pick_cloned(struct drm_client_dev *client,
> > > > > +				 struct drm_client_connector **connectors,
> > > > > +				 unsigned int num_connectors)
> > > > > +{
> > > > > +	struct drm_mode_modeinfo modes[2], udmt_mode, *mode, *tmp;
> > > > > +	struct drm_display_mode *dmt_display_mode = NULL;
> > > > > +	unsigned int i, j, conns[2], num_conns = 0;
> > > > > +	struct drm_client_connector *connector;
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +	struct drm_client_display *display;
> > > > > +
> > > > > +	/* only contemplate cloning in the single crtc case */
> > > > > +	if (dev->mode_config.num_crtc > 1)
> > > > > +		return NULL;
> > > > > +retry:
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		connector = connectors[i];
> > > > > +		if (!connector || connector->has_tile || !connector->num_modes)
> > > > > +			continue;
> > > > > +
> > > > > +		for (j = 0; j < connector->num_modes; j++) {
> > > > > +			mode = &connector->modes[j];
> > > > > +			if (dmt_display_mode) {
> > > > > +				if (drm_umode_equal(&udmt_mode, mode)) {
> > > > > +					conns[num_conns] = i;
> > > > > +					modes[num_conns++] = *mode;
> > > > > +					break;
> > > > > +				}
> > > > > +			} else {
> > > > > +				if (mode->type & DRM_MODE_TYPE_USERDEF) {
> > > > > +					conns[num_conns] = i;
> > > > > +					modes[num_conns++] = *mode;
> > > > > +					break;
> > > > > +				}
> > > > > +			}
> > > > > +		}
> > > > > +		if (num_conns == 2)
> > > > > +			break;
> > > > > +	}
> > > > > +
> > > > > +	if (num_conns == 2)
> > > > > +		goto found;
> > > > > +
> > > > > +	if (dmt_display_mode)
> > > > > +		return NULL;
> > > > > +
> > > > > +	dmt_display_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
> > > > > +	drm_mode_convert_to_umode(&udmt_mode, dmt_display_mode);
> > > > > +	drm_mode_destroy(dev, dmt_display_mode);
> > > > > +
> > > > > +	goto retry;
> > > > > +
> > > > > +found:
> > > > > +	tmp = kcalloc(2, sizeof(*tmp), GFP_KERNEL);
> > > > > +	if (!tmp)
> > > > > +		return ERR_PTR(-ENOMEM);
> > > > > +
> > > > > +	display = drm_client_display_alloc(client, 2);
> > > > > +	if (!display) {
> > > > > +		kfree(tmp);
> > > > > +		return ERR_PTR(-ENOMEM);
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < 2; i++) {
> > > > > +		connector = connectors[conns[i]];
> > > > > +		display->connectors[i] = connector;
> > > > > +		connectors[conns[i]] = NULL;
> > > > > +		kfree(connector->modes);
> > > > > +		tmp[i] = modes[i];
> > > > > +		connector->modes = &tmp[i];
> > > > > +		connector->num_modes = 1;
> > > > > +	}
> > > > > +
> > > > > +	display->cloned = true;
> > > > > +	display->modes = &tmp[0];
> > > > > +	display->num_modes = 1;
> > > > > +
> > > > > +	return display;
> > > > > +}
> > > > > +
> > > > > +static struct drm_client_display *
> > > > > +drm_client_connector_pick_tile(struct drm_client_dev *client,
> > > > > +			       struct drm_client_connector **connectors,
> > > > > +			       unsigned int num_connectors)
> > > > > +{
> > > > > +	unsigned int i, num_conns, num_modes, tile_group = 0;
> > > > > +	struct drm_mode_modeinfo *tile_modes = NULL;
> > > > > +	struct drm_client_connector *connector;
> > > > > +	struct drm_client_display *display;
> > > > > +	u16 conns[32];
> > > > > +
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		connector = connectors[i];
> > > > > +		if (!connector || !connector->tile_group)
> > > > > +			continue;
> > > > > +
> > > > > +		if (!tile_group) {
> > > > > +			tile_group = connector->tile_group;
> > > > > +			num_modes = connector->num_modes;
> > > > > +		}
> > > > > +
> > > > > +		if (connector->tile_group != tile_group)
> > > > > +			continue;
> > > > > +
> > > > > +		if (num_modes != connector->num_modes) {
> > > > > +			DRM_ERROR("Tile connectors must have the same number of modes\n");
> > > > > +			return ERR_PTR(-EINVAL);
> > > > > +		}
> > > > > +
> > > > > +		conns[num_conns++] = i;
> > > > > +		if (WARN_ON(num_conns == 33))
> > > > > +			return ERR_PTR(-EINVAL);
> > > > > +	}
> > > > > +
> > > > > +	if (!num_conns)
> > > > > +		return NULL;
> > > > > +
> > > > > +	if (num_modes) {
> > > > > +		tile_modes = kcalloc(num_modes, sizeof(*tile_modes), GFP_KERNEL);
> > > > > +		if (!tile_modes)
> > > > > +			return ERR_PTR(-ENOMEM);
> > > > > +	}
> > > > > +
> > > > > +	display = drm_client_display_alloc(client, num_conns);
> > > > > +	if (!display) {
> > > > > +		kfree(tile_modes);
> > > > > +		return ERR_PTR(-ENOMEM);
> > > > > +	}
> > > > > +
> > > > > +	if (num_modes)
> > > > > +		drm_client_display_fill_tile_modes(display, tile_modes);
> > > > > +
> > > > > +	return display;
> > > > > +}
> > > > > +
> > > > > +static struct drm_client_display *
> > > > > +drm_client_connector_pick_not_tile(struct drm_client_dev *client,
> > > > > +				   struct drm_client_connector **connectors,
> > > > > +				   unsigned int num_connectors)
> > > > > +{
> > > > > +	struct drm_client_display *display;
> > > > > +	unsigned int i;
> > > > > +
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		if (!connectors[i] || connectors[i]->has_tile)
> > > > > +			continue;
> > > > > +		break;
> > > > > +	}
> > > > > +
> > > > > +	if (i == num_connectors)
> > > > > +		return NULL;
> > > > > +
> > > > > +	display = drm_client_display_alloc(client, 1);
> > > > > +	if (!display)
> > > > > +		return ERR_PTR(-ENOMEM);
> > > > > +
> > > > > +	display->connectors[0] = connectors[i];
> > > > > +	connectors[i] = NULL;
> > > > > +	display->modes = display->connectors[0]->modes;
> > > > > +	display->num_modes = display->connectors[0]->num_modes;
> > > > > +
> > > > > +	return display;
> > > > > +}
> > > > > +
> > > > > +/* Get connectors and bundle them up into displays */
> > > > > +static int drm_client_get_displays(struct drm_client_dev *client,
> > > > > +				   struct drm_client_display ***displays)
> > > > > +{
> > > > > +	int ret, num_connectors, num_displays = 0;
> > > > > +	struct drm_client_connector **connectors;
> > > > > +	struct drm_client_display *display;
> > > > > +	unsigned int i;
> > > > > +
> > > > > +	ret = drm_client_get_file(client);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	num_connectors = drm_client_get_connectors(client, &connectors);
> > > > > +	if (num_connectors <= 0) {
> > > > > +		ret = num_connectors;
> > > > > +		goto err_put_file;
> > > > > +	}
> > > > > +
> > > > > +	*displays = kcalloc(num_connectors, sizeof(*displays), GFP_KERNEL);
> > > > > +	if (!(*displays)) {
> > > > > +		ret = -ENOMEM;
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +
> > > > > +	display = drm_client_connector_pick_cloned(client, connectors,
> > > > > +						   num_connectors);
> > > > > +	if (IS_ERR(display)) {
> > > > > +		ret = PTR_ERR(display);
> > > > > +		goto err_free;
> > > > > +	}
> > > > > +	if (display)
> > > > > +		(*displays)[num_displays++] = display;
> > > > > +
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		display = drm_client_connector_pick_tile(client, connectors,
> > > > > +							 num_connectors);
> > > > > +		if (IS_ERR(display)) {
> > > > > +			ret = PTR_ERR(display);
> > > > > +			goto err_free;
> > > > > +		}
> > > > > +		if (!display)
> > > > > +			break;
> > > > > +		(*displays)[num_displays++] = display;
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		display = drm_client_connector_pick_not_tile(client, connectors,
> > > > > +							     num_connectors);
> > > > > +		if (IS_ERR(display)) {
> > > > > +			ret = PTR_ERR(display);
> > > > > +			goto err_free;
> > > > > +		}
> > > > > +		if (!display)
> > > > > +			break;
> > > > > +		(*displays)[num_displays++] = display;
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < num_connectors; i++) {
> > > > > +		if (connectors[i]) {
> > > > > +			DRM_INFO("Connector %u fell through the cracks.\n",
> > > > > +				 connectors[i]->conn_id);
> > > > > +			drm_client_connector_free(connectors[i]);
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	drm_client_put_file(client);
> > > > > +	kfree(connectors);
> > > > > +
> > > > > +	return num_displays;
> > > > > +
> > > > > +err_free:
> > > > > +	for (i = 0; i < num_displays; i++)
> > > > > +		drm_client_display_free((*displays)[i]);
> > > > > +	kfree(*displays);
> > > > > +	*displays = NULL;
> > > > > +	for (i = 0; i < num_connectors; i++)
> > > > > +		drm_client_connector_free(connectors[i]);
> > > > > +	kfree(connectors);
> > > > > +err_put_file:
> > > > > +	drm_client_put_file(client);
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +static bool
> > > > > +drm_client_display_is_enabled(struct drm_client_display *display, bool strict)
> > > > > +{
> > > > > +	unsigned int i;
> > > > > +
> > > > > +	if (!display->num_modes)
> > > > > +		return false;
> > > > > +
> > > > > +	for (i = 0; i < display->num_connectors; i++)
> > > > > +		if (!drm_client_connector_is_enabled(display->connectors[i], strict))
> > > > > +			return false;
> > > > > +
> > > > > +	return true;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_client_display_get_first_enabled - Get first enabled display
> > > > > + * @client: DRM client
> > > > > + * @strict: If true the connector(s) have to be connected, if false they can
> > > > > + *          also have unknown status.
> > > > > + *
> > > > > + * This function gets all connectors and bundles them into displays
> > > > > + * (tiled/cloned). It then picks the first one with connectors that is enabled
> > > > > + * according to @strict.
> > > > > + *
> > > > > + * Returns:
> > > > > + * Pointer to a client display if such a display was found, NULL if not found
> > > > > + * or an error pointer on failure.
> > > > > + */
> > > > > +struct drm_client_display *
> > > > > +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict)
> > > > > +{
> > > > > +	struct drm_client_display **displays, *display = NULL;
> > > > > +	int num_displays;
> > > > > +	unsigned int i;
> > > > > +
> > > > > +	num_displays = drm_client_get_displays(client, &displays);
> > > > > +	if (num_displays < 0)
> > > > > +		return ERR_PTR(num_displays);
> > > > > +	if (!num_displays)
> > > > > +		return NULL;
> > > > > +
> > > > > +	for (i = 0; i < num_displays; i++) {
> > > > > +		if (!display &&
> > > > > +		    drm_client_display_is_enabled(displays[i], strict)) {
> > > > > +			display = displays[i];
> > > > > +			continue;
> > > > > +		}
> > > > > +		drm_client_display_free(displays[i]);
> > > > > +	}
> > > > > +
> > > > > +	kfree(displays);
> > > > > +
> > > > > +	return display;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_get_first_enabled);
> > > > > +
> > > > > +unsigned int
> > > > > +drm_client_display_preferred_depth(struct drm_client_display *display)
> > > > > +{
> > > > > +	struct drm_connector *conn;
> > > > > +	unsigned int ret;
> > > > > +
> > > > > +	conn = drm_connector_lookup(display->client->dev, NULL,
> > > > > +				    display->connectors[0]->conn_id);
> > > > > +	if (!conn)
> > > > > +		return 0;
> > > > > +
> > > > > +	if (conn->cmdline_mode.bpp_specified)
> > > > > +		ret = conn->cmdline_mode.bpp;
> > > > > +	else
> > > > > +		ret = display->client->dev->mode_config.preferred_depth;
> > > > > +
> > > > > +	drm_connector_put(conn);
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_preferred_depth);
> > > > > +
> > > > > +int drm_client_display_dpms(struct drm_client_display *display, int mode)
> > > > > +{
> > > > > +	struct drm_mode_obj_set_property prop;
> > > > > +
> > > > > +	prop.value = mode;
> > > > > +	prop.prop_id = display->client->dev->mode_config.dpms_property->base.id;
> > > > > +	prop.obj_id = display->connectors[0]->conn_id;
> > > > > +	prop.obj_type = DRM_MODE_OBJECT_CONNECTOR;
> > > > > +
> > > > > +	return drm_mode_obj_set_property(display->client->dev, &prop,
> > > > > +					 display->client->file);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_dpms);
> > > > > +
> > > > > +int drm_client_display_wait_vblank(struct drm_client_display *display)
> > > > > +{
> > > > > +	struct drm_crtc *crtc;
> > > > > +	union drm_wait_vblank vblank_req = {
> > > > > +		.request = {
> > > > > +			.type = _DRM_VBLANK_RELATIVE,
> > > > > +			.sequence = 1,
> > > > > +		},
> > > > > +	};
> > > > > +
> > > > > +	crtc = drm_crtc_find(display->client->dev, display->client->file,
> > > > > +			     display->connectors[0]->crtc_id);
> > > > > +	if (!crtc)
> > > > > +		return -ENOENT;
> > > > > +
> > > > > +	vblank_req.request.type |= drm_crtc_index(crtc) << _DRM_VBLANK_HIGH_CRTC_SHIFT;
> > > > > +
> > > > > +	return drm_wait_vblank(display->client->dev, &vblank_req,
> > > > > +			       display->client->file);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_wait_vblank);
> > > > > +
> > > > > +static int drm_client_get_crtc_index(struct drm_client_dev *client, u32 id)
> > > > > +{
> > > > > +	int i;
> > > > > +
> > > > > +	for (i = 0; i < client->num_crtcs; i++)
> > > > > +		if (client->crtcs[i] == id)
> > > > > +			return i;
> > > > > +
> > > > > +	return -ENOENT;
> > > > > +}
> > > > > +
> > > > > +static int drm_client_display_find_crtcs(struct drm_client_display *display)
> > > > > +{
> > > > > +	struct drm_client_dev *client = display->client;
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +	struct drm_file *file = client->file;
> > > > > +	u32 encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
> > > > > +	unsigned int i, j, available_crtcs = ~0;
> > > > > +	struct drm_mode_get_connector conn_req;
> > > > > +	struct drm_mode_get_encoder enc_req;
> > > > > +	int ret;
> > > > > +
> > > > > +	/* Already done? */
> > > > > +	if (display->connectors[0]->crtc_id)
> > > > > +		return 0;
> > > > > +
> > > > > +	for (i = 0; i < display->num_connectors; i++) {
> > > > > +		u32 active_crtcs = 0, crtcs_for_connector = 0;
> > > > > +		int crtc_idx;
> > > > > +
> > > > > +		memset(&conn_req, 0, sizeof(conn_req));
> > > > > +		conn_req.connector_id = display->connectors[i]->conn_id;
> > > > > +		conn_req.encoders_ptr = (unsigned long)(encoder_ids);
> > > > > +		conn_req.count_encoders = DRM_CONNECTOR_MAX_ENCODER;
> > > > > +		ret = drm_mode_getconnector(dev, &conn_req, file, false);
> > > > > +		if (ret)
> > > > > +			return ret;
> > > > > +
> > > > > +		if (conn_req.encoder_id) {
> > > > > +			memset(&enc_req, 0, sizeof(enc_req));
> > > > > +			enc_req.encoder_id = conn_req.encoder_id;
> > > > > +			ret = drm_mode_getencoder(dev, &enc_req, file);
> > > > > +			if (ret)
> > > > > +				return ret;
> > > > > +			crtcs_for_connector |= enc_req.possible_crtcs;
> > > > > +			if (crtcs_for_connector & available_crtcs)
> > > > > +				goto found;
> > > > > +		}
> > > > > +
> > > > > +		for (j = 0; j < conn_req.count_encoders; j++) {
> > > > > +			memset(&enc_req, 0, sizeof(enc_req));
> > > > > +			enc_req.encoder_id = encoder_ids[j];
> > > > > +			ret = drm_mode_getencoder(dev, &enc_req, file);
> > > > > +			if (ret)
> > > > > +				return ret;
> > > > > +
> > > > > +			crtcs_for_connector |= enc_req.possible_crtcs;
> > > > > +
> > > > > +			if (enc_req.crtc_id) {
> > > > > +				crtc_idx = drm_client_get_crtc_index(client, enc_req.crtc_id);
> > > > > +				if (crtc_idx >= 0)
> > > > > +					active_crtcs |= 1 << crtc_idx;
> > > > > +			}
> > > > > +		}
> > > > > +
> > > > > +found:
> > > > > +		crtcs_for_connector &= available_crtcs;
> > > > > +		active_crtcs &= available_crtcs;
> > > > > +
> > > > > +		if (!crtcs_for_connector)
> > > > > +			return -ENOENT;
> > > > > +
> > > > > +		if (active_crtcs)
> > > > > +			crtc_idx = ffs(active_crtcs) - 1;
> > > > > +		else
> > > > > +			crtc_idx = ffs(crtcs_for_connector) - 1;
> > > > > +
> > > > > +		if (crtc_idx >= client->num_crtcs)
> > > > > +			return -EINVAL;
> > > > > +
> > > > > +		display->connectors[i]->crtc_id = client->crtcs[crtc_idx];
> > > > > +		available_crtcs &= ~BIT(crtc_idx);
> > > > > +	}
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_client_display_commit_mode - Commit a mode to the crtc(s)
> > > > > + * @display: Client display
> > > > > + * @fb_id: Framebuffer id
> > > > > + * @mode: Video mode
> > > > > + *
> > > > > + * Returns:
> > > > > + * Zero on success, negative error code on failure.
> > > > > + */
> > > > > +int drm_client_display_commit_mode(struct drm_client_display *display,
> > > > > +				   u32 fb_id, struct drm_mode_modeinfo *mode)
> > > > > +{
> > > > > +	struct drm_client_dev *client = display->client;
> > > > > +	struct drm_device *dev = client->dev;
> > > > > +	unsigned int num_crtcs = client->num_crtcs;
> > > > > +	struct drm_file *file = client->file;
> > > > > +	unsigned int *xoffsets = NULL, *yoffsets = NULL;
> > > > > +	struct drm_mode_crtc *crtc_reqs, *req;
> > > > > +	u32 cloned_conn_ids[2];
> > > > > +	unsigned int i;
> > > > > +	int idx, ret;
> > > > > +
> > > > > +	ret = drm_client_display_find_crtcs(display);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	crtc_reqs = kcalloc(num_crtcs, sizeof(*crtc_reqs), GFP_KERNEL);
> > > > > +	if (!crtc_reqs)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	for (i = 0; i < num_crtcs; i++)
> > > > > +		crtc_reqs[i].crtc_id = client->crtcs[i];
> > > > > +
> > > > > +	if (drm_client_display_is_tiled(display)) {
> > > > > +		/* TODO calculate tile crtc offsets */
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < display->num_connectors; i++) {
> > > > > +		idx = drm_client_get_crtc_index(client, display->connectors[i]->crtc_id);
> > > > > +		if (idx < 0)
> > > > > +			return -ENOENT;
> > > > > +
> > > > > +		req = &crtc_reqs[idx];
> > > > > +
> > > > > +		req->fb_id = fb_id;
> > > > > +		if (xoffsets) {
> > > > > +			req->x = xoffsets[i];
> > > > > +			req->y = yoffsets[i];
> > > > > +		}
> > > > > +		req->mode_valid = 1;
> > > > > +		req->mode = *mode;
> > > > > +
> > > > > +		if (display->cloned) {
> > > > > +			cloned_conn_ids[0] = display->connectors[0]->conn_id;
> > > > > +			cloned_conn_ids[1] = display->connectors[1]->conn_id;
> > > > > +			req->set_connectors_ptr = (unsigned long)(cloned_conn_ids);
> > > > > +			req->count_connectors = 2;
> > > > > +			break;
> > > > > +		}
> > > > > +
> > > > > +		req->set_connectors_ptr = (unsigned long)(&display->connectors[i]->conn_id);
> > > > > +		req->count_connectors = 1;
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < num_crtcs; i++) {
> > > > > +		ret = drm_mode_setcrtc(dev, &crtc_reqs[i], file, false);
> > > > > +		if (ret)
> > > > > +			break;
> > > > > +	}
> > > > > +
> > > > > +	kfree(xoffsets);
> > > > > +	kfree(yoffsets);
> > > > > +	kfree(crtc_reqs);
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_commit_mode);
> > > > > +
> > > > > +unsigned int drm_client_display_current_fb(struct drm_client_display *display)
> > > > > +{
> > > > > +	struct drm_client_dev *client = display->client;
> > > > > +	int ret;
> > > > > +	struct drm_mode_crtc crtc_req = {
> > > > > +		.crtc_id = display->connectors[0]->crtc_id,
> > > > > +	};
> > > > > +
> > > > > +	ret = drm_mode_getcrtc(client->dev, &crtc_req, client->file);
> > > > > +	if (ret)
> > > > > +		return 0;
> > > > > +
> > > > > +	return crtc_req.fb_id;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_current_fb);
> > > > > +
> > > > > +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
> > > > > +			     struct drm_clip_rect *clips, unsigned int num_clips)
> > > > > +{
> > > > > +	struct drm_client_dev *client = display->client;
> > > > > +	struct drm_mode_fb_dirty_cmd dirty_req = {
> > > > > +		.fb_id = fb_id,
> > > > > +		.clips_ptr = (unsigned long)clips,
> > > > > +		.num_clips = num_clips,
> > > > > +	};
> > > > > +	int ret;
> > > > > +
> > > > > +	if (display->no_flushing)
> > > > > +		return 0;
> > > > > +
> > > > > +	ret = drm_mode_dirtyfb(client->dev, &dirty_req, client->file, false);
> > > > > +	if (ret == -ENOSYS) {
> > > > > +		ret = 0;
> > > > > +		display->no_flushing = true;
> > > > > +	}
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_flush);
> > > > > +
> > > > > +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
> > > > > +				 bool event)
> > > > > +{
> > > > > +	struct drm_client_dev *client = display->client;
> > > > > +	struct drm_mode_crtc_page_flip_target page_flip_req = {
> > > > > +		.crtc_id = display->connectors[0]->crtc_id,
> > > > > +		.fb_id = fb_id,
> > > > > +	};
> > > > > +
> > > > > +	if (event)
> > > > > +		page_flip_req.flags = DRM_MODE_PAGE_FLIP_EVENT;
> > > > > +
> > > > > +	return drm_mode_page_flip(client->dev, &page_flip_req, client->file);
> > > > > +	/*
> > > > > +	 * TODO:
> > > > > +	 * Where do we flush on page flip? Should the driver handle that?
> > > > > +	 */
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_display_page_flip);
> > > > > +
> > > > > +/**
> > > > > + * drm_client_framebuffer_create - Create a client framebuffer
> > > > > + * @client: DRM client
> > > > > + * @mode: Display mode to create a buffer for
> > > > > + * @format: Buffer format
> > > > > + *
> > > > > + * This function creates a &drm_client_buffer which consists of a
> > > > > + * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf
> > > > > + * exported to aquire a virtual address which is stored in
> > > > > + * &drm_client_buffer->vaddr.
> > > > > + * Call drm_client_framebuffer_delete() to free the buffer.
> > > > > + *
> > > > > + * Returns:
> > > > > + * Pointer to a client buffer or an error pointer on failure.
> > > > > + */
> > > > > +struct drm_client_buffer *
> > > > > +drm_client_framebuffer_create(struct drm_client_dev *client,
> > > > > +			      struct drm_mode_modeinfo *mode, u32 format)
> > > > > +{
> > > > > +	struct drm_client_buffer *buffer;
> > > > > +	int ret;
> > > > > +
> > > > > +	buffer = drm_client_buffer_create(client, mode->hdisplay,
> > > > > +					  mode->vdisplay, format);
> > > > > +	if (IS_ERR(buffer))
> > > > > +		return buffer;
> > > > > +
> > > > > +	ret = drm_client_buffer_addfb(buffer, mode);
> > > > > +	if (ret) {
> > > > > +		drm_client_buffer_delete(buffer);
> > > > > +		return ERR_PTR(ret);
> > > > > +	}
> > > > > +
> > > > > +	return buffer;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_framebuffer_create);
> > > > > +
> > > > > +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
> > > > > +{
> > > > > +	drm_client_buffer_rmfb(buffer);
> > > > > +	drm_client_buffer_delete(buffer);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_framebuffer_delete);
> > > > > +
> > > > > +struct drm_client_buffer *
> > > > > +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
> > > > > +			 u32 format)
> > > > > +{
> > > > > +	struct drm_mode_create_dumb dumb_args = { 0 };
> > > > > +	struct drm_prime_handle prime_args = { 0 };
> > > > > +	struct drm_client_buffer *buffer;
> > > > > +	struct dma_buf *dma_buf;
> > > > > +	void *vaddr;
> > > > > +	int ret;
> > > > > +
> > > > > +	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
> > > > > +	if (!buffer)
> > > > > +		return ERR_PTR(-ENOMEM);
> > > > > +
> > > > > +	ret = drm_client_get_file(client);
> > > > > +	if (ret)
> > > > > +		goto err_free;
> > > > > +
> > > > > +	buffer->client = client;
> > > > > +	buffer->width = width;
> > > > > +	buffer->height = height;
> > > > > +	buffer->format = format;
> > > > > +
> > > > > +	dumb_args.width = buffer->width;
> > > > > +	dumb_args.height = buffer->height;
> > > > > +	dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
> > > > > +	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
> > > > > +	if (ret)
> > > > > +		goto err_put_file;
> > > > > +
> > > > > +	buffer->handle = dumb_args.handle;
> > > > > +	buffer->pitch = dumb_args.pitch;
> > > > > +	buffer->size = dumb_args.size;
> > > > > +
> > > > > +	prime_args.handle = dumb_args.handle;
> > > > > +	ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file);
> > > > > +	if (ret)
> > > > > +		goto err_delete;
> > > > > +
> > > > > +	dma_buf = dma_buf_get(prime_args.fd);
> > > > > +	if (IS_ERR(dma_buf)) {
> > > > > +		ret = PTR_ERR(dma_buf);
> > > > > +		goto err_delete;
> > > > > +	}
> > > > > +
> > > > > +	buffer->dma_buf = dma_buf;
> > > > > +
> > > > > +	vaddr = dma_buf_vmap(dma_buf);
> > > > > +	if (!vaddr) {
> > > > > +		ret = -ENOMEM;
> > > > > +		goto err_delete;
> > > > > +	}
> > > > > +
> > > > > +	buffer->vaddr = vaddr;
> > > > > +
> > > > > +	return buffer;
> > > > > +
> > > > > +err_delete:
> > > > > +	drm_client_buffer_delete(buffer);
> > > > > +err_put_file:
> > > > > +	drm_client_put_file(client);
> > > > > +err_free:
> > > > > +	kfree(buffer);
> > > > > +
> > > > > +	return ERR_PTR(ret);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_buffer_create);
> > > > > +
> > > > > +void drm_client_buffer_delete(struct drm_client_buffer *buffer)
> > > > > +{
> > > > > +	if (!buffer)
> > > > > +		return;
> > > > > +
> > > > > +	if (buffer->vaddr)
> > > > > +		dma_buf_vunmap(buffer->dma_buf, buffer->vaddr);
> > > > > +
> > > > > +	if (buffer->dma_buf)
> > > > > +		dma_buf_put(buffer->dma_buf);
> > > > > +
> > > > > +	drm_mode_destroy_dumb(buffer->client->dev, buffer->handle,
> > > > > +			      buffer->client->file);
> > > > > +	drm_client_put_file(buffer->client);
> > > > > +	kfree(buffer);
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_buffer_delete);
> > > > > +
> > > > > +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
> > > > > +			    struct drm_mode_modeinfo *mode)
> > > > > +{
> > > > > +	struct drm_client_dev *client = buffer->client;
> > > > > +	struct drm_mode_fb_cmd2 fb_req = { };
> > > > > +	unsigned int num_fbs, *fb_ids;
> > > > > +	int i, ret;
> > > > > +
> > > > > +	if (buffer->num_fbs)
> > > > > +		return -EINVAL;
> > > > > +
> > > > > +	if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
> > > > > +		return -EINVAL;
> > > > > +
> > > > > +	num_fbs = buffer->height / mode->vdisplay;
> > > > > +	fb_ids = kcalloc(num_fbs, sizeof(*fb_ids), GFP_KERNEL);
> > > > > +	if (!fb_ids)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	fb_req.width = mode->hdisplay;
> > > > > +	fb_req.height = mode->vdisplay;
> > > > > +	fb_req.pixel_format = buffer->format;
> > > > > +	fb_req.handles[0] = buffer->handle;
> > > > > +	fb_req.pitches[0] = buffer->pitch;
> > > > > +
> > > > > +	for (i = 0; i < num_fbs; i++) {
> > > > > +		fb_req.offsets[0] = i * mode->vdisplay * buffer->pitch;
> > > > > +		ret = drm_mode_addfb2(client->dev, &fb_req, client->file,
> > > > > +				      client->funcs->name);
> > > > > +		if (ret)
> > > > > +			goto err_remove;
> > > > > +		fb_ids[i] = fb_req.fb_id;
> > > > > +	}
> > > > > +
> > > > > +	buffer->fb_ids = fb_ids;
> > > > > +	buffer->num_fbs = num_fbs;
> > > > > +
> > > > > +	return 0;
> > > > > +
> > > > > +err_remove:
> > > > > +	for (i--; i >= 0; i--)
> > > > > +		drm_mode_rmfb(client->dev, fb_ids[i], client->file);
> > > > > +	kfree(fb_ids);
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_buffer_addfb);
> > > > > +
> > > > > +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
> > > > > +{
> > > > > +	unsigned int i;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (!buffer || !buffer->num_fbs)
> > > > > +		return 0;
> > > > > +
> > > > > +	for (i = 0; i < buffer->num_fbs; i++) {
> > > > > +		ret = drm_mode_rmfb(buffer->client->dev, buffer->fb_ids[i],
> > > > > +				    buffer->client->file);
> > > > > +		if (ret)
> > > > > +			DRM_DEV_ERROR(buffer->client->dev->dev,
> > > > > +				      "Error removing FB:%u (%d)\n",
> > > > > +				      buffer->fb_ids[i], ret);
> > > > > +	}
> > > > > +
> > > > > +	kfree(buffer->fb_ids);
> > > > > +	buffer->fb_ids = NULL;
> > > > > +	buffer->num_fbs = 0;
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +EXPORT_SYMBOL(drm_client_buffer_rmfb);
> > > > > diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
> > > > > index f869de185986..db161337d87c 100644
> > > > > --- a/drivers/gpu/drm/drm_drv.c
> > > > > +++ b/drivers/gpu/drm/drm_drv.c
> > > > > @@ -33,6 +33,7 @@
> > > > >    #include <linux/mount.h>
> > > > >    #include <linux/slab.h>
> > > > > +#include <drm/drm_client.h>
> > > > >    #include <drm/drm_drv.h>
> > > > >    #include <drm/drmP.h>
> > > > > @@ -463,6 +464,7 @@ int drm_dev_init(struct drm_device *dev,
> > > > >    	dev->driver = driver;
> > > > >    	INIT_LIST_HEAD(&dev->filelist);
> > > > > +	INIT_LIST_HEAD(&dev->filelist_internal);
> > > > >    	INIT_LIST_HEAD(&dev->ctxlist);
> > > > >    	INIT_LIST_HEAD(&dev->vmalist);
> > > > >    	INIT_LIST_HEAD(&dev->maplist);
> > > > > @@ -787,6 +789,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
> > > > >    		 dev->dev ? dev_name(dev->dev) : "virtual device",
> > > > >    		 dev->primary->index);
> > > > > +	drm_client_dev_register(dev);
> > > > > +
> > > > >    	goto out_unlock;
> > > > >    err_minors:
> > > > > @@ -839,6 +843,8 @@ void drm_dev_unregister(struct drm_device *dev)
> > > > >    	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
> > > > >    	drm_minor_unregister(dev, DRM_MINOR_RENDER);
> > > > >    	drm_minor_unregister(dev, DRM_MINOR_CONTROL);
> > > > > +
> > > > > +	drm_client_dev_unregister(dev);
> > > > >    }
> > > > >    EXPORT_SYMBOL(drm_dev_unregister);
> > > > > diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
> > > > > index 55505378df47..bcc688e58776 100644
> > > > > --- a/drivers/gpu/drm/drm_file.c
> > > > > +++ b/drivers/gpu/drm/drm_file.c
> > > > > @@ -35,6 +35,7 @@
> > > > >    #include <linux/slab.h>
> > > > >    #include <linux/module.h>
> > > > > +#include <drm/drm_client.h>
> > > > >    #include <drm/drm_file.h>
> > > > >    #include <drm/drmP.h>
> > > > > @@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
> > > > >    	if (drm_core_check_feature(dev, DRIVER_LEGACY))
> > > > >    		drm_legacy_dev_reinit(dev);
> > > > > +
> > > > > +	drm_client_dev_lastclose(dev);
> > > > >    }
> > > > >    /**
> > > > > diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c
> > > > > index 2d1643bdae78..5d2a6c6717f5 100644
> > > > > --- a/drivers/gpu/drm/drm_probe_helper.c
> > > > > +++ b/drivers/gpu/drm/drm_probe_helper.c
> > > > > @@ -33,6 +33,7 @@
> > > > >    #include <linux/moduleparam.h>
> > > > >    #include <drm/drmP.h>
> > > > > +#include <drm/drm_client.h>
> > > > >    #include <drm/drm_crtc.h>
> > > > >    #include <drm/drm_fourcc.h>
> > > > >    #include <drm/drm_crtc_helper.h>
> > > > > @@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
> > > > >    	drm_sysfs_hotplug_event(dev);
> > > > >    	if (dev->mode_config.funcs->output_poll_changed)
> > > > >    		dev->mode_config.funcs->output_poll_changed(dev);
> > > > > +
> > > > > +	drm_client_dev_hotplug(dev);
> > > > >    }
> > > > >    EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
> > > > > diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> > > > > new file mode 100644
> > > > > index 000000000000..88f6f87919c5
> > > > > --- /dev/null
> > > > > +++ b/include/drm/drm_client.h
> > > > > @@ -0,0 +1,192 @@
> > > > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > > > +
> > > > > +#include <linux/mutex.h>
> > > > > +
> > > > > +struct dma_buf;
> > > > > +struct drm_clip_rect;
> > > > > +struct drm_device;
> > > > > +struct drm_file;
> > > > > +struct drm_mode_modeinfo;
> > > > > +
> > > > > +struct drm_client_dev;
> > > > > +
> > > > > +/**
> > > > > + * struct drm_client_funcs - DRM client  callbacks
> > > > > + */
> > > > > +struct drm_client_funcs {
> > > > > +	/**
> > > > > +	 * @name:
> > > > > +	 *
> > > > > +	 * Name of the client.
> > > > > +	 */
> > > > > +	const char *name;
> > > > > +
> > > > > +	/**
> > > > > +	 * @new:
> > > > > +	 *
> > > > > +	 * Called when a client or a &drm_device is registered.
> > > > > +	 * If the callback returns anything but zero, then this client instance
> > > > > +	 * is dropped.
> > > > > +	 *
> > > > > +	 * This callback is mandatory.
> > > > > +	 */
> > > > > +	int (*new)(struct drm_client_dev *client);
> > > > > +
> > > > > +	/**
> > > > > +	 * @remove:
> > > > > +	 *
> > > > > +	 * Called when a &drm_device is unregistered or the client is
> > > > > +	 * unregistered. If zero is returned drm_client_free() is called
> > > > > +	 * automatically. If the client can't drop it's resources it should
> > > > > +	 * return non-zero and call drm_client_free() later.
> > > > > +	 *
> > > > > +	 * This callback is optional.
> > > > > +	 */
> > > > > +	int (*remove)(struct drm_client_dev *client);
> > > > > +
> > > > > +	/**
> > > > > +	 * @lastclose:
> > > > > +	 *
> > > > > +	 * Called on drm_lastclose(). The first client instance in the list
> > > > > +	 * that returns zero gets the privilege to restore and no more clients
> > > > > +	 * are called.
> > > > > +	 *
> > > > > +	 * This callback is optional.
> > > > > +	 */
> > > > > +	int (*lastclose)(struct drm_client_dev *client);
> > > > > +
> > > > > +	/**
> > > > > +	 * @hotplug:
> > > > > +	 *
> > > > > +	 * Called on drm_kms_helper_hotplug_event().
> > > > > +	 *
> > > > > +	 * This callback is optional.
> > > > > +	 */
> > > > > +	int (*hotplug)(struct drm_client_dev *client);
> > > > > +
> > > > > +// TODO
> > > > > +//	void (*suspend)(struct drm_client_dev *client);
> > > > > +//	void (*resume)(struct drm_client_dev *client);
> > > > > +};
> > > > > +
> > > > > +/**
> > > > > + * struct drm_client_dev - DRM client instance
> > > > > + */
> > > > > +struct drm_client_dev {
> > > > > +	struct list_head list;
> > > > > +	struct drm_device *dev;
> > > > > +	const struct drm_client_funcs *funcs;
> > > > > +	struct mutex lock;
> > > > > +	struct drm_file *file;
> > > > > +	unsigned int file_ref_count;
> > > > > +	u32 *crtcs;
> > > > > +	unsigned int num_crtcs;
> > > > > +	u32 min_width;
> > > > > +	u32 max_width;
> > > > > +	u32 min_height;
> > > > > +	u32 max_height;
> > > > > +	void *private;
> > > > > +};
> > > > > +
> > > > > +void drm_client_free(struct drm_client_dev *client);
> > > > > +int drm_client_register(const struct drm_client_funcs *funcs);
> > > > > +void drm_client_unregister(const struct drm_client_funcs *funcs);
> > > > > +
> > > > > +void drm_client_dev_register(struct drm_device *dev);
> > > > > +void drm_client_dev_unregister(struct drm_device *dev);
> > > > > +void drm_client_dev_hotplug(struct drm_device *dev);
> > > > > +void drm_client_dev_lastclose(struct drm_device *dev);
> > > > > +
> > > > > +int drm_client_get_file(struct drm_client_dev *client);
> > > > > +void drm_client_put_file(struct drm_client_dev *client);
> > > > > +struct drm_event *
> > > > > +drm_client_read_event(struct drm_client_dev *client, bool block);
> > > > > +
> > > > > +struct drm_client_connector {
> > > > > +	unsigned int conn_id;
> > > > > +	unsigned int status;
> > > > > +	unsigned int crtc_id;
> > > > > +	struct drm_mode_modeinfo *modes;
> > > > > +	unsigned int num_modes;
> > > > > +	bool has_tile;
> > > > > +	int tile_group;
> > > > > +	u8 tile_h_loc, tile_v_loc;
> > > > > +};
> > > > > +
> > > > > +struct drm_client_display {
> > > > > +	struct drm_client_dev *client;
> > > > > +
> > > > > +	struct drm_client_connector **connectors;
> > > > > +	unsigned int num_connectors;
> > > > > +
> > > > > +	struct mutex modes_lock;
> > > > > +	struct drm_mode_modeinfo *modes;
> > > > > +	unsigned int num_modes;
> > > > > +
> > > > > +	bool cloned;
> > > > > +	bool no_flushing;
> > > > > +};
> > > > > +
> > > > > +void drm_client_display_free(struct drm_client_display *display);
> > > > > +struct drm_client_display *
> > > > > +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict);
> > > > > +
> > > > > +int drm_client_display_update_modes(struct drm_client_display *display,
> > > > > +				    bool *mode_changed);
> > > > > +
> > > > > +static inline bool
> > > > > +drm_client_display_is_tiled(struct drm_client_display *display)
> > > > > +{
> > > > > +	return !display->cloned && display->num_connectors > 1;
> > > > > +}
> > > > > +
> > > > > +int drm_client_display_dpms(struct drm_client_display *display, int mode);
> > > > > +int drm_client_display_wait_vblank(struct drm_client_display *display);
> > > > > +
> > > > > +struct drm_mode_modeinfo *
> > > > > +drm_client_display_first_mode(struct drm_client_display *display);
> > > > > +struct drm_mode_modeinfo *
> > > > > +drm_client_display_next_mode(struct drm_client_display *display,
> > > > > +			     struct drm_mode_modeinfo *mode);
> > > > > +
> > > > > +#define drm_client_display_for_each_mode(display, mode) \
> > > > > +	for (mode = drm_client_display_first_mode(display); mode; \
> > > > > +	     mode = drm_client_display_next_mode(display, mode))
> > > > > +
> > > > > +unsigned int
> > > > > +drm_client_display_preferred_depth(struct drm_client_display *display);
> > > > > +
> > > > > +int drm_client_display_commit_mode(struct drm_client_display *display,
> > > > > +				   u32 fb_id, struct drm_mode_modeinfo *mode);
> > > > > +unsigned int drm_client_display_current_fb(struct drm_client_display *display);
> > > > > +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
> > > > > +			     struct drm_clip_rect *clips, unsigned int num_clips);
> > > > > +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
> > > > > +				 bool event);
> > > > > +
> > > > > +struct drm_client_buffer {
> > > > > +	struct drm_client_dev *client;
> > > > > +	u32 width;
> > > > > +	u32 height;
> > > > > +	u32 format;
> > > > > +	u32 handle;
> > > > > +	u32 pitch;
> > > > > +	u64 size;
> > > > > +	struct dma_buf *dma_buf;
> > > > > +	void *vaddr;
> > > > > +
> > > > > +	unsigned int *fb_ids;
> > > > > +	unsigned int num_fbs;
> > > > > +};
> > > > > +
> > > > > +struct drm_client_buffer *
> > > > > +drm_client_framebuffer_create(struct drm_client_dev *client,
> > > > > +			      struct drm_mode_modeinfo *mode, u32 format);
> > > > > +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
> > > > > +struct drm_client_buffer *
> > > > > +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
> > > > > +			 u32 format);
> > > > > +void drm_client_buffer_delete(struct drm_client_buffer *buffer);
> > > > > +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
> > > > > +			    struct drm_mode_modeinfo *mode);
> > > > > +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer);
> > > > > diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
> > > > > index 7c4fa32f3fc6..32dfed3d5a86 100644
> > > > > --- a/include/drm/drm_device.h
> > > > > +++ b/include/drm/drm_device.h
> > > > > @@ -67,6 +67,7 @@ struct drm_device {
> > > > >    	struct mutex filelist_mutex;
> > > > >    	struct list_head filelist;
> > > > > +	struct list_head filelist_internal;
> > > > >    	/** \name Memory management */
> > > > >    	/*@{ */
> > > > > diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
> > > > > index 5176c3797680..39af8a4be7b3 100644
> > > > > --- a/include/drm/drm_file.h
> > > > > +++ b/include/drm/drm_file.h
> > > > > @@ -248,6 +248,13 @@ struct drm_file {
> > > > >    	 */
> > > > >    	void *driver_priv;
> > > > > +	/**
> > > > > +	 * @user_priv:
> > > > > +	 *
> > > > > +	 * Optional pointer for user private data. Useful for in-kernel clients.
> > > > > +	 */
> > > > > +	void *user_priv;
> > > > > +
> > > > >    	/**
> > > > >    	 * @fbs:
> > > > >    	 *
> > > > > -- 
> > > > > 2.15.1
> > > > > 
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

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

end of thread, other threads:[~2018-03-13 15:11 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-02-22 20:06 [RFC v3 00/12] drm: Add generic fbdev emulation Noralf Trønnes
2018-02-22 20:06 ` [RFC v3 01/12] drm: provide management functions for drm_file Noralf Trønnes
2018-02-22 20:06 ` [RFC v3 02/12] drm/file: Don't set master on in-kernel clients Noralf Trønnes
2018-03-06  8:28   ` Daniel Vetter
2018-02-22 20:06 ` [RFC v3 03/12] drm: Make ioctls available for in-kernel clients part 1 Noralf Trønnes
2018-02-22 20:06 ` [RFC v3 04/12] drm: Make ioctls available for in-kernel clients part 2 Noralf Trønnes
2018-03-06  8:41   ` Daniel Vetter
2018-02-22 20:06 ` [RFC v3 05/12] drm: Add _ioctl suffix to some functions Noralf Trønnes
2018-02-22 20:06 ` [RFC v3 06/12] drm: Add DRM device iterator Noralf Trønnes
2018-02-22 20:06 ` [RFC v3 07/12] drm/modes: Add drm_umode_equal() Noralf Trønnes
2018-03-06  8:42   ` Daniel Vetter
2018-02-22 20:06 ` [RFC v3 08/12] drm/framebuffer: Add drm_mode_can_dirtyfb() Noralf Trønnes
2018-03-06  8:45   ` Daniel Vetter
2018-02-22 20:06 ` [RFC v3 09/12] drm: Add API for in-kernel clients Noralf Trønnes
2018-03-06  8:56   ` Daniel Vetter
2018-03-08 17:12     ` Noralf Trønnes
2018-03-12 16:51       ` Daniel Vetter
2018-03-12 20:21         ` Noralf Trønnes
2018-03-13 15:11           ` Daniel Vetter
2018-02-22 20:06 ` [RFC v3 10/12] drm/client: Add fbdev emulation client Noralf Trønnes
2018-03-06  9:06   ` Daniel Vetter
2018-02-22 20:06 ` [RFC v3 11/12] drm/client: Add bootsplash client Noralf Trønnes
2018-03-06  9:12   ` Daniel Vetter
2018-03-06 15:21     ` Max Staudt
2018-02-22 20:06 ` [RFC v3 12/12] drm/client: Add VT console client Noralf Trønnes
2018-02-22 21:03 ` ✗ Fi.CI.BAT: failure for drm: Add generic fbdev emulation (rev3) Patchwork

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.