Patchwork [5/6] gl: virtio-serial port driver for OpenGL passthrough.

login
register
mail settings
Submitter andrzej zaborowski
Date Jan. 9, 2012, 7:57 a.m.
Message ID <1326095876-31319-6-git-send-email-balrogg@gmail.com>
Download mbox | patch
Permalink /patch/135094/
State New
Headers show

Comments

andrzej zaborowski - Jan. 9, 2012, 7:57 a.m.
This is a relatively simple to use OpenGL passthrough transport
because it doesn't need any guest kernel changes, but it's not
blazing fast.  Still faster than software rendering.

The main problems are:
* transfers are split in very small chunks by virtio queues, some
  OpenGL calls (think textures etc.) will take thousands of context
  switches, vmenters/vmexits, sysenters/sysexits and buffer copying.
  This should really be a single big zerocopy transfer.
* Linux virtio-serial allows only one process to have the port opened,
  so applications on the guest have to fight over the port access
  and open/close the device at every command buffer.
* host cannot know for sure when a guest process has died unexpectedly.

Signed-off-by: Andrzej Zaborowski <andrew.zaborowski@intel.com>
---
 Makefile.target     |    3 +-
 hw/virtio-gl-port.c |  241 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 243 insertions(+), 1 deletions(-)
 create mode 100644 hw/virtio-gl-port.c

Patch

diff --git a/Makefile.target b/Makefile.target
index 21e9927..3d52c71 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -190,7 +190,8 @@  obj-y = arch_init.o cpus.o monitor.o machine.o gdbstub.o balloon.o ioport.o
 # virtio has to be here due to weird dependency between PCI and virtio-net.
 # need to fix this properly
 obj-$(CONFIG_NO_PCI) += pci-stub.o
-obj-$(CONFIG_VIRTIO) += virtio.o virtio-blk.o virtio-balloon.o virtio-net.o virtio-serial-bus.o
+obj-virtio-$(CONFIG_GL) += virtio-gl-port.o
+obj-$(CONFIG_VIRTIO) += virtio.o virtio-blk.o virtio-balloon.o virtio-net.o virtio-serial-bus.o $(obj-virtio-y)
 obj-y += vhost_net.o
 obj-$(CONFIG_VHOST_NET) += vhost.o
 obj-$(CONFIG_REALLY_VIRTFS) += 9pfs/virtio-9p-device.o
diff --git a/hw/virtio-gl-port.c b/hw/virtio-gl-port.c
new file mode 100644
index 0000000..c75f7aa
--- /dev/null
+++ b/hw/virtio-gl-port.c
@@ -0,0 +1,222 @@ 
+/*
+ * GL passthrough virtio-serial-based transport
+ *
+ * Copyright (c) 2011 Intel Corporation
+ *
+ * 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 or
+ * (at your option) any later version 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw.h"
+#include "qemu-error.h"
+#include "virtio-serial.h"
+
+#include "gl/vmgl.h"
+
+#define CMD_HEADER_SIZE 12
+
+static uint8_t *cmd_buffer = NULL;
+static size_t cmd_buffer_bytes = 0;
+static size_t cmd_buffer_size = 0;
+static const uint8_t *ret_buffer = NULL;
+static size_t ret_buffer_bytes = 0;
+
+static size_t virtio_gl_get_cmd_size(const uint8_t *buf)
+{
+#ifdef TARGET_WORDS_BIGENDIAN
+    return be32_to_cpup((const uint32_t *) buf);
+#else
+    return le32_to_cpup((const uint32_t *) buf);
+#endif
+}
+
+static size_t virtio_gl_get_buffer_size(const uint8_t *buf)
+{
+    return virtio_gl_get_cmd_size(buf + 4);
+}
+
+static void virtio_gl_guest_ready(VirtIOSerialPort *port)
+{
+    ssize_t ret;
+
+    if (!ret_buffer_bytes) {
+        return;
+    }
+
+    ret = virtio_serial_write(port, ret_buffer, ret_buffer_bytes);
+    if (ret < 0) {
+        error_report("%s: virtio_serial_write: error %i\n", __func__,
+                        (int) -ret);
+        return;
+    }
+    ret_buffer += ret;
+    ret_buffer_bytes -= ret;
+}
+
+static void virtio_gl_handle_cmd(VirtIOSerialPort *port, const uint8_t *cmd)
+{
+    int pid = le32_to_cpup((const uint32_t *) cmd);
+    int in_size = virtio_gl_get_cmd_size(cmd + 4);
+    int ret;
+    ProcessStruct *process = vmgl_get_process(pid);
+
+    ret_buffer_bytes = virtio_gl_get_cmd_size(cmd + 8);
+    if (cmd_buffer_size < ret_buffer_bytes) {
+        cmd_buffer_size = ret_buffer_bytes;
+        cmd_buffer = g_realloc(cmd_buffer, cmd_buffer_size);
+    }
+    ret_buffer = cmd_buffer;
+
+    ret = vmgl_decode_call(process, cmd + CMD_HEADER_SIZE,
+                    in_size - CMD_HEADER_SIZE, cmd_buffer + 4);
+    if (!ret) {
+        vmgl_disconnect(process);
+        return;
+    }
+
+#ifdef TARGET_WORDS_BIGENDIAN
+    cpu_to_be32wu((uint32_t *) ret_buffer, ret);
+#else
+    cpu_to_le32wu((uint32_t *) ret_buffer, ret);
+#endif
+    /* FIXME: passing the buffer to decode_call_int and calling
+     * virtio_serial_write precludes zero-copy, figure out something
+     * better.  */
+    virtio_gl_guest_ready(port);
+}
+
+static void virtio_gl_guest_close(VirtIOSerialPort *port)
+{
+    cmd_buffer_bytes = 0;
+    ret_buffer_bytes = 0;
+}
+
+/* Callback function that's called when the guest sends us data */
+static ssize_t virtio_gl_have_data(VirtIOSerialPort *port,
+                const uint8_t *buf, size_t len)
+{
+    size_t cmd_size;
+
+    ret_buffer_bytes = 0;
+
+    if (cmd_buffer_bytes) {
+        if (cmd_buffer_size < len + cmd_buffer_bytes) {
+            cmd_buffer_size = len + cmd_buffer_bytes;
+            cmd_buffer = g_realloc(cmd_buffer, cmd_buffer_size);
+        }
+
+        memcpy(cmd_buffer + cmd_buffer_bytes, buf, len);
+        cmd_buffer_bytes += len;
+        if (cmd_buffer_bytes < CMD_HEADER_SIZE) {
+            return len;
+        }
+
+        cmd_size = virtio_gl_get_buffer_size(cmd_buffer);
+        if (cmd_buffer_bytes > cmd_size) {
+            error_report("virtio-gl-port: received buffer is too big");
+            cmd_buffer_bytes = 0;
+            return -EINVAL;
+        }
+        if (cmd_buffer_bytes < cmd_size) {
+            return len;
+        }
+        buf = cmd_buffer;
+        cmd_buffer_bytes = 0;
+    } else if (len < CMD_HEADER_SIZE ||
+            len < (cmd_size = virtio_gl_get_buffer_size(buf))) {
+        if (len < CMD_HEADER_SIZE) {
+            cmd_size = len;
+        }
+        if (cmd_size > cmd_buffer_size) {
+            cmd_buffer = g_realloc(cmd_buffer, cmd_size);
+            cmd_buffer_size = cmd_size;
+        }
+
+        if (len > cmd_size) {
+            error_report("virtio-gl-port: received buffer too big");
+            return -EINVAL;
+        }
+
+        cmd_buffer_bytes = len;
+        memcpy(cmd_buffer, buf, len);
+        return len;
+    }
+
+    virtio_gl_handle_cmd(port, buf);
+
+    return len;
+}
+
+static int virtio_gl_port_initfn(VirtIOSerialPort *port)
+{
+    VirtIOSerialPortInfo *info =
+        DO_UPCAST(VirtIOSerialPortInfo, qdev, port->dev.info);
+    static int exists = 0;
+
+    if (vmgl_acceleration_capability_check() != 0) {
+        error_report("virtio-gl-port: Host GL capabilities check failed, "
+                        "device won't be created\n");
+        return -1;
+    }
+
+    if (exists ++) {
+        error_report("virtio-gl-port: Only one device makes sense\n");
+        return -1;
+    }
+
+    info->have_data = virtio_gl_have_data;
+    info->guest_ready = virtio_gl_guest_ready;
+    info->guest_close = virtio_gl_guest_close;
+
+    /* The guest identifies "OpenGL passthrough"-capable serial ports
+     * from regular consoles using this name.  */
+    port->name = (char *) "qemugl";
+
+    virtio_serial_open(port);
+
+    return 0;
+}
+
+static int virtio_gl_port_exitfn(VirtIOSerialPort *port)
+{
+    VirtIOSerialPortInfo *info =
+        DO_UPCAST(VirtIOSerialPortInfo, qdev, port->dev.info);
+
+    virtio_serial_close(port);
+
+    info->have_data = NULL;
+    info->guest_ready = NULL;
+    info->guest_close = NULL;
+
+    if (cmd_buffer) {
+        g_free(cmd_buffer);
+        cmd_buffer = NULL;
+        cmd_buffer_size = 0;
+        cmd_buffer_bytes = 0;
+    }
+
+    return 0;
+}
+
+static VirtIOSerialPortInfo virtio_gl_port_info = {
+    .qdev.name		= "virtio-gl-port",
+    .qdev.size		= sizeof(VirtIOSerialPort),
+    .init		= virtio_gl_port_initfn,
+    .exit		= virtio_gl_port_exitfn,
+};
+
+static void virtio_gl_port_register(void)
+{
+    virtio_serial_port_qdev_register(&virtio_gl_port_info);
+}
+device_init(virtio_gl_port_register)