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 at 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

Reply via email to