All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Noralf Trønnes" <noralf@tronnes.org>
To: dri-devel@lists.freedesktop.org
Subject: [RFC 2/6] drm: Add support for userspace drivers
Date: Wed,  4 Jan 2017 14:34:38 +0100	[thread overview]
Message-ID: <20170104133442.4534-3-noralf@tronnes.org> (raw)
In-Reply-To: <20170104133442.4534-1-noralf@tronnes.org>

Add support for writing drm userspace drivers.

Userspace driver usage:
Open /dev/udrm
Ioctl create drm driver/device passing in mode, formats and optional
    dma-buf as transfer buffer
Read/poll for events:
    framebuffer: create, destroy, dirty
    pipe: enable, disable
Write back status value from the event execution
Closing file will delete the drm driver.

The reason for doing buffer copy in the kernel is that on a Raspberry Pi
copying (actually reading) a mmap'ed 150k dma-buf in userspace took 32ms
while in-kernel was 13ms.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/Kconfig          |   2 +
 drivers/gpu/drm/Makefile         |   1 +
 drivers/gpu/drm/udrm/Kconfig     |   9 +
 drivers/gpu/drm/udrm/Makefile    |   4 +
 drivers/gpu/drm/udrm/udrm-dev.c  | 276 +++++++++++++++++++++++++++++
 drivers/gpu/drm/udrm/udrm-drv.c  | 324 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/udrm/udrm-fb.c   | 369 +++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/udrm/udrm-pipe.c | 170 ++++++++++++++++++
 drivers/gpu/drm/udrm/udrm.h      |  84 +++++++++
 include/uapi/drm/udrm.h          |  78 +++++++++
 10 files changed, 1317 insertions(+)
 create mode 100644 drivers/gpu/drm/udrm/Kconfig
 create mode 100644 drivers/gpu/drm/udrm/Makefile
 create mode 100644 drivers/gpu/drm/udrm/udrm-dev.c
 create mode 100644 drivers/gpu/drm/udrm/udrm-drv.c
 create mode 100644 drivers/gpu/drm/udrm/udrm-fb.c
 create mode 100644 drivers/gpu/drm/udrm/udrm-pipe.c
 create mode 100644 drivers/gpu/drm/udrm/udrm.h
 create mode 100644 include/uapi/drm/udrm.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 483059a..b351798 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -223,6 +223,8 @@ source "drivers/gpu/drm/hisilicon/Kconfig"

 source "drivers/gpu/drm/mediatek/Kconfig"

+source "drivers/gpu/drm/udrm/Kconfig"
+
 # Keep legacy drivers last

 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 25c7204..29175bc 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -86,3 +86,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
 obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
 obj-$(CONFIG_DRM_ARCPGU)+= arc/
 obj-y			+= hisilicon/
+obj-y			+= udrm/
diff --git a/drivers/gpu/drm/udrm/Kconfig b/drivers/gpu/drm/udrm/Kconfig
new file mode 100644
index 0000000..41faa4b
--- /dev/null
+++ b/drivers/gpu/drm/udrm/Kconfig
@@ -0,0 +1,9 @@
+config DRM_USER
+	tristate "Support for userspace DRM drivers"
+	depends on DRM
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select VIDEOMODE_HELPERS
+	help
+	  Choose this option if you have a userspace DRM driver.
+	  If M is selected the module will be called tinydrm.
diff --git a/drivers/gpu/drm/udrm/Makefile b/drivers/gpu/drm/udrm/Makefile
new file mode 100644
index 0000000..9eb8c27
--- /dev/null
+++ b/drivers/gpu/drm/udrm/Makefile
@@ -0,0 +1,4 @@
+ccflags-y += -I$(src)/include
+
+udrm-y := udrm-dev.o udrm-drv.o udrm-fb.o udrm-pipe.o
+obj-$(CONFIG_DRM_USER) += udrm.o
diff --git a/drivers/gpu/drm/udrm/udrm-dev.c b/drivers/gpu/drm/udrm/udrm-dev.c
new file mode 100644
index 0000000..eb19b7b
--- /dev/null
+++ b/drivers/gpu/drm/udrm/udrm-dev.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/completion.h>
+#include <linux/dma-buf.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <uapi/drm/udrm.h>
+
+#include "udrm.h"
+
+static struct miscdevice udrm_misc;
+
+int udrm_send_event(struct udrm_device *udev, void *ev_in)
+{
+	struct udrm_event *ev = ev_in;
+	unsigned long time_left;
+	int ret = 0;
+
+	mutex_lock(&udev->dev_lock);
+
+	DRM_DEBUG("IN ev->type=%u, ev->length=%u\n", ev->type, ev->length);
+
+	if (!udev->initialized) {
+		DRM_ERROR("Not initialized\n");
+		ret = -ENODEV;
+		goto out_unlock;
+	}
+
+	ev = kmemdup(ev, ev->length, GFP_KERNEL);
+	if (!ev) {
+		ret = -ENOMEM;
+		goto out_unlock;
+	}
+
+	reinit_completion(&udev->completion);
+
+	ret = mutex_lock_interruptible(&udev->mutex);
+	if (ret) {
+		kfree(ev);
+		goto out_unlock;
+	}
+	udev->ev = ev;
+	mutex_unlock(&udev->mutex);
+
+	wake_up_interruptible(&udev->waitq);
+
+	time_left = wait_for_completion_timeout(&udev->completion, 5 * HZ);
+	ret = udev->event_ret;
+	if (!time_left) {
+		DRM_ERROR("timeout waiting for reply\n");
+		ret = -ETIMEDOUT;
+	}
+
+out_unlock:
+	mutex_unlock(&udev->dev_lock);
+
+	DRM_DEBUG("OUT ret=%d, event_ret=%d\n", ret, udev->event_ret);
+
+	return ret;
+}
+
+static void udrm_release_work(struct work_struct *work)
+{
+	struct udrm_device *udev = container_of(work, struct udrm_device,
+						    release_work);
+	struct drm_device *drm = &udev->drm;
+
+	//drm_device_set_unplugged(drm);
+
+	udev->initialized = false;
+	udev->event_ret = -ENODEV;
+	complete(&udev->completion);
+
+	while (drm->open_count) {
+		DRM_DEBUG_KMS("open_count=%d\n", drm->open_count);
+		msleep(1000);
+	}
+
+	udrm_drm_unregister(udev);
+}
+
+static int udrm_open(struct inode *inode, struct file *file)
+{
+	struct udrm_device *udev;
+
+	udev = kzalloc(sizeof(*udev), GFP_KERNEL);
+	if (!udev)
+		return -ENOMEM;
+
+	mutex_init(&udev->mutex);
+	init_waitqueue_head(&udev->waitq);
+	init_completion(&udev->completion);
+	idr_init(&udev->idr);
+	INIT_WORK(&udev->release_work, udrm_release_work);
+
+	file->private_data = udev;
+	nonseekable_open(inode, file);
+
+	return 0;
+}
+
+static ssize_t udrm_write(struct file *file, const char __user *buffer,
+			   size_t count, loff_t *ppos)
+{
+	struct udrm_device *udev = file->private_data;
+	int ret, event_ret;
+
+	if (!udev->initialized)
+		return -EINVAL;
+
+	if (!count)
+		return 0;
+
+	if (count != sizeof(int))
+		return -EINVAL;
+
+	if (copy_from_user(&event_ret, buffer, sizeof(int)))
+		return -EFAULT;
+
+	ret = mutex_lock_interruptible(&udev->mutex);
+	if (ret)
+		return ret;
+
+	udev->event_ret = event_ret;
+	complete(&udev->completion);
+
+	mutex_unlock(&udev->mutex);
+
+	return count;
+}
+
+static ssize_t udrm_read(struct file *file, char __user *buffer, size_t count,
+			  loff_t *ppos)
+{
+	struct udrm_device *udev = file->private_data;
+	ssize_t ret;
+
+	if (!count)
+		return 0;
+
+	do {
+		ret = mutex_lock_interruptible(&udev->mutex);
+		if (ret)
+			return ret;
+
+		if (!udev->ev && (file->f_flags & O_NONBLOCK)) {
+			ret = -EAGAIN;
+		} else if (udev->ev) {
+			if (count < udev->ev->length)
+				ret = -EINVAL;
+			else if (copy_to_user(buffer, udev->ev, udev->ev->length))
+				ret = -EFAULT;
+			else
+				ret = udev->ev->length;
+			kfree(udev->ev);
+			udev->ev = NULL;
+		}
+
+		mutex_unlock(&udev->mutex);
+
+		if (ret)
+			break;
+
+		if (!(file->f_flags & O_NONBLOCK))
+			ret = wait_event_interruptible(udev->waitq, udev->ev);
+	} while (ret == 0);
+
+	return ret;
+}
+
+static unsigned int udrm_poll(struct file *file, poll_table *wait)
+{
+	struct udrm_device *udev = file->private_data;
+
+	poll_wait(file, &udev->waitq, wait);
+
+	if (udev->ev)
+		return POLLIN | POLLRDNORM;
+
+	return 0;
+}
+
+static int udrm_release(struct inode *inode, struct file *file)
+{
+	struct udrm_device *udev = file->private_data;
+
+	if (udev->initialized)
+		schedule_work(&udev->release_work);
+
+	return 0;
+}
+
+static long udrm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct udrm_device *udev = file->private_data;
+	struct udrm_dev_create dev_create;
+	uint32_t *formats;
+	int ret;
+
+	switch (cmd) {
+	case UDRM_DEV_CREATE:
+		if (copy_from_user(&dev_create, (void __user *)arg,
+				   sizeof(dev_create)))
+			return -EFAULT;
+
+		if (!dev_create.formats || !dev_create.num_formats)
+			return -EINVAL;
+
+		formats = memdup_user((void __user *)
+				      (uintptr_t) dev_create.formats,
+				      dev_create.num_formats * sizeof(*formats));
+		if (IS_ERR(formats))
+			return PTR_ERR(formats);
+
+		udev->initialized = true;
+		ret = udrm_drm_register(udev, &dev_create, formats,
+					dev_create.num_formats);
+		kfree(formats);
+		if (ret) {
+			udev->initialized = false;
+			return ret;
+		}
+
+		if (copy_to_user((void __user *)arg, &dev_create,
+				 sizeof(dev_create)))
+			return -EFAULT;
+		break;
+	default:
+		ret = -ENOTTY;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct file_operations udrm_fops = {
+	.owner		= THIS_MODULE,
+	.open		= udrm_open,
+	.release	= udrm_release,
+	.read		= udrm_read,
+	.write		= udrm_write,
+	.poll		= udrm_poll,
+
+	.unlocked_ioctl	= udrm_ioctl,
+/* FIXME
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= udrm_compat_ioctl,
+#endif
+*/
+	.llseek		= no_llseek,
+};
+
+static struct miscdevice udrm_misc = {
+	.fops		= &udrm_fops,
+	.minor		= MISC_DYNAMIC_MINOR,
+	.name		= "udrm",
+};
+module_misc_device(udrm_misc);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_DESCRIPTION("Userspace driver support for DRM");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/udrm/udrm-drv.c b/drivers/gpu/drm/udrm/udrm-drv.c
new file mode 100644
index 0000000..c3363c3
--- /dev/null
+++ b/drivers/gpu/drm/udrm/udrm-drv.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <linux/dma-buf.h>
+
+#include <uapi/drm/udrm.h>
+
+#include "udrm.h"
+
+static void udrm_lastclose(struct drm_device *drm)
+{
+	struct udrm_device *udev = drm_to_udrm(drm);
+
+	DRM_DEBUG_KMS("initialized=%u, fbdev_used=%u\n", udev->initialized,
+		      udev->fbdev_used);
+
+	if (udev->fbdev_used)
+		drm_fbdev_cma_restore_mode(udev->fbdev_cma);
+	else
+		drm_crtc_force_disable_all(drm);
+}
+
+static void udrm_gem_cma_free_object(struct drm_gem_object *gem_obj)
+{
+	if (gem_obj->import_attach) {
+		struct drm_gem_cma_object *cma_obj;
+
+		cma_obj = to_drm_gem_cma_obj(gem_obj);
+		dma_buf_vunmap(gem_obj->import_attach->dmabuf, cma_obj->vaddr);
+		cma_obj->vaddr = NULL;
+	}
+
+	drm_gem_cma_free_object(gem_obj);
+}
+
+static struct drm_gem_object *
+udrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
+				      struct dma_buf_attachment *attach,
+				      struct sg_table *sgt)
+{
+	struct drm_gem_cma_object *cma_obj;
+	struct drm_gem_object *obj;
+	void *vaddr;
+
+	vaddr = dma_buf_vmap(attach->dmabuf);
+	if (!vaddr) {
+		DRM_ERROR("Failed to vmap PRIME buffer\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	obj = drm_gem_cma_prime_import_sg_table(drm, attach, sgt);
+	if (IS_ERR(obj)) {
+		dma_buf_vunmap(attach->dmabuf, vaddr);
+		return obj;
+	}
+
+	cma_obj = to_drm_gem_cma_obj(obj);
+	cma_obj->vaddr = vaddr;
+
+	return obj;
+}
+
+static int udrm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data,
+					     struct drm_file *file_priv)
+{
+	struct drm_prime_handle *args = data;
+
+	/* FIXME: only the userspace driver should use this */
+
+	/* check flags are valid */
+	if (args->flags & ~(DRM_CLOEXEC | DRM_RDWR))
+		return -EINVAL;
+
+	return dev->driver->prime_handle_to_fd(dev, file_priv, args->handle,
+					       args->flags, &args->fd);
+}
+
+static const struct drm_ioctl_desc udrm_ioctls[] = {
+	DRM_IOCTL_DEF_DRV(UDRM_PRIME_HANDLE_TO_FD, udrm_prime_handle_to_fd_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+};
+
+static const struct file_operations udrm_drm_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.release	= drm_release,
+	.unlocked_ioctl	= drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= drm_compat_ioctl,
+#endif
+	.poll		= drm_poll,
+	.read		= drm_read,
+	.llseek		= no_llseek,
+	.mmap		= drm_gem_cma_mmap,
+};
+
+static void udrm_dirty_work(struct work_struct *work)
+{
+	struct udrm_device *udev = container_of(work, struct udrm_device,
+						   dirty_work);
+	struct drm_framebuffer *fb = udev->pipe.plane.fb;
+	struct drm_crtc *crtc = &udev->pipe.crtc;
+
+	if (fb)
+		fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
+
+	if (udev->event) {
+		DRM_DEBUG_KMS("crtc event\n");
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, udev->event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+		udev->event = NULL;
+	}
+}
+
+static const struct drm_mode_config_funcs udrm_mode_config_funcs = {
+	.fb_create = udrm_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int udrm_drm_init(struct udrm_device *udev, char *drv_name)
+{
+	struct drm_driver *drv = &udev->driver;
+	struct drm_device *drm = &udev->drm;
+	int ret;
+
+	drv->name = kstrdup(drv_name, GFP_KERNEL);
+	if (!drv->name)
+		return -ENOMEM;
+
+	drv->driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+				  DRIVER_ATOMIC;
+	drv->gem_free_object		= udrm_gem_cma_free_object;
+	drv->gem_vm_ops			= &drm_gem_cma_vm_ops;
+	drv->prime_handle_to_fd		= drm_gem_prime_handle_to_fd;
+	drv->prime_fd_to_handle		= drm_gem_prime_fd_to_handle;
+	drv->gem_prime_import		= drm_gem_prime_import;
+	drv->gem_prime_export		= drm_gem_prime_export;
+	drv->gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table;
+	drv->gem_prime_import_sg_table	= udrm_gem_cma_prime_import_sg_table;
+	drv->gem_prime_vmap		= drm_gem_cma_prime_vmap;
+	drv->gem_prime_vunmap		= drm_gem_cma_prime_vunmap;
+	drv->gem_prime_mmap		= drm_gem_cma_prime_mmap;
+	drv->dumb_create		= drm_gem_cma_dumb_create;
+	drv->dumb_map_offset		= drm_gem_cma_dumb_map_offset;
+	drv->dumb_destroy		= drm_gem_dumb_destroy;
+	drv->fops			= &udrm_drm_fops;
+	drv->lastclose			= udrm_lastclose;
+
+	drv->ioctls		= udrm_ioctls;
+	drv->num_ioctls		= ARRAY_SIZE(udrm_ioctls);
+
+	drv->desc		= "DRM userspace driver support";
+	drv->date		= "20161119";
+	drv->major		= 1;
+	drv->minor		= 0;
+
+	INIT_WORK(&udev->dirty_work, udrm_dirty_work);
+	mutex_init(&udev->dev_lock);
+
+	ret = drm_dev_init(drm, drv, NULL);
+	if (ret)
+		return ret;
+
+	drm_mode_config_init(drm);
+	drm->mode_config.funcs = &udrm_mode_config_funcs;
+
+	return 0;
+}
+
+static void udrm_drm_fini(struct udrm_device *udev)
+{
+	struct drm_device *drm = &udev->drm;
+
+	DRM_DEBUG_KMS("udrm_drm_fini\n");
+
+	mutex_destroy(&udev->dev_lock);
+	drm_mode_config_cleanup(drm);
+	drm_dev_unref(drm);
+}
+
+static int udrm_buf_get(struct udrm_device *udev, int fd, u32 mode,
+			   uint32_t *formats, unsigned int num_formats)
+{
+	int i, max_cpp = 0;
+	size_t len;
+
+	if (mode & UDRM_BUF_MODE_EMUL_XRGB8888) {
+		if (formats[0] != DRM_FORMAT_RGB565)
+			return -EINVAL;
+		udev->emulate_xrgb8888_format = formats[0];
+	}
+
+	for (i = 0; i < num_formats; i++) {
+		if (udev->emulate_xrgb8888_format &&
+		    formats[i] == DRM_FORMAT_XRGB8888)
+			continue;
+		max_cpp = max(max_cpp, drm_format_plane_cpp(formats[i], 0));
+	}
+
+	if (!max_cpp)
+		return -EINVAL;
+
+	len = udev->display_mode.hdisplay * udev->display_mode.vdisplay * max_cpp;
+
+	udev->dmabuf = dma_buf_get(fd);
+	if (IS_ERR(udev->dmabuf))
+		return PTR_ERR(udev->dmabuf);
+
+	if (len > udev->dmabuf->size) {
+		dma_buf_put(udev->dmabuf);
+		return -EINVAL;
+	}
+
+	/* FIXME is dma_buf_attach() necessary when there's no device? */
+
+	udev->buf_mode = mode;
+	udev->buf_fd = fd;
+
+	return 0;
+}
+
+static void fbdev_init_work(struct work_struct *work)
+{
+	struct udrm_device *udev = container_of(work, struct udrm_device,
+						fbdev_init_work);
+	int ret;
+
+	ret = udrm_fbdev_init(udev);
+	if (ret)
+		DRM_ERROR("Failed to initialize fbdev: %d\n", ret);
+
+}
+
+int udrm_drm_register(struct udrm_device *udev,
+		      struct udrm_dev_create *dev_create,
+		      uint32_t *formats, unsigned int num_formats)
+{
+	struct drm_device *drm;
+	int ret;
+
+	ret = drm_mode_convert_umode(&udev->display_mode, &dev_create->mode);
+	if (ret)
+		return ret;
+
+	drm_mode_debug_printmodeline(&udev->display_mode);
+
+	if (dev_create->buf_mode) {
+		ret = udrm_buf_get(udev, dev_create->buf_fd, dev_create->buf_mode,
+				   formats, num_formats);
+		if (ret)
+			return ret;
+	}
+
+	ret = udrm_drm_init(udev, dev_create->name);
+	if (ret)
+		goto err_put_dmabuf;
+
+	drm = &udev->drm;
+	drm->mode_config.funcs = &udrm_mode_config_funcs;
+
+	ret = udrm_display_pipe_init(udev, DRM_MODE_CONNECTOR_VIRTUAL,
+				     formats, num_formats);
+	if (ret)
+		goto err_fini;
+
+	drm->mode_config.preferred_depth = drm_format_plane_cpp(formats[0], 0) * 8;
+
+	drm_mode_config_reset(drm);
+
+	DRM_DEBUG_KMS("preferred_depth=%u\n", drm->mode_config.preferred_depth);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		goto err_fini;
+
+	/*
+	 * fbdev initialization generates events, so to avoid having to queue
+	 * up events or use a multithreading userspace driver, let a worker do
+	 * it so userspace can be ready for the events.
+	 */
+	INIT_WORK(&udev->fbdev_init_work, fbdev_init_work);
+	schedule_work(&udev->fbdev_init_work);
+
+	dev_create->index = drm->primary->index;
+
+	return 0;
+
+err_put_dmabuf:
+	if (udev->dmabuf)
+		dma_buf_put(udev->dmabuf);
+err_fini:
+	udrm_drm_fini(udev);
+
+	return ret;
+}
+
+void udrm_drm_unregister(struct udrm_device *udev)
+{
+	struct drm_device *drm = &udev->drm;
+
+	DRM_DEBUG_KMS("udrm_drm_unregister\n");
+
+	cancel_work_sync(&udev->fbdev_init_work);
+	drm_crtc_force_disable_all(drm);
+	cancel_work_sync(&udev->dirty_work);
+	udrm_fbdev_fini(udev);
+	drm_dev_unregister(drm);
+
+	if (udev->dmabuf)
+		dma_buf_put(udev->dmabuf);
+
+	udrm_drm_fini(udev);
+}
diff --git a/drivers/gpu/drm/udrm/udrm-fb.c b/drivers/gpu/drm/udrm/udrm-fb.c
new file mode 100644
index 0000000..2c6a33d
--- /dev/null
+++ b/drivers/gpu/drm/udrm/udrm-fb.c
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <linux/dma-buf.h>
+
+#include <uapi/drm/udrm.h>
+
+#include "udrm.h"
+
+static void tinydrm_merge_clips(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *src, unsigned int num_clips,
+			 unsigned int flags, u32 max_width, u32 max_height)
+{
+	unsigned int i;
+
+	if (!src || !num_clips) {
+		dst->x1 = 0;
+		dst->x2 = max_width;
+		dst->y1 = 0;
+		dst->y2 = max_height;
+		return;
+	}
+
+	dst->x1 = ~0;
+	dst->y1 = ~0;
+	dst->x2 = 0;
+	dst->y2 = 0;
+
+	for (i = 0; i < num_clips; i++) {
+		if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY)
+			i++;
+		dst->x1 = min(dst->x1, src[i].x1);
+		dst->x2 = max(dst->x2, src[i].x2);
+		dst->y1 = min(dst->y1, src[i].y1);
+		dst->y2 = max(dst->y2, src[i].y2);
+	}
+
+	if (dst->x2 > max_width || dst->y2 > max_height ||
+	    dst->x1 >= dst->x2 || dst->y1 >= dst->y2) {
+		DRM_DEBUG_KMS("Illegal clip: x1=%u, x2=%u, y1=%u, y2=%u\n",
+			      dst->x1, dst->x2, dst->y1, dst->y2);
+		dst->x1 = 0;
+		dst->y1 = 0;
+		dst->x2 = max_width;
+		dst->y2 = max_height;
+	}
+}
+
+static void udrm_buf_memcpy(void *dst, void *vaddr, unsigned int pitch,
+			    unsigned int cpp, struct drm_clip_rect *clip)
+{
+	void *src = vaddr + (clip->y1 * pitch) + (clip->x1 * cpp);
+	size_t len = (clip->x2 - clip->x1) * cpp;
+	unsigned int y;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		memcpy(dst, src, len);
+		src += pitch;
+		dst += len;
+	}
+}
+
+static void udrm_buf_swab16(u16 *dst, void *vaddr, unsigned int pitch,
+			    struct drm_clip_rect *clip)
+{
+	unsigned int x, y;
+	u16 *src;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		src = vaddr + (y * pitch);
+		src += clip->x1;
+		for (x = clip->x1; x < clip->x2; x++)
+			*dst++ = swab16(*src++);
+	}
+}
+
+static void udrm_buf_emul_xrgb888(void *dst, void *vaddr, unsigned int pitch,
+			u32 buf_mode, struct drm_clip_rect *clip)
+{
+	bool swap = (buf_mode & 7) == UDRM_BUF_MODE_SWAP_BYTES;
+	u16 val16, *dst16 = dst;
+	unsigned int x, y;
+	u32 *src;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		src = vaddr + (y * pitch);
+		src += clip->x1;
+		for (x = clip->x1; x < clip->x2; x++) {
+			val16 = ((*src & 0x00F80000) >> 8) |
+				((*src & 0x0000FC00) >> 5) |
+				((*src & 0x000000F8) >> 3);
+			src++;
+			if (swap)
+				*dst16++ = swab16(val16);
+			else
+				*dst16++ = val16;
+		}
+	}
+}
+
+static bool udrm_fb_dirty_buf_copy(struct udrm_device *udev,
+				   struct drm_framebuffer *fb,
+				   struct drm_clip_rect *clip)
+{
+	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	unsigned int cpp = drm_format_plane_cpp(fb->pixel_format, 0);
+	unsigned int pitch = fb->pitches[0];
+	void *dst, *src = cma_obj->vaddr;
+	int ret = 0;
+
+	if (cma_obj->base.import_attach) {
+		ret = dma_buf_begin_cpu_access(cma_obj->base.import_attach->dmabuf,
+					       DMA_FROM_DEVICE);
+		if (ret)
+			return false;
+	}
+
+	dst = dma_buf_vmap(udev->dmabuf);
+	if (!dst) {
+		ret = -ENOMEM;
+		goto out_end_access;
+	}
+
+	if (udev->emulate_xrgb8888_format &&
+	    fb->pixel_format == DRM_FORMAT_XRGB8888) {
+		udrm_buf_emul_xrgb888(dst, src, pitch, udev->buf_mode, clip);
+		goto out;
+	}
+
+	switch (udev->buf_mode & 7) {
+	case UDRM_BUF_MODE_PLAIN_COPY:
+		udrm_buf_memcpy(dst, src, pitch, cpp, clip);
+		break;
+	case UDRM_BUF_MODE_SWAP_BYTES:
+		/* FIXME support more */
+		if (cpp == 2)
+			udrm_buf_swab16(dst, src, pitch, clip);
+		else
+			ret = -EINVAL;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+out:
+	dma_buf_vunmap(udev->dmabuf, dst);
+out_end_access:
+	if (cma_obj->base.import_attach)
+		ret = dma_buf_end_cpu_access(cma_obj->base.import_attach->dmabuf,
+					     DMA_FROM_DEVICE);
+
+	return ret ? false : true;
+}
+
+static int udrm_fb_dirty(struct drm_framebuffer *fb,
+			     struct drm_file *file_priv,
+			     unsigned int flags, unsigned int color,
+			     struct drm_clip_rect *clips,
+			     unsigned int num_clips)
+{
+	struct udrm_device *udev = drm_to_udrm(fb->dev);
+	struct drm_mode_fb_dirty_cmd *dirty;
+	struct udrm_event_fb_dirty *ev;
+	struct drm_clip_rect clip;
+	size_t size_clips, size;
+	int ret;
+
+	/* don't return -EINVAL, xorg will stop flushing */
+	if (!udev->prepared)
+		return 0;
+
+	/* fbdev can flush even when we're not interested */
+	if (udev->pipe.plane.fb != fb)
+		return 0;
+
+	/* Make sure to flush everything the first time */
+	if (!udev->enabled) {
+		clips = NULL;
+		num_clips = 0;
+	}
+
+	udev->enabled = true;
+
+	/*
+	 * FIXME: are there any apps/libs that pass more than one clip rect?
+	 *        should we support passing multi clips to the driver?
+	 */
+	tinydrm_merge_clips(&clip, clips, num_clips, flags,
+			    fb->width, fb->height);
+	clips = &clip;
+	num_clips = 1;
+
+	DRM_DEBUG("Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n", fb->base.id,
+		  clips->x1, clips->x2, clips->y1, clips->y2);
+
+	if (udev->dmabuf && num_clips == 1)
+		udrm_fb_dirty_buf_copy(udev, fb, clips);
+
+	size_clips = num_clips * sizeof(struct drm_clip_rect);
+	size = sizeof(struct udrm_event_fb_dirty) + size_clips;
+	ev = kzalloc(size, GFP_KERNEL);
+	if (!ev)
+		return -ENOMEM;
+
+	ev->base.type = UDRM_EVENT_FB_DIRTY;
+	ev->base.length = size;
+	dirty = &ev->fb_dirty_cmd;
+
+	dirty->fb_id = fb->base.id;
+	dirty->flags = flags;
+	dirty->color = color;
+	dirty->num_clips = num_clips;
+
+	if (num_clips)
+		memcpy(ev->clips, clips, size_clips);
+
+	ret = udrm_send_event(udev, ev);
+	if (ret)
+		pr_err_ratelimited("Failed to update display %d\n", ret);
+
+	return ret;
+}
+
+static void udrm_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct udrm_device *udev = drm_to_udrm(fb->dev);
+	struct udrm_event_fb ev = {
+		.base = {
+			.type = UDRM_EVENT_FB_DESTROY,
+			.length = sizeof(ev),
+		},
+	};
+	struct drm_framebuffer *iter;
+	int id;
+
+	DRM_DEBUG_KMS("[FB:%d]\n", fb->base.id);
+
+	idr_for_each_entry(&udev->idr, iter, id) {
+		if (fb == iter)
+			break;
+	}
+
+	if (!iter) {
+		DRM_ERROR("failed to find idr\n");
+		goto out;
+	}
+
+	ev.fb_id = id;
+	idr_remove(&udev->idr, id);
+
+	udrm_send_event(udev, &ev);
+out:
+	drm_fb_cma_destroy(fb);
+}
+
+static const struct drm_framebuffer_funcs udrm_fb_funcs = {
+	.destroy	= udrm_fb_destroy,
+	.create_handle	= drm_fb_cma_create_handle,
+	.dirty		= udrm_fb_dirty,
+};
+
+static int udrm_fb_create_event(struct drm_framebuffer *fb)
+{
+	struct udrm_device *udev = drm_to_udrm(fb->dev);
+	struct udrm_event_fb ev = {
+		.base = {
+			.type = UDRM_EVENT_FB_CREATE,
+			.length = sizeof(ev),
+		},
+		.fb_id = fb->base.id,
+	};
+	int ret;
+
+	DRM_DEBUG_KMS("[FB:%d]\n", fb->base.id);
+
+	/* Needed because the id is gone in &drm_framebuffer_funcs->destroy */
+	ret = idr_alloc(&udev->idr, fb, fb->base.id, fb->base.id + 1, GFP_KERNEL);
+	if (ret < 1) {
+		DRM_ERROR("[FB:%d]: failed to allocate idr %d\n", fb->base.id, ret);
+		return ret;
+	}
+
+	ret = udrm_send_event(udev, &ev);
+
+	return ret;
+}
+
+struct drm_framebuffer *
+udrm_fb_create(struct drm_device *drm, struct drm_file *file_priv,
+		   const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct drm_framebuffer *fb;
+	int ret;
+
+	fb = drm_fb_cma_create_with_funcs(drm, file_priv, mode_cmd,
+					  &udrm_fb_funcs);
+	if (IS_ERR(fb))
+		return fb;
+
+	DRM_DEBUG_KMS("[FB:%d] pixel_format: %s\n", fb->base.id,
+		      drm_get_format_name(fb->pixel_format));
+
+	ret = udrm_fb_create_event(fb);
+
+	return fb;
+}
+
+static int udrm_fbdev_create(struct drm_fb_helper *helper,
+				struct drm_fb_helper_surface_size *sizes)
+{
+	struct udrm_device *udev = drm_to_udrm(helper->dev);
+	int ret;
+
+	ret = drm_fbdev_cma_create_with_funcs(helper, sizes, &udrm_fb_funcs);
+	if (ret)
+		return ret;
+
+	strncpy(helper->fbdev->fix.id, helper->dev->driver->name, 16);
+	udev->fbdev_helper = helper;
+
+	DRM_DEBUG_KMS("fbdev: [FB:%d] pixel_format=%s\n", helper->fb->base.id,
+		      drm_get_format_name(helper->fb->pixel_format));
+
+	udrm_fb_create_event(helper->fb);
+
+	return 0;
+}
+
+static const struct drm_fb_helper_funcs udrm_fb_helper_funcs = {
+	.fb_probe = udrm_fbdev_create,
+};
+
+int udrm_fbdev_init(struct udrm_device *udev)
+{
+	struct drm_device *drm = &udev->drm;
+	struct drm_fbdev_cma *fbdev;
+	int bpp;
+
+	DRM_DEBUG_KMS("\n");
+
+	bpp = drm->mode_config.preferred_depth;
+	fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
+					      drm->mode_config.num_crtc,
+					      drm->mode_config.num_connector,
+					      &udrm_fb_helper_funcs);
+	if (IS_ERR(fbdev))
+		return PTR_ERR(fbdev);
+
+	udev->fbdev_cma = fbdev;
+
+	return 0;
+}
+
+void udrm_fbdev_fini(struct udrm_device *udev)
+{
+	drm_fbdev_cma_fini(udev->fbdev_cma);
+	udev->fbdev_cma = NULL;
+	udev->fbdev_helper = NULL;
+}
diff --git a/drivers/gpu/drm/udrm/udrm-pipe.c b/drivers/gpu/drm/udrm/udrm-pipe.c
new file mode 100644
index 0000000..be110cd
--- /dev/null
+++ b/drivers/gpu/drm/udrm/udrm-pipe.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include <uapi/drm/udrm.h>
+
+#include "udrm.h"
+
+static int udrm_connector_get_modes(struct drm_connector *connector)
+{
+	struct udrm_device *udev = drm_to_udrm(connector->dev);
+	struct drm_display_mode *mode = &udev->display_mode;
+
+	mode = drm_mode_duplicate(connector->dev, mode);
+	if (!mode) {
+		DRM_ERROR("Failed to duplicate mode\n");
+		return 0;
+	}
+
+	if (mode->name[0] == '\0')
+		drm_mode_set_name(mode);
+
+	mode->type |= DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	if (mode->width_mm) {
+		connector->display_info.width_mm = mode->width_mm;
+		connector->display_info.height_mm = mode->height_mm;
+	}
+
+	return 1;
+}
+
+static const struct drm_connector_helper_funcs udrm_connector_hfuncs = {
+	.get_modes = udrm_connector_get_modes,
+	.best_encoder = drm_atomic_helper_best_encoder,
+};
+
+static enum drm_connector_status
+udrm_connector_detect(struct drm_connector *connector, bool force)
+{
+	if (drm_device_is_unplugged(connector->dev))
+		return connector_status_disconnected;
+
+	return connector->status;
+}
+
+static const struct drm_connector_funcs udrm_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = udrm_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static void udrm_display_pipe_enable(struct drm_simple_display_pipe *pipe,
+				     struct drm_crtc_state *crtc_state)
+{
+	struct udrm_device *udev = pipe_to_udrm(pipe);
+	struct udrm_event ev = {
+		.type = UDRM_EVENT_PIPE_ENABLE,
+		.length = sizeof(ev),
+	};
+
+	DRM_DEBUG_KMS("\n");
+	udev->prepared = true;
+	udrm_send_event(udev, &ev);
+}
+
+static void udrm_display_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct udrm_device *udev = pipe_to_udrm(pipe);
+	struct udrm_event ev = {
+		.type = UDRM_EVENT_PIPE_DISABLE,
+		.length = sizeof(ev),
+	};
+
+	DRM_DEBUG_KMS("\n");
+	udev->prepared = false;
+	udev->enabled = false;
+	udrm_send_event(udev, &ev);
+}
+
+static void udrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+				 struct drm_plane_state *old_state)
+{
+	struct udrm_device *udev = pipe_to_udrm(pipe);
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	struct drm_crtc *crtc = &udev->pipe.crtc;
+
+	if (!fb)
+		DRM_DEBUG_KMS("fb unset\n");
+	else if (fb != old_state->fb)
+		DRM_DEBUG_KMS("fb changed\n");
+	else
+		DRM_DEBUG_KMS("No fb change\n");
+
+	if (fb && (fb != old_state->fb)) {
+		pipe->plane.fb = fb;
+
+		if (crtc->state->event) {
+			udev->event = crtc->state->event;
+			crtc->state->event = NULL;
+		}
+
+		schedule_work(&udev->dirty_work);
+	}
+
+	if (crtc->state->event) {
+		DRM_DEBUG_KMS("crtc event\n");
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+		crtc->state->event = NULL;
+	}
+
+	if (udev->fbdev_helper && fb == udev->fbdev_helper->fb)
+		udev->fbdev_used = true;
+}
+
+static const struct drm_simple_display_pipe_funcs udrm_pipe_funcs = {
+	.enable = udrm_display_pipe_enable,
+	.disable = udrm_display_pipe_disable,
+	.update = udrm_display_pipe_update,
+};
+
+int udrm_display_pipe_init(struct udrm_device *udev,
+			  int connector_type,
+			  const uint32_t *formats,
+			  unsigned int format_count)
+{
+	const struct drm_display_mode *mode = &udev->display_mode;
+	struct drm_connector *connector = &udev->connector;
+	struct drm_device *drm = &udev->drm;
+	int ret;
+
+	drm->mode_config.min_width = mode->hdisplay;
+	drm->mode_config.max_width = mode->hdisplay;
+	drm->mode_config.min_height = mode->vdisplay;
+	drm->mode_config.max_height = mode->vdisplay;
+
+	drm_connector_helper_add(connector, &udrm_connector_hfuncs);
+	ret = drm_connector_init(drm, connector, &udrm_connector_funcs,
+				 connector_type);
+	if (ret)
+		return ret;
+
+	connector->status = connector_status_connected;
+
+	ret = drm_simple_display_pipe_init(drm, &udev->pipe, &udrm_pipe_funcs,
+					   formats, format_count, connector);
+	if (ret)
+		drm_connector_cleanup(connector);
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/udrm/udrm.h b/drivers/gpu/drm/udrm/udrm.h
new file mode 100644
index 0000000..e1a0191
--- /dev/null
+++ b/drivers/gpu/drm/udrm/udrm.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_TINYDRM_H
+#define __LINUX_TINYDRM_H
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+struct udrm_device {
+	struct drm_device drm;
+	struct drm_driver driver;
+	struct drm_simple_display_pipe pipe;
+	struct drm_display_mode	display_mode;
+	struct drm_connector connector;
+	struct work_struct dirty_work;
+	struct mutex dev_lock;
+	bool prepared;
+	bool enabled;
+
+	struct drm_fbdev_cma *fbdev_cma;
+	struct drm_fb_helper *fbdev_helper;
+	struct work_struct fbdev_init_work;
+	bool fbdev_used;
+
+	struct drm_pending_vblank_event *event;
+
+	struct idr		idr;
+
+	struct mutex		mutex;
+	wait_queue_head_t	waitq;
+	struct completion	completion;
+
+	struct udrm_event	*ev;
+	int			event_ret;
+
+	u32 buf_mode;
+	u32 emulate_xrgb8888_format;
+	struct dma_buf *dmabuf;
+	int buf_fd;
+
+	bool			initialized;
+	struct work_struct	release_work;
+};
+
+static inline struct udrm_device *
+drm_to_udrm(struct drm_device *drm)
+{
+	return container_of(drm, struct udrm_device, drm);
+}
+
+static inline struct udrm_device *
+pipe_to_udrm(struct drm_simple_display_pipe *pipe)
+{
+	return container_of(pipe, struct udrm_device, pipe);
+}
+
+int udrm_send_event(struct udrm_device *udev, void *ev_in);
+
+int udrm_drm_register(struct udrm_device *udev,
+		      struct udrm_dev_create *dev_create,
+		      uint32_t *formats, unsigned int num_formats);
+void udrm_drm_unregister(struct udrm_device *udev);
+
+int
+udrm_display_pipe_init(struct udrm_device *tdev,
+			  int connector_type,
+			  const uint32_t *formats,
+			  unsigned int format_count);
+
+struct drm_framebuffer *
+udrm_fb_create(struct drm_device *drm, struct drm_file *file_priv,
+		  const struct drm_mode_fb_cmd2 *mode_cmd);
+int udrm_fbdev_init(struct udrm_device *tdev);
+void udrm_fbdev_fini(struct udrm_device *tdev);
+
+#endif /* __LINUX_TINYDRM_H */
diff --git a/include/uapi/drm/udrm.h b/include/uapi/drm/udrm.h
new file mode 100644
index 0000000..66cd2b56
--- /dev/null
+++ b/include/uapi/drm/udrm.h
@@ -0,0 +1,78 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _UAPI__UDRM_H_
+#define _UAPI__UDRM_H_
+
+#if defined(__KERNEL__)
+#include <uapi/drm/drm_mode.h>
+#include <linux/types.h>
+#else
+#include <linux/types.h>
+#include <drm/drm_mode.h>
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define UDRM_MAX_NAME_SIZE    80
+
+/* FIXME: Update Documentation/ioctl/ioctl-number.txt */
+#define UDRM_IOCTL_BASE       0xB5
+
+#define UDRM_BUF_MODE_NONE		0
+#define UDRM_BUF_MODE_PLAIN_COPY	1
+#define UDRM_BUF_MODE_SWAP_BYTES	2
+
+#define UDRM_BUF_MODE_EMUL_XRGB8888	BIT(8)
+
+struct udrm_dev_create {
+	char name[UDRM_MAX_NAME_SIZE];
+	struct drm_mode_modeinfo mode;
+	__u64 formats;
+	__u32 num_formats;
+	__u32 buf_mode;
+	__s32 buf_fd;
+
+	__u32 index;
+};
+
+#define UDRM_DEV_CREATE       _IOWR(UDRM_IOCTL_BASE, 1, struct udrm_dev_create)
+
+struct udrm_event {
+	__u32 type;
+	__u32 length;
+};
+
+#define UDRM_EVENT_PIPE_ENABLE	1
+#define UDRM_EVENT_PIPE_DISABLE	2
+
+#define UDRM_EVENT_FB_CREATE	3
+#define UDRM_EVENT_FB_DESTROY	4
+
+struct udrm_event_fb {
+	struct udrm_event base;
+	__u32 fb_id;
+};
+
+#define UDRM_EVENT_FB_DIRTY 	5
+
+struct udrm_event_fb_dirty {
+	struct udrm_event base;
+	struct drm_mode_fb_dirty_cmd fb_dirty_cmd;
+	struct drm_clip_rect clips[];
+};
+
+#define UDRM_PRIME_HANDLE_TO_FD 0x01
+#define DRM_IOCTL_UDRM_PRIME_HANDLE_TO_FD    DRM_IOWR(DRM_COMMAND_BASE + UDRM_PRIME_HANDLE_TO_FD, struct drm_prime_handle)
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* _UAPI__UDRM_H_ */
--
2.10.2

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

  parent reply	other threads:[~2017-01-04 14:10 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-01-04 13:34 [RFC 0/6] drm: Add support for userspace drivers Noralf Trønnes
2017-01-04 13:34 ` [RFC 1/6] drm/modes: Export drm_mode_convert_umode() Noralf Trønnes
2017-01-04 13:34 ` Noralf Trønnes [this message]
2017-01-04 13:34 ` [RFC 3/6] dma-buf: Support generic userspace allocations Noralf Trønnes
2017-01-04 15:08   ` Daniel Vetter
2017-01-04 13:34 ` [RFC 4/6] spi: Let clients do scatter/gather transfers Noralf Trønnes
2017-01-04 13:34 ` [RFC 5/6] spi: spidev: Add dma-buf support Noralf Trønnes
2017-01-04 13:34 ` [RFC 6/6] spi: spidev: Add userspace driver support Noralf Trønnes
2017-01-04 15:06 ` [RFC 0/6] drm: Add support for userspace drivers Daniel Vetter
2017-01-04 15:15   ` Martin Peres

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170104133442.4534-3-noralf@tronnes.org \
    --to=noralf@tronnes.org \
    --cc=dri-devel@lists.freedesktop.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.