[RFC 2/6] drm: Add support for userspace drivers
Noralf Trønnes
noralf at tronnes.org
Wed Jan 4 13:34:38 UTC 2017
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
More information about the dri-devel
mailing list