VirtGL : Add a GL passthrough device for Tizen emulator
authorjinhyung.jo <jinhyung.jo@samsung.com>
Mon, 17 Mar 2014 05:59:46 +0000 (14:59 +0900)
committerSeokYeon Hwang <syeon.hwang@samsung.com>
Wed, 9 Apr 2014 05:42:30 +0000 (14:42 +0900)
Add a GL passthrough device for Tizen Emulator

Change-Id: I53d455ebf433a5c648951a2e13406e2ac4470712
Signed-off-by: Jinhyung Jo <jinhyung.jo@samsung.com>
arch/x86/configs/i386_tizen_emul_defconfig
drivers/char/Kconfig
drivers/char/Makefile
drivers/char/virtio-gl.c [new file with mode: 0644]

index 179da08c7b41841e4e684b3aeb9b5e772d47bb5a..0d2a43635d8ebdc6ce0fa856baea6ecdc51785a4 100644 (file)
@@ -1695,6 +1695,7 @@ CONFIG_HPET=y
 # CONFIG_TELCLOCK is not set
 CONFIG_DEVPORT=y
 CONFIG_VDPRAM_DEVICE=y
+CONFIG_VIRTIOGL=y
 CONFIG_I2C=y
 CONFIG_I2C_BOARDINFO=y
 CONFIG_I2C_COMPAT=y
index 4a7785577f2722380bd80b4fddd1d841efd9532b..a246d5c7d00056470a025a3702a448d76913ab3a 100644 (file)
@@ -598,5 +598,13 @@ config VDPRAM_DEVICE
         help
           vdpram is telephony simulator works with the event injector.
 
+config VIRTIOGL
+       tristate "Virtio userspace memory transport"
+       depends on VIRTIO_PCI
+       default n
+       ---help---
+         A Driver to facilitate transferring data from userspace to a
+         hypervisor (eg. qemu)
+
 endmenu
 
index d354b9ab577e2faa89bff3aac7459bdc90d5f589..929da878d9ffd228addc2bf7c2012b348ae3042a 100644 (file)
@@ -64,3 +64,4 @@ js-rtc-y = rtc.o
 obj-$(CONFIG_TILE_SROM)                += tile-srom.o
 
 obj-$(CONFIG_VDPRAM_DEVICE)    += vdpram.o
+obj-$(CONFIG_VIRTIOGL)         += virtio-gl.o
diff --git a/drivers/char/virtio-gl.c b/drivers/char/virtio-gl.c
new file mode 100644 (file)
index 0000000..b8ffdce
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2010 Intel Corporation
+ *
+ * Author: Ian Molton <ian.molton@collabora.co.uk>
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/dma-mapping.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_config.h>
+
+#define DEVICE_NAME "glmem"
+
+/* Define to use debugging checksums on transfers */
+#undef DEBUG_GLIO
+
+struct virtio_gl_data {
+       char *buffer;
+       int pages;
+       unsigned int pid;
+};
+
+struct virtio_gl_header {
+       int pid;
+       int buf_size;
+       int r_buf_size;
+#ifdef DEBUG_GLIO
+       int sum;
+#endif
+       char buffer;
+} __packed;
+
+#define to_virtio_gl_data(a)   ((struct virtio_gl_data *)(a)->private_data)
+
+#ifdef DEBUG_GLIO
+#define SIZE_OUT_HEADER (sizeof(int)*4)
+#define SIZE_IN_HEADER (sizeof(int)*2)
+#else
+#define SIZE_OUT_HEADER (sizeof(int)*3)
+#define SIZE_IN_HEADER sizeof(int)
+#endif
+
+static struct virtqueue *vq;
+
+static DEFINE_MUTEX(gl_mutex);
+
+/* This is videobuf_vmalloc_to_sg() from videobuf-dma-sg.c with
+ * some modifications
+ */
+static struct scatterlist *vmalloc_to_sg(struct scatterlist *sg_list,
+                               unsigned char *virt, unsigned int pages)
+{
+       struct page *pg;
+
+       /* unaligned */
+       BUG_ON((ulong)virt & ~PAGE_MASK);
+
+       /* Fill with elements for the data */
+       while (pages) {
+               pg = vmalloc_to_page(virt);
+               if (!pg)
+                       goto err;
+
+               sg_set_page(sg_list, pg, PAGE_SIZE, 0);
+               virt += PAGE_SIZE;
+               sg_list++;
+               pages--;
+       }
+
+       return sg_list;
+
+err:
+       kfree(sg_list);
+       return NULL;
+}
+
+static int put_data(struct virtio_gl_data *gldata)
+{
+       struct scatterlist *sg, *sg_list;
+       unsigned int count, ret, o_page, i_page, sg_entries;
+       struct virtio_gl_header *header =
+                               (struct virtio_gl_header *)gldata->buffer;
+
+       mutex_lock(&gl_mutex);
+
+       ret = header->buf_size;
+
+       o_page = (header->buf_size + PAGE_SIZE-1) >> PAGE_SHIFT;
+       i_page = (header->r_buf_size + PAGE_SIZE-1) >> PAGE_SHIFT;
+
+       header->pid = gldata->pid;
+
+       if ((o_page && i_page) &&
+               (o_page > gldata->pages || i_page > gldata->pages)) {
+               i_page = 0;
+       }
+
+       if (o_page > gldata->pages)
+               o_page = gldata->pages;
+
+       if (i_page > gldata->pages)
+               i_page = gldata->pages;
+
+       if (!o_page)
+               o_page = 1;
+
+       sg_entries = o_page + i_page;
+
+       sg_list = kcalloc(sg_entries, sizeof(struct scatterlist), GFP_KERNEL);
+
+       if (!sg_list) {
+               ret = -EIO;
+               goto out;
+       }
+
+       sg_init_table(sg_list, sg_entries);
+
+       sg = vmalloc_to_sg(sg_list, gldata->buffer, o_page);
+       sg = vmalloc_to_sg(sg, gldata->buffer, i_page);
+
+       if (!sg) {
+               ret = -EIO;
+               goto out_free;
+       }
+
+#if 0
+       /* The fifth parameter of virtqueue_add_buf : FIX ME */
+       /* Transfer data */
+       if (virtqueue_add_buf(vq, sg_list, o_page, i_page, (void *)1, GFP_ATOMIC) >= 0) {
+               virtqueue_kick(vq);
+               /* Chill out until it's done with the buffer. */
+               while (!virtqueue_get_buf(vq, &count))
+                       cpu_relax();
+       }
+#endif
+       /* The fourth parameter of virtqueue_add_buf : FIX ME */
+       /* Transfer data */
+       if ((virtqueue_add_outbuf(vq, sg_list, o_page, (void *)1, GFP_ATOMIC) >= 0) &&
+               (virtqueue_add_inbuf(vq, sg_list, i_page, (void *)1, GFP_ATOMIC) >= 0)) {
+               virtqueue_kick(vq);
+               /* Chill out until it's done with the buffer. */
+               while (!virtqueue_get_buf(vq, &count))
+                       cpu_relax();
+       }
+
+out_free:
+       kfree(sg_list);
+out:
+       mutex_unlock(&gl_mutex);
+       return ret;
+}
+
+static void free_buffer(struct virtio_gl_data *gldata)
+{
+       if (gldata->buffer) {
+               vfree(gldata->buffer);
+               gldata->buffer = NULL;
+       }
+}
+
+static int glmem_open(struct inode *inode, struct file *file)
+{
+       struct virtio_gl_data *gldata = kzalloc(sizeof(struct virtio_gl_data),
+                                               GFP_KERNEL);
+
+       if (!gldata)
+               return -ENXIO;
+
+       gldata->pid = pid_nr(task_pid(current));
+
+       file->private_data = gldata;
+
+       return 0;
+}
+
+static int glmem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+       struct virtio_gl_data *gldata = to_virtio_gl_data(filp);
+       int pages = (vma->vm_end - vma->vm_start) / PAGE_SIZE;
+
+       /* Set a reasonable limit */
+       if (pages > 16)
+               return -ENOMEM;
+
+       /* for now, just allow one buffer to be mmap()ed. */
+       if (gldata->buffer)
+               return -EIO;
+
+       gldata->buffer = vmalloc_user(pages*PAGE_SIZE);
+
+       if (!gldata->buffer)
+               return -ENOMEM;
+
+       gldata->pages = pages;
+
+       if (remap_vmalloc_range(vma, gldata->buffer, 0) < 0) {
+               vfree(gldata->buffer);
+               return -EIO;
+       }
+
+       vma->vm_flags |= VM_DONTEXPAND;
+
+       return 0;
+}
+
+static int glmem_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
+{
+       struct virtio_gl_data *gldata = to_virtio_gl_data(filp);
+
+       put_data(gldata);
+
+       return 0;
+}
+
+static int glmem_release(struct inode *inode, struct file *file)
+{
+       struct virtio_gl_data *gldata = to_virtio_gl_data(file);
+
+       if (gldata && gldata->buffer) {
+               struct virtio_gl_header *header =
+                               (struct virtio_gl_header *)gldata->buffer;
+
+               /* Make sure the host hears about the process ending / dying */
+               header->pid = gldata->pid;
+               header->buf_size = SIZE_OUT_HEADER + 2;
+               header->r_buf_size = SIZE_IN_HEADER;
+               *(short *)(&header->buffer) = -1;
+
+               put_data(gldata);
+               free_buffer(gldata);
+       }
+
+       kfree(gldata);
+
+       return 0;
+}
+
+static const struct file_operations glmem_fops = {
+       .owner          = THIS_MODULE,
+       .open           = glmem_open,
+       .mmap           = glmem_mmap,
+       .fsync          = glmem_fsync,
+       .release        = glmem_release,
+};
+
+static struct miscdevice glmem_dev = {
+       MISC_DYNAMIC_MINOR,
+       DEVICE_NAME,
+       &glmem_fops
+};
+
+static int glmem_probe(struct virtio_device *vdev)
+{
+       int ret;
+
+       /* We expect a single virtqueue. */
+       vq = virtio_find_single_vq(vdev, NULL, "output");
+       if (IS_ERR(vq))
+               return PTR_ERR(vq);
+
+       ret = misc_register(&glmem_dev);
+       if (ret) {
+               printk(KERN_ERR "glmem: cannot register glmem_dev as misc");
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static void glmem_remove(struct virtio_device *vdev)
+{
+       vdev->config->reset(vdev);
+       misc_deregister(&glmem_dev);
+       vdev->config->del_vqs(vdev);
+}
+
+static struct virtio_device_id id_table[] = {
+       { VIRTIO_ID_GL, VIRTIO_DEV_ANY_ID },
+       { 0 },
+};
+
+static struct virtio_driver virtio_gl_driver = {
+       .driver = {
+               .name =  KBUILD_MODNAME,
+               .owner = THIS_MODULE,
+       },
+       .id_table =     id_table,
+       .probe =        glmem_probe,
+       .remove =       glmem_remove,
+};
+
+static int __init glmem_init(void)
+{
+       return register_virtio_driver(&virtio_gl_driver);
+}
+
+static void __exit glmem_exit(void)
+{
+       unregister_virtio_driver(&virtio_gl_driver);
+}
+
+module_init(glmem_init);
+module_exit(glmem_exit);
+
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_DESCRIPTION("Virtio gl passthrough driver");
+MODULE_LICENSE("GPL v2");
+