Re: [Qemu-devel] Re: [PATCH] Implement a virtio GPU transport

From: Ian Molton
Date: Wed Oct 27 2010 - 09:06:08 EST


On 19/10/10 11:39, Avi Kivity wrote:
On 10/19/2010 12:31 PM, Ian Molton wrote:

2. should start with a patch to the virtio-pci spec to document what
you're doing

Where can I find that spec?

http://ozlabs.org/~rusty/virtio-spec/

Ok, but I'm not patching that until theres been some review.

There are links to the associated qemu and guest OS changes in my original email.

It doesnt, at present... It could be changed fairly easily ithout
breaking anything if that happens though.

The hypervisor and the guest can be changed independently. The driver
should be coded so that it doesn't depend on hypervisor implementation
details.

Fixed - updated patch tested and attached.

-Ian diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile
index 30879df..35699a0 100644
--- a/drivers/gpu/Makefile
+++ b/drivers/gpu/Makefile
@@ -1 +1 @@
-obj-y += drm/ vga/
+obj-y += drm/ vga/ misc/
diff --git a/drivers/gpu/misc/Kconfig b/drivers/gpu/misc/Kconfig
new file mode 100644
index 0000000..50043d3
--- /dev/null
+++ b/drivers/gpu/misc/Kconfig
@@ -0,0 +1,8 @@
+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)
+
diff --git a/drivers/gpu/misc/Makefile b/drivers/gpu/misc/Makefile
new file mode 100644
index 0000000..d9ab333
--- /dev/null
+++ b/drivers/gpu/misc/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIRTIOGL) += virtio-gl.o
diff --git a/drivers/gpu/misc/virtio-gl.c b/drivers/gpu/misc/virtio-gl.c
new file mode 100644
index 0000000..ad3ba14
--- /dev/null
+++ b/drivers/gpu/misc/virtio-gl.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2010 Intel Corporation
+ *
+ * Author: Ian Molton <ian.molton@xxxxxxxxxxxxxxx>
+ *
+ * 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.
+ *
+ * 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/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 completion done;
+};
+
+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 void glmem_done(struct virtqueue *vq)
+{
+ struct virtio_gl_data *gldata;
+ int count;
+
+ gldata = virtqueue_get_buf(vq, &count);
+
+ if (!gldata)
+ BUG();
+
+ complete(&gldata->done);
+}
+
+/* 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 ret, o_page, i_page, sg_entries;
+ struct virtio_gl_header *header =
+ (struct virtio_gl_header *)gldata->buffer;
+
+ 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;
+ }
+
+ /* Transfer data */
+ if (virtqueue_add_buf(vq, sg_list, o_page, i_page, gldata) >= 0) {
+ virtqueue_kick(vq);
+ /* Chill out until it's done with the buffer. */
+ wait_for_completion(&gldata->done);
+ }
+
+out_free:
+ kfree(sg_list);
+out:
+ 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));
+ init_completion(&gldata->done);
+
+ 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, 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, glmem_done, "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 __devexit 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 = __devexit_p(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");
+
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 3d94a14..9a9a6cc 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -16,6 +16,7 @@ source "drivers/char/agp/Kconfig"
source "drivers/gpu/vga/Kconfig"

source "drivers/gpu/drm/Kconfig"
+source "drivers/gpu/misc/Kconfig"

config VGASTATE
tristate
diff --git a/include/linux/virtio_ids.h b/include/linux/virtio_ids.h
index 06660c0..663b496 100644
--- a/include/linux/virtio_ids.h
+++ b/include/linux/virtio_ids.h
@@ -12,6 +12,7 @@
#define VIRTIO_ID_CONSOLE 3 /* virtio console */
#define VIRTIO_ID_RNG 4 /* virtio ring */
#define VIRTIO_ID_BALLOON 5 /* virtio balloon */
+#define VIRTIO_ID_GL 6 /* virtio usermem */
#define VIRTIO_ID_9P 9 /* 9p virtio console */

#endif /* _LINUX_VIRTIO_IDS_H */