All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC 0/6] drm: Add support for userspace drivers
@ 2017-01-04 13:34 Noralf Trønnes
  2017-01-04 13:34 ` [RFC 1/6] drm/modes: Export drm_mode_convert_umode() Noralf Trønnes
                   ` (6 more replies)
  0 siblings, 7 replies; 10+ messages in thread
From: Noralf Trønnes @ 2017-01-04 13:34 UTC (permalink / raw)
  To: dri-devel

Hi,

I was previously working on tinydrm as a replacement for staging/fbtft.
During a break from that work, I started to think about if it would be
possible to move the drivers to userspace instead. No point in having
them in the kernel if it's not necessary.

This patchset includes all the pieces necessary to get a userspace
driver[1] working that is compatible with the fbtft/fb_ili9341 driver.
It is tested with a SPI interfaced display hat/shield for the Raspberry Pi,
which has an eeprom with a Device Tree fragment on it (merged by the
bootloader). With the help of udev and systemd, the driver is able to
autoload when the display is connected.
Performance wise, the kernel driver can do ~19fps on a 320x240 spi@32MHz
display, whereas the userspace version does ~18fps. So performance is not
an argument to keep the driver in the kernel.

What do you think about this approach?

Note: This patchset is based on 4.9

[1] https://github.com/notro/udrm


Noralf.


Noralf Trønnes (6):
  drm/modes: Export drm_mode_convert_umode()
  drm: Add support for userspace drivers
  dma-buf: Support generic userspace allocations
  spi: Let clients do scatter/gather transfers
  spi: spidev: Add dma-buf support
  spi: spidev: Add userspace driver support

 drivers/dma-buf/Makefile         |   2 +-
 drivers/dma-buf/dev.c            | 211 ++++++++++++++++++
 drivers/gpu/drm/Kconfig          |   2 +
 drivers/gpu/drm/Makefile         |   1 +
 drivers/gpu/drm/drm_modes.c      |   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 ++++++++
 drivers/spi/Kconfig              |   1 +
 drivers/spi/spi.c                |  24 ++-
 drivers/spi/spidev.c             | 447 ++++++++++++++++++++++++++++++++++++++-
 include/linux/spi/spi.h          |   4 +
 include/uapi/drm/udrm.h          |  78 +++++++
 include/uapi/linux/dma-buf-dev.h |  35 +++
 include/uapi/linux/spi/spidev.h  |   8 +
 19 files changed, 2032 insertions(+), 18 deletions(-)
 create mode 100644 drivers/dma-buf/dev.c
 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
 create mode 100644 include/uapi/linux/dma-buf-dev.h

--
2.10.2

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

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC 1/6] drm/modes: Export drm_mode_convert_umode()
  2017-01-04 13:34 [RFC 0/6] drm: Add support for userspace drivers Noralf Trønnes
@ 2017-01-04 13:34 ` Noralf Trønnes
  2017-01-04 13:34 ` [RFC 2/6] drm: Add support for userspace drivers Noralf Trønnes
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Noralf Trønnes @ 2017-01-04 13:34 UTC (permalink / raw)
  To: dri-devel

Export drm_mode_convert_umode() for the benefit of udrm.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_modes.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
index 53f07ac..97cf51f 100644
--- a/drivers/gpu/drm/drm_modes.c
+++ b/drivers/gpu/drm/drm_modes.c
@@ -1555,3 +1555,4 @@ int drm_mode_convert_umode(struct drm_display_mode *out,
 out:
 	return ret;
 }
+EXPORT_SYMBOL(drm_mode_convert_umode);
-- 
2.10.2

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

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC 2/6] drm: Add support for userspace drivers
  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
  2017-01-04 13:34 ` [RFC 3/6] dma-buf: Support generic userspace allocations Noralf Trønnes
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Noralf Trønnes @ 2017-01-04 13:34 UTC (permalink / raw)
  To: dri-devel

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

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC 3/6] dma-buf: Support generic userspace allocations
  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 ` [RFC 2/6] drm: Add support for userspace drivers Noralf Trønnes
@ 2017-01-04 13:34 ` 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
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 10+ messages in thread
From: Noralf Trønnes @ 2017-01-04 13:34 UTC (permalink / raw)
  To: dri-devel

Add a generic way for userspace to allocate dma-buf's for SPI transfers.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/dma-buf/Makefile         |   2 +-
 drivers/dma-buf/dev.c            | 211 +++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/dma-buf-dev.h |  35 +++++++
 3 files changed, 247 insertions(+), 1 deletion(-)
 create mode 100644 drivers/dma-buf/dev.c
 create mode 100644 include/uapi/linux/dma-buf-dev.h

diff --git a/drivers/dma-buf/Makefile b/drivers/dma-buf/Makefile
index 210a10b..ec867f7 100644
--- a/drivers/dma-buf/Makefile
+++ b/drivers/dma-buf/Makefile
@@ -1,3 +1,3 @@
-obj-y := dma-buf.o fence.o reservation.o seqno-fence.o fence-array.o
+obj-y := dma-buf.o fence.o reservation.o seqno-fence.o fence-array.o dev.o
 obj-$(CONFIG_SYNC_FILE)		+= sync_file.o
 obj-$(CONFIG_SW_SYNC)		+= sw_sync.o sync_debug.o
diff --git a/drivers/dma-buf/dev.c b/drivers/dma-buf/dev.c
new file mode 100644
index 0000000..536d9bf
--- /dev/null
+++ b/drivers/dma-buf/dev.c
@@ -0,0 +1,211 @@
+/*
+ * 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/dma-buf.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <uapi/linux/dma-buf-dev.h>
+
+struct dma_buf_dev_object {
+	struct device *dev;
+	unsigned long attrs;
+	dma_addr_t dma_addr;
+	void *vaddr;
+	size_t size;
+};
+
+static struct sg_table *
+dma_buf_dev_map_dma_buf(struct dma_buf_attachment *attach,
+			enum dma_data_direction dir)
+{
+	struct dma_buf_dev_object *obj = attach->dmabuf->priv;
+	struct sg_table *sgt;
+	int ret;
+
+	sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
+	if (!sgt)
+		return ERR_PTR(-ENOMEM);
+
+	ret = dma_get_sgtable(obj->dev, sgt, obj->vaddr,
+			      obj->dma_addr, obj->size);
+	if (ret < 0)
+		goto err_free;
+
+	if (!dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir)) {
+		ret = -ENOMEM;
+		goto err_free_table;
+	}
+
+	return sgt;
+
+err_free_table:
+	sg_free_table(sgt);
+err_free:
+	kfree(sgt);
+
+	return ERR_PTR(ret);
+}
+
+static void dma_buf_dev_unmap_dma_buf(struct dma_buf_attachment *attach,
+				      struct sg_table *sgt,
+				      enum dma_data_direction dir)
+{
+	dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir);
+	sg_free_table(sgt);
+	kfree(sgt);
+}
+
+static void dma_buf_dev_release(struct dma_buf *dma_buf)
+{
+	struct dma_buf_dev_object *obj = dma_buf->priv;
+
+/* FIXME remove */
+pr_info("%s()\n", __func__);
+	dma_free_attrs(obj->dev, obj->size, obj->vaddr, obj->dma_addr,
+		       obj->attrs);
+	kfree(obj);
+}
+
+static void *dma_buf_dev_kmap(struct dma_buf *dma_buf, unsigned long page_num)
+{
+	struct dma_buf_dev_object *obj = dma_buf->priv;
+
+	return obj->vaddr + page_num * PAGE_SIZE;
+}
+
+static void *dma_buf_dev_vmap(struct dma_buf *dma_buf)
+{
+	struct dma_buf_dev_object *obj = dma_buf->priv;
+
+	return obj->vaddr;
+}
+
+static int dma_buf_dev_mmap(struct dma_buf *dma_buf,
+			    struct vm_area_struct *vma)
+{
+	struct dma_buf_dev_object *obj = dma_buf->priv;
+	int ret;
+
+	vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
+
+	ret = dma_mmap_attrs(obj->dev, vma, obj->vaddr, obj->dma_addr,
+			     vma->vm_end - vma->vm_start, obj->attrs);
+
+	return ret;
+}
+
+static const struct dma_buf_ops dma_buf_dev_ops =  {
+	.map_dma_buf = dma_buf_dev_map_dma_buf,
+	.unmap_dma_buf = dma_buf_dev_unmap_dma_buf,
+	.release = dma_buf_dev_release,
+	.kmap_atomic = dma_buf_dev_kmap,
+	.kmap = dma_buf_dev_kmap,
+	.vmap = dma_buf_dev_vmap,
+	.mmap = dma_buf_dev_mmap,
+};
+
+struct dma_buf *dma_buf_dev_alloc_attrs(struct device *dev, size_t size,
+					unsigned long attrs, int flags)
+{
+	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
+	struct dma_buf_dev_object *obj;
+	struct dma_buf *dmabuf;
+	int ret;
+
+	if (flags & ~(O_CLOEXEC | O_ACCMODE))
+		return ERR_PTR(-EINVAL);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return ERR_PTR(-ENOMEM);
+
+	obj->dev = dev;
+	obj->size = size;
+	obj->attrs = attrs;
+
+	obj->vaddr = dma_alloc_attrs(dev, size, &obj->dma_addr, GFP_KERNEL, attrs);
+	if (!obj->vaddr) {
+		ret = -ENOMEM;
+		goto err_free_obj;
+	}
+
+	exp_info.ops = &dma_buf_dev_ops;
+	exp_info.size = obj->size;
+	exp_info.flags = flags;
+	exp_info.priv = obj;
+
+	dmabuf = dma_buf_export(&exp_info);
+	if (IS_ERR(dmabuf)) {
+		ret = PTR_ERR(dmabuf);
+		goto err_free_buf;
+	}
+
+	return dmabuf;
+
+err_free_buf:
+	dma_free_attrs(dev, size, obj->vaddr, obj->dma_addr, attrs);
+err_free_obj:
+	kfree(obj);
+
+	return ERR_PTR(ret);
+}
+
+static long dma_buf_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct dma_buf_dev_create create;
+	struct dma_buf *dmabuf;
+
+	switch (cmd) {
+	case DMA_BUF_DEV_IOCTL_CREATE:
+
+		if (copy_from_user(&create, (void __user *)arg, sizeof(create)))
+			return -EFAULT;
+
+		if (!create.size)
+			return -EINVAL;
+
+		dmabuf = dma_buf_dev_alloc_attrs(NULL, create.size,
+						 create.attrs, create.flags);
+		if (IS_ERR(dmabuf))
+			return PTR_ERR(dmabuf);
+
+		create.fd = dma_buf_fd(dmabuf, create.flags);
+		if (create.fd < 0) {
+			dma_buf_put(dmabuf);
+			return create.fd;
+		}
+
+		if (copy_to_user((void __user *)arg, &create, sizeof(create)))
+			return -EFAULT;
+
+		return 0;
+	default:
+		return -ENOTTY;
+	}
+}
+
+static const struct file_operations dma_buf_dev_fops = {
+	.owner		= THIS_MODULE,
+	.unlocked_ioctl	= dma_buf_dev_ioctl,
+	.compat_ioctl	= dma_buf_dev_ioctl,
+};
+
+static struct miscdevice dma_buf_dev_misc = {
+	.fops		= &dma_buf_dev_fops,
+	.minor		= MISC_DYNAMIC_MINOR,
+	.name		= "dma-buf",
+};
+module_misc_device(dma_buf_dev_misc);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_DESCRIPTION("User mode dma-buf creation");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/dma-buf-dev.h b/include/uapi/linux/dma-buf-dev.h
new file mode 100644
index 0000000..fddbe04
--- /dev/null
+++ b/include/uapi/linux/dma-buf-dev.h
@@ -0,0 +1,35 @@
+/*
+ * 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 _DMA_BUF_DEV_UAPI_H_
+#define _DMA_BUF_DEV_UAPI_H_
+
+#include <linux/types.h>
+
+/**
+ * struct dma_buf_dev_create -
+ * @attrs: Attributes of mapping properties requested in dma_alloc_attrs
+ * @size: Buffer size
+ * @flags: Mode flags for the dma-buf file
+ * @fd: Returned dma-buf file descriptor
+ */
+struct dma_buf_dev_create {
+	__u64 attrs;
+#define DMA_BUF_DEV_ATTR_WRITE_COMBINE          BIT(2)
+	__u64 size;
+	__u64 flags;
+
+	__s64 fd;
+};
+
+/* FIXME: Update Documentation/ioctl/ioctl-number.txt */
+#define DMA_BUF_DEV_BASE		0xB6
+#define DMA_BUF_DEV_IOCTL_CREATE	_IOWR(DMA_BUF_DEV_BASE, 0, struct dma_buf_dev_create)
+
+#endif
-- 
2.10.2

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

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC 4/6] spi: Let clients do scatter/gather transfers
  2017-01-04 13:34 [RFC 0/6] drm: Add support for userspace drivers Noralf Trønnes
                   ` (2 preceding siblings ...)
  2017-01-04 13:34 ` [RFC 3/6] dma-buf: Support generic userspace allocations Noralf Trønnes
@ 2017-01-04 13:34 ` Noralf Trønnes
  2017-01-04 13:34 ` [RFC 5/6] spi: spidev: Add dma-buf support Noralf Trønnes
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Noralf Trønnes @ 2017-01-04 13:34 UTC (permalink / raw)
  To: dri-devel

Just a hack. Someone probably has an idea about how this should be done.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/spi/spi.c       | 24 +++++++++++++++++-------
 include/linux/spi/spi.h |  4 ++++
 2 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 838783c..b2958be 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -808,23 +808,28 @@ static int __spi_map_msg(struct spi_master *master, struct spi_message *msg)
 		if (!master->can_dma(master, msg->spi, xfer))
 			continue;
 
-		if (xfer->tx_buf != NULL) {
+		if (xfer->tx_buf != NULL && !xfer->tx_sg.nents) {
 			ret = spi_map_buf(master, tx_dev, &xfer->tx_sg,
 					  (void *)xfer->tx_buf, xfer->len,
 					  DMA_TO_DEVICE);
 			if (ret != 0)
 				return ret;
+
+			xfer->tx_sg_core_mapped = true;
 		}
 
-		if (xfer->rx_buf != NULL) {
+		if (xfer->rx_buf != NULL && !xfer->rx_sg.nents) {
 			ret = spi_map_buf(master, rx_dev, &xfer->rx_sg,
 					  xfer->rx_buf, xfer->len,
 					  DMA_FROM_DEVICE);
 			if (ret != 0) {
 				spi_unmap_buf(master, tx_dev, &xfer->tx_sg,
 					      DMA_TO_DEVICE);
+				xfer->tx_sg_core_mapped = false;
 				return ret;
 			}
+
+			xfer->rx_sg_core_mapped = true;
 		}
 	}
 
@@ -852,11 +857,16 @@ static int __spi_unmap_msg(struct spi_master *master, struct spi_message *msg)
 		rx_dev = &master->dev;
 
 	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
-		if (!master->can_dma(master, msg->spi, xfer))
-			continue;
-
-		spi_unmap_buf(master, rx_dev, &xfer->rx_sg, DMA_FROM_DEVICE);
-		spi_unmap_buf(master, tx_dev, &xfer->tx_sg, DMA_TO_DEVICE);
+		if (xfer->rx_sg_core_mapped) {
+			spi_unmap_buf(master, rx_dev, &xfer->rx_sg,
+				      DMA_FROM_DEVICE);
+			xfer->rx_sg.nents = 0;
+		}
+		if (xfer->tx_sg_core_mapped) {
+			spi_unmap_buf(master, tx_dev, &xfer->tx_sg,
+				      DMA_TO_DEVICE);
+			xfer->tx_sg.nents = 0;
+		}
 	}
 
 	return 0;
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 4b743ac..f27fda6 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -682,6 +682,8 @@ extern void spi_res_release(struct spi_master *master,
  * @transfer_list: transfers are sequenced through @spi_message.transfers
  * @tx_sg: Scatterlist for transmit, currently not for client use
  * @rx_sg: Scatterlist for receive, currently not for client use
+ * @tx_sg_core_mapped: Scatterlist has been mapped by spi core
+ * @rx_sg_core_mapped: Scatterlist has been mapped by spi core
  *
  * SPI transfers always write the same number of bytes as they read.
  * Protocol drivers should always provide @rx_buf and/or @tx_buf.
@@ -751,6 +753,8 @@ struct spi_transfer {
 	dma_addr_t	rx_dma;
 	struct sg_table tx_sg;
 	struct sg_table rx_sg;
+	bool		tx_sg_core_mapped;
+	bool		rx_sg_core_mapped;
 
 	unsigned	cs_change:1;
 	unsigned	tx_nbits:3;
-- 
2.10.2

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

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC 5/6] spi: spidev: Add dma-buf support
  2017-01-04 13:34 [RFC 0/6] drm: Add support for userspace drivers Noralf Trønnes
                   ` (3 preceding siblings ...)
  2017-01-04 13:34 ` [RFC 4/6] spi: Let clients do scatter/gather transfers Noralf Trønnes
@ 2017-01-04 13:34 ` 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
  6 siblings, 0 replies; 10+ messages in thread
From: Noralf Trønnes @ 2017-01-04 13:34 UTC (permalink / raw)
  To: dri-devel

Add support for using dma-buf buffers in transfers from userspace.

FIXME: Backwards compatibility needs to be taken care of somehow.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/spi/Kconfig             |   1 +
 drivers/spi/spidev.c            | 158 +++++++++++++++++++++++++++++++++++++++-
 include/uapi/linux/spi/spidev.h |   8 ++
 3 files changed, 163 insertions(+), 4 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index b799547..ac6bbd1 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -731,6 +731,7 @@ comment "SPI Protocol Masters"
 
 config SPI_SPIDEV
 	tristate "User mode SPI device driver support"
+	select SG_SPLIT if DMA_SHARED_BUFFER
 	help
 	  This supports user mode SPI protocol drivers.
 
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index d780491..35e6377 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -21,6 +21,8 @@
 #include <linux/ioctl.h>
 #include <linux/fs.h>
 #include <linux/device.h>
+#include <linux/dma-buf.h>
+#include <linux/dmaengine.h>
 #include <linux/err.h>
 #include <linux/list.h>
 #include <linux/errno.h>
@@ -87,6 +89,16 @@ struct spidev_data {
 	u32			speed_hz;
 };
 
+struct spidev_dmabuf {
+	struct device *dev;
+	struct dma_buf_attachment *attach;
+	enum dma_data_direction dir;
+	struct sg_table *sgt_dmabuf;
+	void *vaddr;
+	struct scatterlist *sgl;
+	unsigned int nents;
+};
+
 static LIST_HEAD(device_list);
 static DEFINE_MUTEX(device_list_lock);
 
@@ -205,21 +217,122 @@ spidev_write(struct file *filp, const char __user *buf,
 	return status;
 }
 
+#ifdef CONFIG_DMA_SHARED_BUFFER
+
+static int spidev_dmabuf_map(struct spidev_dmabuf *sdmabuf, struct device *dev,
+			     int fd, enum dma_data_direction dir,
+			     u32 offset, u32 len)
+{
+	struct dma_buf_attachment *attach;
+	struct dma_buf *dmabuf;
+	struct sg_table *sgt;
+	void *vaddr;
+	size_t sizes[1] = { len, };
+	struct scatterlist *out[1];
+	int out_nents[1];
+	int ret;
+
+	dmabuf = dma_buf_get(fd);
+	if (IS_ERR(dmabuf))
+		return PTR_ERR(dmabuf);
+
+	attach = dma_buf_attach(dmabuf, dev);
+	if (IS_ERR(attach)) {
+		ret = PTR_ERR(attach);
+		goto err_put;
+	}
+
+	sgt = dma_buf_map_attachment(attach, dir);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+		goto err_detach;
+	}
+
+	ret = sg_split(sgt->sgl, sgt->nents, offset, 1, sizes, out, out_nents, GFP_KERNEL);
+	if (ret) {
+		goto err_unmap;
+	}
+
+	/* A virtual address is only necessary if master can't do dma. */
+//	ret = dma_buf_begin_cpu_access(dmabuf, dir);
+//	if (ret)
+//		goto err_free_sg;
+
+	vaddr = dma_buf_vmap(dmabuf);
+	if (!vaddr) {
+		ret = -ENOMEM;
+		goto err_end_access;
+	}
+
+	sdmabuf->dev = dev;
+	sdmabuf->attach = attach;
+	sdmabuf->dir = dir;
+	sdmabuf->sgt_dmabuf = sgt;
+	sdmabuf->vaddr = vaddr;
+	sdmabuf->sgl = out[0];
+	sdmabuf->nents = out_nents[0];
+
+	return 0;
+
+err_end_access:
+//	dma_buf_end_cpu_access(dmabuf, dir);
+//err_free_sg:
+	kfree(out[0]);
+err_unmap:
+	dma_buf_unmap_attachment(attach, sgt, dir);
+err_detach:
+	dma_buf_detach(dmabuf, attach);
+err_put:
+	dma_buf_put(dmabuf);
+
+	return ret;
+}
+
+static void spidev_dmabuf_unmap(struct spidev_dmabuf *sdmabuf)
+{
+	struct dma_buf *dmabuf;
+
+	if (!sdmabuf->attach)
+		return;
+
+	dmabuf = sdmabuf->attach->dmabuf;
+	dma_buf_vunmap(dmabuf, sdmabuf->vaddr);
+//	dma_buf_end_cpu_access(dmabuf, sdmabuf->dir);
+	dma_buf_unmap_attachment(sdmabuf->attach, sdmabuf->sgt_dmabuf, sdmabuf->dir);
+	dma_buf_detach(dmabuf, sdmabuf->attach);
+	dma_buf_put(dmabuf);
+}
+#else
+static int spidev_dmabuf_map(struct spidev_dmabuf *sdmabuf, struct device *dev,
+			     int fd, enum dma_data_direction dir,
+			     u32 offset, u32 len)
+{
+	return -ENOTSUPP;
+}
+
+static void spidev_dmabuf_unmap(struct spidev_dmabuf *sdmabuf) {}
+#endif
+
 static int spidev_message(struct spidev_data *spidev,
 		struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
 {
+	struct spi_device	*spi = spidev->spi;
 	struct spi_message	msg;
 	struct spi_transfer	*k_xfers;
 	struct spi_transfer	*k_tmp;
 	struct spi_ioc_transfer *u_tmp;
+	struct spidev_dmabuf	*sdmabufs, *s_tmp;
 	unsigned		n, total, tx_total, rx_total;
 	u8			*tx_buf, *rx_buf;
 	int			status = -EFAULT;
 
 	spi_message_init(&msg);
 	k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
-	if (k_xfers == NULL)
-		return -ENOMEM;
+	sdmabufs = kcalloc(n_xfers * 2, sizeof(*s_tmp), GFP_KERNEL);
+	if (!k_xfers || !sdmabufs) {
+		status = -ENOMEM;
+		goto done;
+	}
 
 	/* Construct spi_message, copying any tx data to bounce buffer.
 	 * We walk the array of user-provided transfers, using each one
@@ -230,6 +343,7 @@ static int spidev_message(struct spidev_data *spidev,
 	total = 0;
 	tx_total = 0;
 	rx_total = 0;
+	s_tmp = sdmabufs;
 	for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
 			n;
 			n--, k_tmp++, u_tmp++) {
@@ -259,7 +373,12 @@ static int spidev_message(struct spidev_data *spidev,
 						u_tmp->len))
 				goto done;
 			rx_buf += k_tmp->len;
+		} else if (u_tmp->rx_dma_fd > 0) {
+			/* TODO */
+			status = -ENOTSUPP;
+			goto done;
 		}
+
 		if (u_tmp->tx_buf) {
 			/* this transfer needs space in TX bounce buffer */
 			tx_total += k_tmp->len;
@@ -273,6 +392,31 @@ static int spidev_message(struct spidev_data *spidev,
 					u_tmp->len))
 				goto done;
 			tx_buf += k_tmp->len;
+		} else if (u_tmp->tx_dma_fd > 0) {
+			struct device *tx_dev;
+
+			if (k_tmp->len > spi->master->max_dma_len) {
+				status = -EMSGSIZE;
+				goto done;
+			}
+
+			if (spi->master->dma_tx)
+				tx_dev = spi->master->dma_tx->device->dev;
+			else
+				tx_dev = &spi->master->dev;
+
+			status = spidev_dmabuf_map(s_tmp, tx_dev,
+						   u_tmp->tx_dma_fd,
+						   DMA_TO_DEVICE,
+						   u_tmp->dma_offset,
+						   k_tmp->len);
+			if (status)
+				goto done;
+
+			k_tmp->tx_sg.sgl = s_tmp->sgl;
+			k_tmp->tx_sg.nents = s_tmp->nents;
+			k_tmp->tx_buf = s_tmp->vaddr + u_tmp->dma_offset;
+			s_tmp++;
 		}
 
 		k_tmp->cs_change = !!u_tmp->cs_change;
@@ -287,8 +431,10 @@ static int spidev_message(struct spidev_data *spidev,
 		dev_dbg(&spidev->spi->dev,
 			"  xfer len %u %s%s%s%dbits %u usec %uHz\n",
 			u_tmp->len,
-			u_tmp->rx_buf ? "rx " : "",
-			u_tmp->tx_buf ? "tx " : "",
+			u_tmp->rx_buf ? "rx " :
+					u_tmp->rx_dma_fd ? "tx-dma" : "",
+			u_tmp->tx_buf ? "tx " :
+					u_tmp->tx_dma_fd ? "rx-dma" : "",
 			u_tmp->cs_change ? "cs " : "",
 			u_tmp->bits_per_word ? : spidev->spi->bits_per_word,
 			u_tmp->delay_usecs,
@@ -317,6 +463,10 @@ static int spidev_message(struct spidev_data *spidev,
 	status = total;
 
 done:
+	for (n = n_xfers, s_tmp = sdmabufs; n; n--, s_tmp++)
+		spidev_dmabuf_unmap(s_tmp);
+
+	kfree(sdmabufs);
 	kfree(k_xfers);
 	return status;
 }
diff --git a/include/uapi/linux/spi/spidev.h b/include/uapi/linux/spi/spidev.h
index dd5f21e..a9b6cd0 100644
--- a/include/uapi/linux/spi/spidev.h
+++ b/include/uapi/linux/spi/spidev.h
@@ -64,6 +64,9 @@
  * @delay_usecs: If nonzero, how long to delay after the last bit transfer
  *	before optionally deselecting the device before the next transfer.
  * @cs_change: True to deselect device before starting the next transfer.
+ * @tx_dma_fd: File descriptor for transmitting dma-buf buffers.
+ * @rx_dma_fd: File descriptor for receiving dma-buf buffers.
+ * @dma_offset: Offset into dma-buf buffer.
  *
  * This structure is mapped directly to the kernel spi_transfer structure;
  * the fields have the same meanings, except of course that the pointers
@@ -100,6 +103,11 @@ struct spi_ioc_transfer {
 	__u8		rx_nbits;
 	__u16		pad;
 
+	__s32		tx_dma_fd;
+	__s32		rx_dma_fd;
+	__u32		dma_offset;
+	__u32		pad2;
+
 	/* If the contents of 'struct spi_ioc_transfer' ever change
 	 * incompatibly, then the ioctl number (currently 0) must change;
 	 * ioctls with constant size fields get a bit more in the way of
-- 
2.10.2

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

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC 6/6] spi: spidev: Add userspace driver support
  2017-01-04 13:34 [RFC 0/6] drm: Add support for userspace drivers Noralf Trønnes
                   ` (4 preceding siblings ...)
  2017-01-04 13:34 ` [RFC 5/6] spi: spidev: Add dma-buf support Noralf Trønnes
@ 2017-01-04 13:34 ` Noralf Trønnes
  2017-01-04 15:06 ` [RFC 0/6] drm: Add support for userspace drivers Daniel Vetter
  6 siblings, 0 replies; 10+ messages in thread
From: Noralf Trønnes @ 2017-01-04 13:34 UTC (permalink / raw)
  To: dri-devel

Add support for spi userspace drivers backed by it's own spi_driver.

Userspace driver usage:
Open /dev/spidev
Write a string containing driver name and optional DT compatible.
  This registers a spidev spi_driver.
Read/poll to receive notice when devices have been bound/probed.
The driver now uses /dev/spidevN.N as normal to access the device.
When the file is closed, the spi_driver is unregistered.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/spi/spidev.c | 289 +++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 283 insertions(+), 6 deletions(-)

diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index 35e6377..b8f3559 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -26,11 +26,13 @@
 #include <linux/err.h>
 #include <linux/list.h>
 #include <linux/errno.h>
+#include <linux/miscdevice.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
 #include <linux/compat.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/poll.h>
 #include <linux/acpi.h>

 #include <linux/spi/spi.h>
@@ -99,6 +101,20 @@ struct spidev_dmabuf {
 	unsigned int nents;
 };

+struct spidev_drv {
+	struct spi_driver	spidrv;
+	struct mutex		event_lock;
+	struct list_head	events;
+	wait_queue_head_t	waitq;
+	struct completion	completion;
+};
+
+struct spidev_drv_event {
+	struct list_head list;
+	u8 bus_num;
+	u8 chip_select;
+};
+
 static LIST_HEAD(device_list);
 static DEFINE_MUTEX(device_list_lock);

@@ -994,6 +1010,254 @@ static struct spi_driver spidev_spi_driver = {

 /*-------------------------------------------------------------------------*/

+static int spidev_drv_probe(struct spi_device *spi)
+{
+	struct spi_driver *spidrv = to_spi_driver(spi->dev.driver);
+	struct spidev_drv *sdrv = container_of(spidrv, struct spidev_drv,
+					       spidrv);
+	struct spidev_drv_event *new_device;
+	int ret;
+
+	ret = spidev_probe(spi);
+	if (ret)
+		return ret;
+
+	ret = mutex_lock_interruptible(&sdrv->event_lock);
+	if (ret)
+		goto out;
+
+	new_device = kzalloc(sizeof(*new_device), GFP_KERNEL);
+	if (new_device) {
+		new_device->bus_num = spi->master->bus_num;
+		new_device->chip_select = spi->chip_select;
+		list_add_tail(&new_device->list, &sdrv->events);
+	} else {
+		ret = -ENOMEM;
+	}
+
+	mutex_unlock(&sdrv->event_lock);
+
+	wake_up_interruptible(&sdrv->waitq);
+out:
+	if (ret)
+		dev_err(&spi->dev, "Failed to add event %d\n", ret);
+
+	return 0;
+}
+
+static int spidev_drv_remove(struct spi_device *spi)
+{
+	int ret;
+
+	ret = spidev_remove(spi);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static ssize_t spidev_drv_write(struct file *file, const char __user *buffer,
+				size_t count, loff_t *ppos)
+{
+	char *str, *token, *drvname, *compatible;
+	struct of_device_id *of_ids = NULL;
+	struct spidev_drv *sdrv = NULL;
+	struct spi_driver *spidrv;
+	unsigned int i;
+	int status;
+
+	if (file->private_data)
+		return -EBUSY;
+
+	if (!count)
+		return 0;
+
+	if (count == 1)
+		return -EINVAL;
+
+	str = strndup_user(buffer, count);
+	if (IS_ERR(str))
+		return PTR_ERR(str);
+
+	for (i = 0, token = str; *token; token++)
+		if (*token == '\n')
+			i++;
+
+	if (i > 1) {
+		status = -EINVAL;
+		goto err_free;
+	}
+
+	drvname = str;
+	if (i) {
+		strsep(&str, "\n");
+		compatible = str;
+	} else {
+		compatible = NULL;
+	}
+
+	if (compatible && strlen(compatible) > 127) {
+		status = -EINVAL;
+		goto err_free;
+	}
+
+pr_info("spidev: Add driver '%s', compatible='%s'\n", drvname, compatible);
+
+	sdrv = kzalloc(sizeof(*sdrv), GFP_KERNEL);
+	if (!sdrv) {
+		status = -ENOMEM;
+		goto err_free;
+	}
+
+	INIT_LIST_HEAD(&sdrv->events);
+	mutex_init(&sdrv->event_lock);
+	init_waitqueue_head(&sdrv->waitq);
+
+	spidrv = &sdrv->spidrv;
+	spidrv->driver.name = drvname;
+	spidrv->probe = spidev_drv_probe;
+	spidrv->remove = spidev_drv_remove;
+
+	if (compatible) {
+		/* the second blank entry is the sentinel */
+		of_ids = kcalloc(2, sizeof(*of_ids), GFP_KERNEL);
+		if (!of_ids) {
+			status = -ENOMEM;
+			goto err_free;
+		}
+		strcpy(of_ids[0].compatible, compatible);
+		spidrv->driver.of_match_table = of_ids;
+	}
+
+	status = spi_register_driver(spidrv);
+	if (status < 0)
+		goto err_free;
+
+	file->private_data = sdrv;
+
+	return count;
+
+err_free:
+	kfree(sdrv);
+	kfree(of_ids);
+	kfree(str);
+
+	return status;
+}
+
+static ssize_t spidev_drv_read(struct file *file, char __user *buffer,
+			       size_t count, loff_t *ppos)
+{
+	struct spidev_drv *sdrv = file->private_data;
+	struct spidev_drv_event *new_device;
+	char str[32];
+	ssize_t ret;
+
+	if (!sdrv)
+		return -ENODEV;
+
+	if (!count)
+		return 0;
+
+	do {
+		ret = mutex_lock_interruptible(&sdrv->event_lock);
+		if (ret)
+			return ret;
+
+		if (list_empty(&sdrv->events)) {
+			if (file->f_flags & O_NONBLOCK)
+				ret = -EAGAIN;
+		} else {
+			new_device = list_first_entry(&sdrv->events,
+						      struct spidev_drv_event,
+						      list);
+			ret = scnprintf(str, sizeof(str) - 1, "spidev%u.%u",
+					new_device->bus_num,
+					new_device->chip_select);
+			if (ret < 0)
+				goto unlock;
+
+			str[ret++] = '\0';
+
+			if (ret > count) {
+				ret = -EINVAL;
+				goto unlock;
+			} else if (copy_to_user(buffer, str, ret)) {
+				ret = -EFAULT;
+				goto unlock;
+			}
+
+			list_del(&new_device->list);
+			kfree(new_device);
+		}
+unlock:
+		mutex_unlock(&sdrv->event_lock);
+
+		if (ret)
+			break;
+
+		if (!(file->f_flags & O_NONBLOCK))
+			ret = wait_event_interruptible(sdrv->waitq,
+						!list_empty(&sdrv->events));
+	} while (ret == 0);
+
+	return ret;
+}
+
+static unsigned int spidev_drv_poll(struct file *file, poll_table *wait)
+{
+	struct spidev_drv *sdrv = file->private_data;
+
+	poll_wait(file, &sdrv->waitq, wait);
+
+	if (!list_empty(&sdrv->events))
+		return POLLIN | POLLRDNORM;
+
+	return 0;
+}
+
+static int spidev_drv_open(struct inode *inode, struct file *file)
+{
+	file->private_data = NULL;
+	nonseekable_open(inode, file);
+
+	return 0;
+}
+
+static int spidev_drv_release(struct inode *inode, struct file *file)
+{
+	struct spidev_drv *sdrv = file->private_data;
+	struct spidev_drv_event *entry, *tmp;
+
+	if (sdrv) {
+		spi_unregister_driver(&sdrv->spidrv);
+		list_for_each_entry_safe(entry, tmp, &sdrv->events, list)
+			kfree(entry);
+		kfree(sdrv->spidrv.driver.name);
+		kfree(sdrv);
+	}
+
+	return 0;
+}
+
+static const struct file_operations spidev_drv_fops = {
+	.owner		= THIS_MODULE,
+	.open		= spidev_drv_open,
+	.release	= spidev_drv_release,
+	.read		= spidev_drv_read,
+	.write		= spidev_drv_write,
+	.poll		= spidev_drv_poll,
+	.llseek		= no_llseek,
+};
+
+static struct miscdevice spidev_misc = {
+	.fops		= &spidev_drv_fops,
+	.minor		= MISC_DYNAMIC_MINOR,
+	.name		= "spidev",
+};
+
+/*-------------------------------------------------------------------------*/
+
 static int __init spidev_init(void)
 {
 	int status;
@@ -1009,21 +1273,34 @@ static int __init spidev_init(void)

 	spidev_class = class_create(THIS_MODULE, "spidev");
 	if (IS_ERR(spidev_class)) {
-		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
-		return PTR_ERR(spidev_class);
+		status = PTR_ERR(spidev_class);
+		goto err_unreg_chardev;
 	}

 	status = spi_register_driver(&spidev_spi_driver);
-	if (status < 0) {
-		class_destroy(spidev_class);
-		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
-	}
+	if (status < 0)
+		goto err_destroy_class;
+
+	status = misc_register(&spidev_misc);
+	if (status < 0)
+		goto err_unreg_driver;
+
+	return 0;
+
+err_unreg_driver:
+	spi_unregister_driver(&spidev_spi_driver);
+err_destroy_class:
+	class_destroy(spidev_class);
+err_unreg_chardev:
+	unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
+
 	return status;
 }
 module_init(spidev_init);

 static void __exit spidev_exit(void)
 {
+	misc_deregister(&spidev_misc);
 	spi_unregister_driver(&spidev_spi_driver);
 	class_destroy(spidev_class);
 	unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
--
2.10.2

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

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [RFC 0/6] drm: Add support for userspace drivers
  2017-01-04 13:34 [RFC 0/6] drm: Add support for userspace drivers Noralf Trønnes
                   ` (5 preceding siblings ...)
  2017-01-04 13:34 ` [RFC 6/6] spi: spidev: Add userspace driver support Noralf Trønnes
@ 2017-01-04 15:06 ` Daniel Vetter
  2017-01-04 15:15   ` Martin Peres
  6 siblings, 1 reply; 10+ messages in thread
From: Daniel Vetter @ 2017-01-04 15:06 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel

On Wed, Jan 04, 2017 at 02:34:36PM +0100, Noralf Trønnes wrote:
> Hi,
> 
> I was previously working on tinydrm as a replacement for staging/fbtft.
> During a break from that work, I started to think about if it would be
> possible to move the drivers to userspace instead. No point in having
> them in the kernel if it's not necessary.
> 
> This patchset includes all the pieces necessary to get a userspace
> driver[1] working that is compatible with the fbtft/fb_ili9341 driver.
> It is tested with a SPI interfaced display hat/shield for the Raspberry Pi,
> which has an eeprom with a Device Tree fragment on it (merged by the
> bootloader). With the help of udev and systemd, the driver is able to
> autoload when the display is connected.
> Performance wise, the kernel driver can do ~19fps on a 320x240 spi@32MHz
> display, whereas the userspace version does ~18fps. So performance is not
> an argument to keep the driver in the kernel.
> 
> What do you think about this approach?

It's a pretty nifty thing, and iirc some of the ideas for implementing
miracast centered around the same idea: Small userspace drm driver shim
that forwards everything to the miracast code running all in userspace.
David Herrmann looked into that years ago, not sure he ever got the full
stack up&running.

For dumb panels itself I'm not sure it's the right design. Adding a
kernel/userspace interface will make code sharing harder, and experience
also says that we'll rework kms semantics every few years. In-kernel
drivers seem to me like the better default choice, except when there's a
clear reason for why userspace is the only option. For miracast that's the
case, since porting the userspace video encode libraries to the kernel
just doesn't make sense. For SPI and other slow buses I don't see any such
compelling reason to move it into userspace.
-Daniel


> 
> Note: This patchset is based on 4.9
> 
> [1] https://github.com/notro/udrm
> 
> 
> Noralf.
> 
> 
> Noralf Trønnes (6):
>   drm/modes: Export drm_mode_convert_umode()
>   drm: Add support for userspace drivers
>   dma-buf: Support generic userspace allocations
>   spi: Let clients do scatter/gather transfers
>   spi: spidev: Add dma-buf support
>   spi: spidev: Add userspace driver support
> 
>  drivers/dma-buf/Makefile         |   2 +-
>  drivers/dma-buf/dev.c            | 211 ++++++++++++++++++
>  drivers/gpu/drm/Kconfig          |   2 +
>  drivers/gpu/drm/Makefile         |   1 +
>  drivers/gpu/drm/drm_modes.c      |   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 ++++++++
>  drivers/spi/Kconfig              |   1 +
>  drivers/spi/spi.c                |  24 ++-
>  drivers/spi/spidev.c             | 447 ++++++++++++++++++++++++++++++++++++++-
>  include/linux/spi/spi.h          |   4 +
>  include/uapi/drm/udrm.h          |  78 +++++++
>  include/uapi/linux/dma-buf-dev.h |  35 +++
>  include/uapi/linux/spi/spidev.h  |   8 +
>  19 files changed, 2032 insertions(+), 18 deletions(-)
>  create mode 100644 drivers/dma-buf/dev.c
>  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
>  create mode 100644 include/uapi/linux/dma-buf-dev.h
> 
> --
> 2.10.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC 3/6] dma-buf: Support generic userspace allocations
  2017-01-04 13:34 ` [RFC 3/6] dma-buf: Support generic userspace allocations Noralf Trønnes
@ 2017-01-04 15:08   ` Daniel Vetter
  0 siblings, 0 replies; 10+ messages in thread
From: Daniel Vetter @ 2017-01-04 15:08 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel

On Wed, Jan 04, 2017 at 02:34:39PM +0100, Noralf Trønnes wrote:
> Add a generic way for userspace to allocate dma-buf's for SPI transfers.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Having a central dma-buf allocator is a common thing, there's already ION
in drivers/staging/android. If we need one I think it's better to
accelarate ION destaging than creating yet another one.
> ---
>  drivers/dma-buf/Makefile         |   2 +-
>  drivers/dma-buf/dev.c            | 211 +++++++++++++++++++++++++++++++++++++++
>  include/uapi/linux/dma-buf-dev.h |  35 +++++++
>  3 files changed, 247 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/dma-buf/dev.c
>  create mode 100644 include/uapi/linux/dma-buf-dev.h
> 
> diff --git a/drivers/dma-buf/Makefile b/drivers/dma-buf/Makefile
> index 210a10b..ec867f7 100644
> --- a/drivers/dma-buf/Makefile
> +++ b/drivers/dma-buf/Makefile
> @@ -1,3 +1,3 @@
> -obj-y := dma-buf.o fence.o reservation.o seqno-fence.o fence-array.o
> +obj-y := dma-buf.o fence.o reservation.o seqno-fence.o fence-array.o dev.o
>  obj-$(CONFIG_SYNC_FILE)		+= sync_file.o
>  obj-$(CONFIG_SW_SYNC)		+= sw_sync.o sync_debug.o
> diff --git a/drivers/dma-buf/dev.c b/drivers/dma-buf/dev.c
> new file mode 100644
> index 0000000..536d9bf
> --- /dev/null
> +++ b/drivers/dma-buf/dev.c
> @@ -0,0 +1,211 @@
> +/*
> + * 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/dma-buf.h>
> +#include <linux/miscdevice.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +
> +#include <uapi/linux/dma-buf-dev.h>
> +
> +struct dma_buf_dev_object {
> +	struct device *dev;
> +	unsigned long attrs;
> +	dma_addr_t dma_addr;
> +	void *vaddr;
> +	size_t size;
> +};
> +
> +static struct sg_table *
> +dma_buf_dev_map_dma_buf(struct dma_buf_attachment *attach,
> +			enum dma_data_direction dir)
> +{
> +	struct dma_buf_dev_object *obj = attach->dmabuf->priv;
> +	struct sg_table *sgt;
> +	int ret;
> +
> +	sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
> +	if (!sgt)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ret = dma_get_sgtable(obj->dev, sgt, obj->vaddr,
> +			      obj->dma_addr, obj->size);
> +	if (ret < 0)
> +		goto err_free;
> +
> +	if (!dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir)) {
> +		ret = -ENOMEM;
> +		goto err_free_table;
> +	}
> +
> +	return sgt;
> +
> +err_free_table:
> +	sg_free_table(sgt);
> +err_free:
> +	kfree(sgt);
> +
> +	return ERR_PTR(ret);
> +}
> +
> +static void dma_buf_dev_unmap_dma_buf(struct dma_buf_attachment *attach,
> +				      struct sg_table *sgt,
> +				      enum dma_data_direction dir)
> +{
> +	dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir);
> +	sg_free_table(sgt);
> +	kfree(sgt);
> +}
> +
> +static void dma_buf_dev_release(struct dma_buf *dma_buf)
> +{
> +	struct dma_buf_dev_object *obj = dma_buf->priv;
> +
> +/* FIXME remove */
> +pr_info("%s()\n", __func__);
> +	dma_free_attrs(obj->dev, obj->size, obj->vaddr, obj->dma_addr,
> +		       obj->attrs);
> +	kfree(obj);
> +}
> +
> +static void *dma_buf_dev_kmap(struct dma_buf *dma_buf, unsigned long page_num)
> +{
> +	struct dma_buf_dev_object *obj = dma_buf->priv;
> +
> +	return obj->vaddr + page_num * PAGE_SIZE;
> +}
> +
> +static void *dma_buf_dev_vmap(struct dma_buf *dma_buf)
> +{
> +	struct dma_buf_dev_object *obj = dma_buf->priv;
> +
> +	return obj->vaddr;
> +}
> +
> +static int dma_buf_dev_mmap(struct dma_buf *dma_buf,
> +			    struct vm_area_struct *vma)
> +{
> +	struct dma_buf_dev_object *obj = dma_buf->priv;
> +	int ret;
> +
> +	vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
> +
> +	ret = dma_mmap_attrs(obj->dev, vma, obj->vaddr, obj->dma_addr,
> +			     vma->vm_end - vma->vm_start, obj->attrs);
> +
> +	return ret;
> +}
> +
> +static const struct dma_buf_ops dma_buf_dev_ops =  {
> +	.map_dma_buf = dma_buf_dev_map_dma_buf,
> +	.unmap_dma_buf = dma_buf_dev_unmap_dma_buf,
> +	.release = dma_buf_dev_release,
> +	.kmap_atomic = dma_buf_dev_kmap,
> +	.kmap = dma_buf_dev_kmap,
> +	.vmap = dma_buf_dev_vmap,
> +	.mmap = dma_buf_dev_mmap,
> +};
> +
> +struct dma_buf *dma_buf_dev_alloc_attrs(struct device *dev, size_t size,
> +					unsigned long attrs, int flags)
> +{
> +	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
> +	struct dma_buf_dev_object *obj;
> +	struct dma_buf *dmabuf;
> +	int ret;
> +
> +	if (flags & ~(O_CLOEXEC | O_ACCMODE))
> +		return ERR_PTR(-EINVAL);
> +
> +	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> +	if (!obj)
> +		return ERR_PTR(-ENOMEM);
> +
> +	obj->dev = dev;
> +	obj->size = size;
> +	obj->attrs = attrs;
> +
> +	obj->vaddr = dma_alloc_attrs(dev, size, &obj->dma_addr, GFP_KERNEL, attrs);

Hm, does dma_alloc reall always work with dev == NULL? I had no idea this
was possible ...

Cheers, Daniel

> +	if (!obj->vaddr) {
> +		ret = -ENOMEM;
> +		goto err_free_obj;
> +	}
> +
> +	exp_info.ops = &dma_buf_dev_ops;
> +	exp_info.size = obj->size;
> +	exp_info.flags = flags;
> +	exp_info.priv = obj;
> +
> +	dmabuf = dma_buf_export(&exp_info);
> +	if (IS_ERR(dmabuf)) {
> +		ret = PTR_ERR(dmabuf);
> +		goto err_free_buf;
> +	}
> +
> +	return dmabuf;
> +
> +err_free_buf:
> +	dma_free_attrs(dev, size, obj->vaddr, obj->dma_addr, attrs);
> +err_free_obj:
> +	kfree(obj);
> +
> +	return ERR_PTR(ret);
> +}
> +
> +static long dma_buf_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> +	struct dma_buf_dev_create create;
> +	struct dma_buf *dmabuf;
> +
> +	switch (cmd) {
> +	case DMA_BUF_DEV_IOCTL_CREATE:
> +
> +		if (copy_from_user(&create, (void __user *)arg, sizeof(create)))
> +			return -EFAULT;
> +
> +		if (!create.size)
> +			return -EINVAL;
> +
> +		dmabuf = dma_buf_dev_alloc_attrs(NULL, create.size,
> +						 create.attrs, create.flags);
> +		if (IS_ERR(dmabuf))
> +			return PTR_ERR(dmabuf);
> +
> +		create.fd = dma_buf_fd(dmabuf, create.flags);
> +		if (create.fd < 0) {
> +			dma_buf_put(dmabuf);
> +			return create.fd;
> +		}
> +
> +		if (copy_to_user((void __user *)arg, &create, sizeof(create)))
> +			return -EFAULT;
> +
> +		return 0;
> +	default:
> +		return -ENOTTY;
> +	}
> +}
> +
> +static const struct file_operations dma_buf_dev_fops = {
> +	.owner		= THIS_MODULE,
> +	.unlocked_ioctl	= dma_buf_dev_ioctl,
> +	.compat_ioctl	= dma_buf_dev_ioctl,
> +};
> +
> +static struct miscdevice dma_buf_dev_misc = {
> +	.fops		= &dma_buf_dev_fops,
> +	.minor		= MISC_DYNAMIC_MINOR,
> +	.name		= "dma-buf",
> +};
> +module_misc_device(dma_buf_dev_misc);
> +
> +MODULE_AUTHOR("Noralf Trønnes");
> +MODULE_DESCRIPTION("User mode dma-buf creation");
> +MODULE_LICENSE("GPL");
> diff --git a/include/uapi/linux/dma-buf-dev.h b/include/uapi/linux/dma-buf-dev.h
> new file mode 100644
> index 0000000..fddbe04
> --- /dev/null
> +++ b/include/uapi/linux/dma-buf-dev.h
> @@ -0,0 +1,35 @@
> +/*
> + * 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 _DMA_BUF_DEV_UAPI_H_
> +#define _DMA_BUF_DEV_UAPI_H_
> +
> +#include <linux/types.h>
> +
> +/**
> + * struct dma_buf_dev_create -
> + * @attrs: Attributes of mapping properties requested in dma_alloc_attrs
> + * @size: Buffer size
> + * @flags: Mode flags for the dma-buf file
> + * @fd: Returned dma-buf file descriptor
> + */
> +struct dma_buf_dev_create {
> +	__u64 attrs;
> +#define DMA_BUF_DEV_ATTR_WRITE_COMBINE          BIT(2)
> +	__u64 size;
> +	__u64 flags;
> +
> +	__s64 fd;
> +};
> +
> +/* FIXME: Update Documentation/ioctl/ioctl-number.txt */
> +#define DMA_BUF_DEV_BASE		0xB6
> +#define DMA_BUF_DEV_IOCTL_CREATE	_IOWR(DMA_BUF_DEV_BASE, 0, struct dma_buf_dev_create)
> +
> +#endif
> -- 
> 2.10.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC 0/6] drm: Add support for userspace drivers
  2017-01-04 15:06 ` [RFC 0/6] drm: Add support for userspace drivers Daniel Vetter
@ 2017-01-04 15:15   ` Martin Peres
  0 siblings, 0 replies; 10+ messages in thread
From: Martin Peres @ 2017-01-04 15:15 UTC (permalink / raw)
  To: dri-devel

On 04/01/17 17:06, Daniel Vetter wrote:
> On Wed, Jan 04, 2017 at 02:34:36PM +0100, Noralf Trønnes wrote:
>> Hi,
>>
>> I was previously working on tinydrm as a replacement for staging/fbtft.
>> During a break from that work, I started to think about if it would be
>> possible to move the drivers to userspace instead. No point in having
>> them in the kernel if it's not necessary.
>>
>> This patchset includes all the pieces necessary to get a userspace
>> driver[1] working that is compatible with the fbtft/fb_ili9341 driver.
>> It is tested with a SPI interfaced display hat/shield for the Raspberry Pi,
>> which has an eeprom with a Device Tree fragment on it (merged by the
>> bootloader). With the help of udev and systemd, the driver is able to
>> autoload when the display is connected.
>> Performance wise, the kernel driver can do ~19fps on a 320x240 spi@32MHz
>> display, whereas the userspace version does ~18fps. So performance is not
>> an argument to keep the driver in the kernel.
>>
>> What do you think about this approach?
>
> It's a pretty nifty thing, and iirc some of the ideas for implementing
> miracast centered around the same idea: Small userspace drm driver shim
> that forwards everything to the miracast code running all in userspace.
> David Herrmann looked into that years ago, not sure he ever got the full
> stack up&running.

This is actually something David Herrmann told us not to do, stating 
that it was too hard for Desktop Environments to select which GPUs to 
output to. I however disagreed as this is something they need to support 
anyway for USB GPUs and miracast could piggy back on this effort.

An engineer in Intel Finland did work on this and we got approval for 
Open Sourcing it but I really have no time for this right now though :s

That was my 2 cents.

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

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2017-01-04 15:16 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [RFC 2/6] drm: Add support for userspace drivers Noralf Trønnes
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

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.