diff mbox

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

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

Commit Message

Noralf Trønnes April 12, 2018, 4:12 p.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(+)
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 */
 	/*@{ */