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@xxxxxxxxxxx> --- 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@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel