diff mbox

[RFC,v4,19/25] drm/client: Finish the in-kernel client API

Message ID 20180414115318.14500-20-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes April 14, 2018, 11:53 a.m. UTC
The modesetting code is already present, this adds the rest of the API.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_client.c       | 573 +++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_debugfs.c      |   7 +
 drivers/gpu/drm/drm_drv.c          |  11 +
 drivers/gpu/drm/drm_file.c         |   3 +
 drivers/gpu/drm/drm_probe_helper.c |   3 +
 drivers/gpu/drm/drm_sysfs.c        |  20 ++
 include/drm/drm_client.h           | 103 +++++++
 include/drm/drm_device.h           |   4 +
 8 files changed, 724 insertions(+)

Comments

Daniel Vetter April 16, 2018, 8:27 a.m. UTC | #1
On Sat, Apr 14, 2018 at 01:53:12PM +0200, Noralf Trønnes wrote:
> The modesetting code is already present, this adds the rest of the API.

Mentioning the TODO in the commit message would be good. Helps readers
like me who have an attention span measured in seconds :-)

Just commenting on the create_buffer leak here

> +static 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 = { };
> +	struct drm_prime_handle prime_args = { };
> +	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);
> +
> +	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_free;
> +
> +	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;
> +	}
> +
> +	/*
> +	 * If called from a worker the dmabuf fd isn't closed and the ref
> +	 * doesn't drop to zero on free.
> +	 * If I use __close_fd() it's all fine, but that function is not exported.
> +	 *
> +	 * How do I get rid of this fd when in a worker/kernel thread?
> +	 * The fd isn't used beyond this function.
> +	 */
> +//	WARN_ON(__close_fd(current->files, prime_args.fd));

Hm, this isn't 100% what I had in mind as the sequence for generic buffer
creation. Pseudo-code:

	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
	if (ret)
		goto err_free;
	
	gem_bo = drm_gem_object_lookup(client->file, dumb_args.handle);

gives you _really_ directly the underlying gem_bo. Of course this doesn't
work for non-gem based driver, but reality is that (almost) all of them
are. And we will not accept any new drivers which aren't gem based. So
ignoring vmwgfx for this drm_client work is imo perfectly fine. We should
ofc keep the option in the fb helpers to use non-gem buffers (so that
vmwgfx could switch over from their own in-driver fbdev helpers). All we
need for that is to keep the fb_probe callback.

Was there any other reason than vmwgfx for using prime buffers instead of
just directly using gem?

Cheers, Daniel
Noralf Trønnes April 16, 2018, 3:58 p.m. UTC | #2
Den 16.04.2018 10.27, skrev Daniel Vetter:
> On Sat, Apr 14, 2018 at 01:53:12PM +0200, Noralf Trønnes wrote:
>> The modesetting code is already present, this adds the rest of the API.
> Mentioning the TODO in the commit message would be good. Helps readers
> like me who have an attention span measured in seconds :-)
>
> Just commenting on the create_buffer leak here
>
>> +static 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 = { };
>> +	struct drm_prime_handle prime_args = { };
>> +	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);
>> +
>> +	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_free;
>> +
>> +	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;
>> +	}
>> +
>> +	/*
>> +	 * If called from a worker the dmabuf fd isn't closed and the ref
>> +	 * doesn't drop to zero on free.
>> +	 * If I use __close_fd() it's all fine, but that function is not exported.
>> +	 *
>> +	 * How do I get rid of this fd when in a worker/kernel thread?
>> +	 * The fd isn't used beyond this function.
>> +	 */
>> +//	WARN_ON(__close_fd(current->files, prime_args.fd));
> Hm, this isn't 100% what I had in mind as the sequence for generic buffer
> creation. Pseudo-code:
>
> 	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
> 	if (ret)
> 		goto err_free;
> 	
> 	gem_bo = drm_gem_object_lookup(client->file, dumb_args.handle);
>
> gives you _really_ directly the underlying gem_bo. Of course this doesn't
> work for non-gem based driver, but reality is that (almost) all of them
> are. And we will not accept any new drivers which aren't gem based. So
> ignoring vmwgfx for this drm_client work is imo perfectly fine. We should
> ofc keep the option in the fb helpers to use non-gem buffers (so that
> vmwgfx could switch over from their own in-driver fbdev helpers). All we
> need for that is to keep the fb_probe callback.
>
> Was there any other reason than vmwgfx for using prime buffers instead of
> just directly using gem?

The reason for using a prime buffer is that it gives me easy access to a
dma_buf which I use to get the virtual address (dma_buf_vmap) and for
mmap (dma_buf_mmap).

Would this stripped down version of drm_gem_prime_handle_to_fd() work?

struct dma_buf *drm_gem_to_dmabuf(struct drm_gem_object *obj)
{
     struct dma_buf *dmabuf;

     mutex_lock(&obj->dev->object_name_lock);
     /* re-export the original imported object */
     if (obj->import_attach) {
         dmabuf = obj->import_attach->dmabuf;
         get_dma_buf(dmabuf);
         goto out;
     }

     if (obj->dma_buf) {
         dmabuf = obj->dma_buf;
         get_dma_buf(dmabuf);
         goto out;
     }

     dmabuf = export_and_register_object(obj->dev, obj, 0);
out:
     mutex_unlock(&obj->dev->object_name_lock);

     return dmabuf;
}

Now I could do this:

     ret = drm_mode_create_dumb(dev, &dumb_args, file);

     obj = drm_gem_object_lookup(file, dumb_args.handle);

     dmabuf = drm_gem_to_dmabuf(obj);

     vaddr = dma_buf_vmap(dmabuf);


Noralf.
Daniel Vetter April 17, 2018, 8:08 a.m. UTC | #3
On Mon, Apr 16, 2018 at 05:58:23PM +0200, Noralf Trønnes wrote:
> 
> Den 16.04.2018 10.27, skrev Daniel Vetter:
> > On Sat, Apr 14, 2018 at 01:53:12PM +0200, Noralf Trønnes wrote:
> > > The modesetting code is already present, this adds the rest of the API.
> > Mentioning the TODO in the commit message would be good. Helps readers
> > like me who have an attention span measured in seconds :-)
> > 
> > Just commenting on the create_buffer leak here
> > 
> > > +static 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 = { };
> > > +	struct drm_prime_handle prime_args = { };
> > > +	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);
> > > +
> > > +	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_free;
> > > +
> > > +	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;
> > > +	}
> > > +
> > > +	/*
> > > +	 * If called from a worker the dmabuf fd isn't closed and the ref
> > > +	 * doesn't drop to zero on free.
> > > +	 * If I use __close_fd() it's all fine, but that function is not exported.
> > > +	 *
> > > +	 * How do I get rid of this fd when in a worker/kernel thread?
> > > +	 * The fd isn't used beyond this function.
> > > +	 */
> > > +//	WARN_ON(__close_fd(current->files, prime_args.fd));
> > Hm, this isn't 100% what I had in mind as the sequence for generic buffer
> > creation. Pseudo-code:
> > 
> > 	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
> > 	if (ret)
> > 		goto err_free;
> > 	
> > 	gem_bo = drm_gem_object_lookup(client->file, dumb_args.handle);
> > 
> > gives you _really_ directly the underlying gem_bo. Of course this doesn't
> > work for non-gem based driver, but reality is that (almost) all of them
> > are. And we will not accept any new drivers which aren't gem based. So
> > ignoring vmwgfx for this drm_client work is imo perfectly fine. We should
> > ofc keep the option in the fb helpers to use non-gem buffers (so that
> > vmwgfx could switch over from their own in-driver fbdev helpers). All we
> > need for that is to keep the fb_probe callback.
> > 
> > Was there any other reason than vmwgfx for using prime buffers instead of
> > just directly using gem?
> 
> The reason for using a prime buffer is that it gives me easy access to a
> dma_buf which I use to get the virtual address (dma_buf_vmap) and for
> mmap (dma_buf_mmap).

Ah yes, I missed that.

Wrt mmap, not sure we should use the dma-buf mmap or the dumb mmap. I
guess in the end it wont matter much really.

> 
> Would this stripped down version of drm_gem_prime_handle_to_fd() work?
> 
> struct dma_buf *drm_gem_to_dmabuf(struct drm_gem_object *obj)
> {
>     struct dma_buf *dmabuf;
> 
>     mutex_lock(&obj->dev->object_name_lock);
>     /* re-export the original imported object */
>     if (obj->import_attach) {
>         dmabuf = obj->import_attach->dmabuf;
>         get_dma_buf(dmabuf);
>         goto out;
>     }
> 
>     if (obj->dma_buf) {
>         dmabuf = obj->dma_buf;
>         get_dma_buf(dmabuf);
>         goto out;
>     }
> 
>     dmabuf = export_and_register_object(obj->dev, obj, 0);
> out:
>     mutex_unlock(&obj->dev->object_name_lock);
> 
>     return dmabuf;
> }
> 
> Now I could do this:
> 
>     ret = drm_mode_create_dumb(dev, &dumb_args, file);
> 
>     obj = drm_gem_object_lookup(file, dumb_args.handle);
> 
>     dmabuf = drm_gem_to_dmabuf(obj);
> 
>     vaddr = dma_buf_vmap(dmabuf);

Nah, if we need the dma-buf anyway, I'd try to go directly from the handle
to the dma-buf. So roughly:

	ret = drm_mode_create_dumb(dev, &dumb_args, file);
	
	dma_buf = drm_gem_prime_handle_to_dmabuf(file, dumb_args.handle);

See my reply to the ioctl wrapper patch for details.
-Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index bce1630a0db2..760f1795f812 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -8,7 +8,9 @@ 
  * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
  */
 
+#include <linux/dma-buf.h>
 #include <linux/list.h>
+#include <linux/mutex.h>
 #include <linux/slab.h>
 
 #include <drm/drm_atomic.h>
@@ -17,14 +19,280 @@ 
 #include <drm/drm_connector.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_device.h>
+#include <drm/drm_file.h>
 #include <drm/drm_modes.h>
 
+#include "drm_crtc_internal.h"
 #include "drm_internal.h"
 
 struct drm_client_display_offset {
 	int x, y;
 };
 
+static int drm_client_alloc_file(struct drm_client_dev *client)
+{
+	struct drm_device *dev = client->dev;
+	struct drm_file *file;
+
+	file = drm_file_alloc(dev->primary);
+	if (IS_ERR(file))
+		return PTR_ERR(file);
+
+	drm_dev_get(dev);
+
+	mutex_lock(&dev->filelist_mutex);
+	list_add(&file->lhead, &dev->filelist_internal);
+	mutex_unlock(&dev->filelist_mutex);
+
+	client->file = file;
+
+	return 0;
+}
+
+static void drm_client_free_file(struct drm_client_dev *client)
+{
+	struct drm_device *dev = client->dev;
+
+	mutex_lock(&dev->filelist_mutex);
+	list_del(&client->file->lhead);
+	mutex_unlock(&dev->filelist_mutex);
+
+	drm_file_free(client->file);
+	drm_dev_put(dev);
+}
+
+struct drm_client_dev *
+drm_client_new(struct drm_device *dev, const struct drm_client_funcs *funcs)
+{
+	struct drm_client_dev *client;
+	int ret;
+
+	if (WARN_ON(!funcs->name))
+		return ERR_PTR(-EINVAL);
+
+	client = kzalloc(sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return ERR_PTR(-ENOMEM);
+
+	client->dev = dev;
+	client->funcs = funcs;
+
+	ret = drm_client_alloc_file(client);
+	if (ret) {
+		kfree(client);
+		return ERR_PTR(ret);
+	}
+
+	mutex_lock(&dev->clientlist_mutex);
+	list_add(&client->list, &dev->clientlist);
+	mutex_unlock(&dev->clientlist_mutex);
+
+	return client;
+}
+EXPORT_SYMBOL(drm_client_new);
+
+struct drm_client_dev *
+drm_client_new_from_id(unsigned int dev_id, const struct drm_client_funcs *funcs)
+{
+	struct drm_client_dev *client;
+	struct drm_minor *minor;
+
+	minor = drm_minor_acquire(dev_id);
+	if (IS_ERR(minor))
+		return ERR_CAST(minor);
+
+	client = drm_client_new(minor->dev, funcs);
+
+	drm_minor_release(minor);
+
+	return client;
+}
+EXPORT_SYMBOL(drm_client_new_from_id);
+
+/**
+ * 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.
+ *
+ * Note:
+ * If the client can't release it's resources on remove, it needs to hold a
+ * reference on the driver module to prevent the code from going away.
+ */
+void drm_client_free(struct drm_client_dev *client)
+{
+	DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
+	drm_client_free_file(client);
+	kfree(client);
+}
+EXPORT_SYMBOL(drm_client_free);
+
+static void drm_client_remove_locked(struct drm_client_dev *client)
+{
+	list_del(&client->list);
+
+	if (!client->funcs->remove || !client->funcs->remove(client))
+		drm_client_free(client);
+}
+
+static void drm_client_remove_safe(struct drm_device *dev,
+				   struct drm_client_dev *client)
+{
+	struct drm_client_dev *iter;
+
+	mutex_lock(&dev->clientlist_mutex);
+	list_for_each_entry(iter, &dev->clientlist, list) {
+		if (iter == client) {
+			drm_client_remove_locked(client);
+			break;
+		}
+	}
+	mutex_unlock(&dev->clientlist_mutex);
+}
+
+/**
+ * drm_client_remove - Remove client
+ * @client: Client
+ *
+ * Remove a client.
+ */
+void drm_client_remove(struct drm_client_dev *client)
+{
+	struct drm_device *dev;
+
+	if (!client)
+		return;
+
+	dev = client->dev;
+	drm_dev_get(dev);
+	drm_client_remove_safe(dev, client);
+	drm_dev_put(dev);
+}
+EXPORT_SYMBOL(drm_client_remove);
+
+struct drm_client_remove_defer {
+	struct list_head list;
+	struct drm_device *dev;
+	struct drm_client_dev *client;
+};
+
+static LIST_HEAD(drm_client_remove_defer_list);
+static DEFINE_MUTEX(drm_client_remove_defer_list_lock);
+
+static void drm_client_remove_defer_work_fn(struct work_struct *work)
+{
+	struct drm_client_remove_defer *defer, *tmp;
+
+	mutex_lock(&drm_client_remove_defer_list_lock);
+	list_for_each_entry_safe(defer, tmp, &drm_client_remove_defer_list, list) {
+		drm_client_remove_safe(defer->dev, defer->client);
+		drm_dev_put(defer->dev);
+		list_del(&defer->list);
+		kfree(defer);
+	}
+	mutex_unlock(&drm_client_remove_defer_list_lock);
+}
+
+static DECLARE_WORK(drm_client_remove_defer_work, drm_client_remove_defer_work_fn);
+
+/**
+ * drm_client_remove_defer - Deferred client removal
+ * @client: Client
+ *
+ * Defer client removal to a worker. This makes it possible for a client running
+ * in a worker to remove itself.
+ *
+ * Returns:
+ * Zero on success, or -ENOMEM on allocation failure.
+ */
+int drm_client_remove_defer(struct drm_client_dev *client)
+{
+	struct drm_client_remove_defer *defer;
+
+	if (!client)
+		return 0;
+
+	defer = kzalloc(sizeof(*defer), GFP_KERNEL);
+	if (!defer)
+		return -ENOMEM;
+
+	defer->dev = client->dev;
+	defer->client = client;
+	drm_dev_get(client->dev);
+
+	mutex_lock(&drm_client_remove_defer_list_lock);
+	list_add(&defer->list, &drm_client_remove_defer_list);
+	mutex_unlock(&drm_client_remove_defer_list_lock);
+
+	schedule_work(&drm_client_remove_defer_work);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_client_remove_defer);
+
+void drm_client_init(void)
+{
+}
+
+void drm_client_exit(void)
+{
+	flush_work(&drm_client_remove_defer_work);
+}
+
+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(&dev->clientlist_mutex);
+	list_for_each_entry_safe(client, tmp, &dev->clientlist, list)
+		drm_client_remove_locked(client);
+	mutex_unlock(&dev->clientlist_mutex);
+}
+
+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(&dev->clientlist_mutex);
+	list_for_each_entry(client, &dev->clientlist, list) {
+		if (!client->funcs->hotplug)
+			continue;
+
+		ret = client->funcs->hotplug(client);
+		DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->funcs->name, ret);
+	}
+	mutex_unlock(&dev->clientlist_mutex);
+}
+EXPORT_SYMBOL(drm_client_dev_hotplug);
+
+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(&dev->clientlist_mutex);
+	list_for_each_entry(client, &dev->clientlist, list) {
+		if (!client->funcs->lastclose)
+			continue;
+
+		ret = client->funcs->lastclose(client);
+		DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->funcs->name, ret);
+	}
+	mutex_unlock(&dev->clientlist_mutex);
+}
+
 /**
  * drm_client_display_create() - Create display structure
  * @dev: DRM device
@@ -348,6 +616,90 @@  int drm_client_display_restore(struct drm_client_display *display, bool force)
 }
 EXPORT_SYMBOL(drm_client_display_restore);
 
+/**
+ * drm_client_display_commit_mode - Commit a mode/fb to the CRTC(s)
+ * @display: Client display
+ * @fb: Framebuffer (if NULL the current fb is used)
+ * @mode: Display mode (if NULL the current mode is used)
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int drm_client_display_commit(struct drm_client_display *display,
+			      struct drm_framebuffer *fb, struct drm_display_mode *mode)
+{
+	struct drm_display_mode *use_mode = NULL;
+	struct drm_mode_set *modeset;
+	unsigned int count = 0;
+
+	if (mode) {
+		struct drm_display_mode *iter;
+
+		drm_client_display_for_each_mode(iter, display) {
+			if (!use_mode && drm_mode_equal(iter, mode))
+				use_mode = iter;
+			count++;
+		}
+
+		if (!use_mode)
+			return -EINVAL;
+
+		/*
+		 * Don't actually set the mode in the single mode case since it
+		 * might be a tiled display which consists of multiple modes.
+		 * Just keep the current mode.
+		 */
+		if (count == 1)
+			use_mode = NULL;
+	}
+
+	count = 0;
+	drm_client_display_for_each_modeset(modeset, display) {
+		if (!modeset->num_connectors)
+			continue;
+
+		if (fb)
+			modeset->fb = fb;
+
+		if (use_mode) {
+			if (WARN_ON(++count > 1))
+				return -EINVAL;
+
+			if (modeset->mode)
+				drm_mode_destroy(display->dev, modeset->mode);
+			modeset->mode = drm_mode_duplicate(display->dev, use_mode);
+			if (!modeset->mode)
+				return -ENOMEM;
+		}
+	}
+
+	return drm_client_display_restore(display, false);
+}
+EXPORT_SYMBOL(drm_client_display_commit);
+
+struct drm_framebuffer *drm_client_display_current_fb(struct drm_client_display *display)
+{
+	struct drm_mode_set *modeset;
+
+	drm_client_display_for_each_modeset(modeset, display) {
+		struct drm_crtc *crtc = modeset->crtc;
+		struct drm_framebuffer *fb = NULL;
+
+		drm_modeset_lock(&crtc->primary->mutex, NULL);
+		if (crtc->primary->state && crtc->primary->state->fb)
+			fb = crtc->primary->state->fb;
+		else if (!crtc->primary->state && crtc->primary->fb)
+			fb = crtc->primary->fb;
+		drm_modeset_unlock(&crtc->primary->mutex);
+
+		if (fb)
+			return fb;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL(drm_client_display_current_fb);
+
 static void drm_client_display_dpms_legacy(struct drm_client_display *display, int dpms_mode)
 {
 	struct drm_device *dev = display->dev;
@@ -869,3 +1221,224 @@  drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int
 	return display;
 }
 EXPORT_SYMBOL(drm_client_find_display);
+
+static 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);
+	kfree(buffer);
+}
+
+/* For testing __close_fd() */
+#include <linux/fdtable.h>
+
+static 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 = { };
+	struct drm_prime_handle prime_args = { };
+	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);
+
+	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_free;
+
+	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;
+	}
+
+	/*
+	 * If called from a worker the dmabuf fd isn't closed and the ref
+	 * doesn't drop to zero on free.
+	 * If I use __close_fd() it's all fine, but that function is not exported.
+	 *
+	 * How do I get rid of this fd when in a worker/kernel thread?
+	 * The fd isn't used beyond this function.
+	 */
+//	WARN_ON(__close_fd(current->files, prime_args.fd));
+
+	pr_info("%s: PF_KTHREAD=%u\n", __func__, !!(current->flags & PF_KTHREAD));
+
+	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_free:
+	kfree(buffer);
+
+	return ERR_PTR(ret);
+}
+
+static int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
+{
+	int ret;
+
+	if (!buffer || !buffer->fb)
+		return 0;
+
+	ret = drm_mode_rmfb(buffer->client->dev, buffer->fb->base.id, buffer->client->file);
+	if (ret)
+		DRM_DEV_ERROR(buffer->client->dev->dev,
+			      "Error removing FB:%u (%d)\n", buffer->fb->base.id, ret);
+
+	buffer->fb = NULL;
+
+	return 0;
+}
+
+static int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
+				   struct drm_display_mode *mode)
+{
+	struct drm_client_dev *client = buffer->client;
+	struct drm_mode_fb_cmd2 fb_req = { };
+	int ret;
+
+	if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
+		return -EINVAL;
+
+	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;
+
+	ret = drm_mode_addfb2(client->dev, &fb_req, client->file, client->funcs->name);
+	if (ret)
+		return ret;
+
+	buffer->fb = drm_framebuffer_lookup(client->dev, buffer->client->file, fb_req.fb_id);
+	if (WARN_ON(!buffer->fb))
+		return -ENOENT;
+
+	/* drop the reference we picked up in framebuffer lookup */
+	drm_framebuffer_put(buffer->fb);
+
+	return 0;
+}
+
+/**
+ * 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_display_mode *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);
+
+int drm_client_framebuffer_flush(struct drm_client_buffer *buffer,
+				 struct drm_clip_rect *rect)
+{
+	if (!buffer->fb || !buffer->fb->funcs->dirty)
+		return 0;
+
+	return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
+					0, 0, rect, rect ? 1 : 0);
+}
+EXPORT_SYMBOL(drm_client_framebuffer_flush);
+
+#ifdef CONFIG_DEBUG_FS
+static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct drm_printer p = drm_seq_file_printer(m);
+	struct drm_client_dev *client;
+
+	mutex_lock(&dev->clientlist_mutex);
+	list_for_each_entry(client, &dev->clientlist, list)
+		drm_printf(&p, "%s\n", client->funcs->name);
+	mutex_unlock(&dev->clientlist_mutex);
+
+	return 0;
+}
+
+static const struct drm_info_list drm_client_debugfs_list[] = {
+	{ "internal_clients", drm_client_debugfs_internal_clients, 0 },
+};
+
+int drm_client_debugfs_init(struct drm_minor *minor)
+{
+	return drm_debugfs_create_files(drm_client_debugfs_list,
+					ARRAY_SIZE(drm_client_debugfs_list),
+					minor->debugfs_root, minor);
+}
+#endif
diff --git a/drivers/gpu/drm/drm_debugfs.c b/drivers/gpu/drm/drm_debugfs.c
index b2482818fee8..50a20bfc07ea 100644
--- a/drivers/gpu/drm/drm_debugfs.c
+++ b/drivers/gpu/drm/drm_debugfs.c
@@ -28,6 +28,7 @@ 
 #include <linux/slab.h>
 #include <linux/export.h>
 
+#include <drm/drm_client.h>
 #include <drm/drm_debugfs.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_atomic.h>
@@ -164,6 +165,12 @@  int drm_debugfs_init(struct drm_minor *minor, int minor_id,
 			DRM_ERROR("Failed to create framebuffer debugfs file\n");
 			return ret;
 		}
+
+		ret = drm_client_debugfs_init(minor);
+		if (ret) {
+			DRM_ERROR("Failed to create client debugfs file\n");
+			return ret;
+		}
 	}
 
 	if (dev->driver->debugfs_init) {
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index 32a83b41ab61..6f21bafb29be 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -34,6 +34,7 @@ 
 #include <linux/slab.h>
 #include <linux/srcu.h>
 
+#include <drm/drm_client.h>
 #include <drm/drm_drv.h>
 #include <drm/drmP.h>
 
@@ -507,6 +508,8 @@  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->clientlist);
 	INIT_LIST_HEAD(&dev->ctxlist);
 	INIT_LIST_HEAD(&dev->vmalist);
 	INIT_LIST_HEAD(&dev->maplist);
@@ -516,6 +519,7 @@  int drm_dev_init(struct drm_device *dev,
 	spin_lock_init(&dev->event_lock);
 	mutex_init(&dev->struct_mutex);
 	mutex_init(&dev->filelist_mutex);
+	mutex_init(&dev->clientlist_mutex);
 	mutex_init(&dev->ctxlist_mutex);
 	mutex_init(&dev->master_mutex);
 
@@ -572,6 +576,7 @@  int drm_dev_init(struct drm_device *dev,
 err_free:
 	mutex_destroy(&dev->master_mutex);
 	mutex_destroy(&dev->ctxlist_mutex);
+	mutex_destroy(&dev->clientlist_mutex);
 	mutex_destroy(&dev->filelist_mutex);
 	mutex_destroy(&dev->struct_mutex);
 	return ret;
@@ -607,6 +612,7 @@  void drm_dev_fini(struct drm_device *dev)
 
 	mutex_destroy(&dev->master_mutex);
 	mutex_destroy(&dev->ctxlist_mutex);
+	mutex_destroy(&dev->clientlist_mutex);
 	mutex_destroy(&dev->filelist_mutex);
 	mutex_destroy(&dev->struct_mutex);
 	kfree(dev->unique);
@@ -862,6 +868,8 @@  void drm_dev_unregister(struct drm_device *dev)
 {
 	struct drm_map_list *r_list, *list_temp;
 
+	drm_client_dev_unregister(dev);
+
 	if (drm_core_check_feature(dev, DRIVER_LEGACY))
 		drm_lastclose(dev);
 
@@ -968,6 +976,7 @@  static const struct file_operations drm_stub_fops = {
 
 static void drm_core_exit(void)
 {
+	drm_client_exit();
 	unregister_chrdev(DRM_MAJOR, "drm");
 	debugfs_remove(drm_debugfs_root);
 	drm_sysfs_destroy();
@@ -1001,6 +1010,8 @@  static int __init drm_core_init(void)
 	if (ret < 0)
 		goto error;
 
+	drm_client_init();
+
 	drm_core_init_complete = true;
 
 	DRM_DEBUG("Initialized\n");
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 527743394150..26be57e28a9d 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/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
index 1c5b5ce1fd7f..1fc066c41861 100644
--- a/drivers/gpu/drm/drm_sysfs.c
+++ b/drivers/gpu/drm/drm_sysfs.c
@@ -18,6 +18,7 @@ 
 #include <linux/err.h>
 #include <linux/export.h>
 
+#include <drm/drm_client.h>
 #include <drm/drm_sysfs.h>
 #include <drm/drmP.h>
 #include "drm_internal.h"
@@ -320,6 +321,24 @@  void drm_sysfs_hotplug_event(struct drm_device *dev)
 }
 EXPORT_SYMBOL(drm_sysfs_hotplug_event);
 
+static ssize_t remove_internal_clients_store(struct device *dev,
+					     struct device_attribute *attr,
+					     const char *buf, size_t len)
+{
+	struct drm_minor *minor = dev_get_drvdata(dev);
+
+	drm_client_dev_unregister(minor->dev);
+
+	return len;
+}
+static DEVICE_ATTR_WO(remove_internal_clients);
+
+static struct attribute *minor_attrs[] = {
+	&dev_attr_remove_internal_clients.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(minor);
+
 static void drm_sysfs_release(struct device *dev)
 {
 	kfree(dev);
@@ -347,6 +366,7 @@  struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
 	kdev->class = drm_class;
 	kdev->type = &drm_sysfs_device_minor;
 	kdev->parent = minor->dev->dev;
+	kdev->groups = minor_groups;
 	kdev->release = drm_sysfs_release;
 	dev_set_drvdata(kdev, minor);
 
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index 524f793d6e7b..6fd2fcaae826 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -5,13 +5,91 @@ 
 
 #include <linux/types.h>
 
+struct drm_clip_rect;
 struct drm_connector;
 struct drm_crtc;
 struct drm_device;
 struct drm_display_mode;
+struct drm_framebuffer;
+struct drm_minor;
 struct drm_mode_set;
 struct drm_plane;
 
+struct drm_client_dev;
+
+/**
+ * struct drm_client_funcs - DRM client callbacks
+ */
+struct drm_client_funcs {
+	/**
+	 * @name:
+	 *
+	 * Name of the client. Mandatory.
+	 */
+	const char *name;
+
+	/**
+	 * @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);
+};
+
+/**
+ * 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 drm_file *file;
+	unsigned int file_ref_count;
+	void *private;
+};
+
+struct drm_client_dev *
+drm_client_new(struct drm_device *dev, const struct drm_client_funcs *funcs);
+struct drm_client_dev *
+drm_client_new_from_id(unsigned int dev_id, const struct drm_client_funcs *funcs);
+void drm_client_remove(struct drm_client_dev *client);
+int drm_client_remove_defer(struct drm_client_dev *client);
+void drm_client_free(struct drm_client_dev *client);
+
+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);
+
+void drm_client_init(void);
+void drm_client_exit(void);
+
+int drm_client_debugfs_init(struct drm_minor *minor);
+
 /**
  * struct drm_client_display - DRM client display
  */
@@ -76,4 +154,29 @@  drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int
 #define drm_client_display_for_each_mode(mode, display) \
 	list_for_each_entry(mode, &display->modes, head)
 
+int drm_client_display_commit(struct drm_client_display *display,
+			      struct drm_framebuffer *fb, struct drm_display_mode *mode);
+struct drm_framebuffer *drm_client_display_current_fb(struct drm_client_display *display);
+
+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;
+	struct drm_framebuffer *fb;
+};
+
+struct drm_client_buffer *
+drm_client_framebuffer_create(struct drm_client_dev *client,
+			      struct drm_display_mode *mode, u32 format);
+void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
+
+int drm_client_framebuffer_flush(struct drm_client_buffer *buffer,
+				 struct drm_clip_rect *rect);
+
 #endif
diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
index 3a0eac2885b7..17edadf8b691 100644
--- a/include/drm/drm_device.h
+++ b/include/drm/drm_device.h
@@ -74,6 +74,10 @@  struct drm_device {
 
 	struct mutex filelist_mutex;
 	struct list_head filelist;
+	struct list_head filelist_internal;
+
+	struct mutex clientlist_mutex;
+	struct list_head clientlist;
 
 	/** \name Memory management */
 	/*@{ */