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

login
register
mail settings
Submitter Andrzej Zaborowski
Date Feb. 22, 2012, 11:22 p.m.
Message ID <1329952970-356-5-git-send-email-andrew.zaborowski@intel.com>
Download mbox | patch
Permalink /patch/142588/
State New
Headers show

Comments

Andrzej Zaborowski - Feb. 22, 2012, 11:22 p.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>
---
v2: rebase after QOM.
---
 Makefile.target     |    3 +-
 hw/virtio-gl-port.c |  220 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 222 insertions(+), 1 deletions(-)
 create mode 100644 hw/virtio-gl-port.c

Patch

diff --git a/Makefile.target b/Makefile.target
index 90e2739..3fae6ec 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -199,7 +199,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..dbeaa2e
--- /dev/null
+++ b/hw/virtio-gl-port.c
@@ -0,0 +1,220 @@ 
+/*
+ * 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;
+static size_t cmd_buffer_bytes;
+static size_t cmd_buffer_size;
+static const uint8_t *ret_buffer;
+static size_t ret_buffer_bytes;
+
+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)
+{
+    static int exists;
+
+    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;
+    }
+
+    /* 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)
+{
+    virtio_serial_close(port);
+
+    if (cmd_buffer) {
+        g_free(cmd_buffer);
+        cmd_buffer = NULL;
+        cmd_buffer_size = 0;
+        cmd_buffer_bytes = 0;
+    }
+
+    return 0;
+}
+
+static void virtio_gl_port_class_init(ObjectClass *klass, void *data)
+{
+    VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
+
+    k->init = virtio_gl_port_initfn,
+    k->exit = virtio_gl_port_exitfn,
+    k->have_data = virtio_gl_have_data,
+    k->guest_ready = virtio_gl_guest_ready;
+    k->guest_close = virtio_gl_guest_close;
+}
+
+static TypeInfo virtio_gl_port_info = {
+    .name		= "virtio-gl-port",
+    .parent		= TYPE_VIRTIO_SERIAL_PORT,
+    .instance_size	= sizeof(VirtIOSerialPort),
+    .class_init		= virtio_gl_port_class_init,
+};
+
+static void virtio_gl_port_register_types(void)
+{
+    type_register_static(&virtio_gl_port_info);
+}
+type_init(virtio_gl_port_register_types)