From 793301cc8eef9e275a3e271e943dc7e51acd525f Mon Sep 17 00:00:00 2001
From: Alon Levy <alevy@redhat.com>
Date: Tue, 23 Mar 2010 09:20:14 -0300
Subject: [PATCH 4/4] spice virtual machine channel, replacement for removed vdi_port

RH-Author: Alon Levy <alevy@redhat.com>
Message-id: <930886637.733831269336014353.JavaMail.root@zmail06.collab.prod.int.phx2.redhat.com>
Patchwork-id: 8044
O-Subject: [PATCH] spice virtual machine channel, replacement for removed
	vdi_port
Bugzilla: 576488
RH-Acked-by: Gerd Hoffmann <kraxel@redhat.com>
RH-Acked-by: Yonit Halperin <yhalperi@redhat.com>
RH-Acked-by: Juan Quintela <quintela@redhat.com>

Hi All,

 This patch is for spice, it adds a virtio serial port that is used to replace the vdi_port pci device that is being removed. It's purpose is to enable spice-client to guest communication both ways, for mouse control (which is better then via qemu when bw is low), for connect/disconnect events of spice-client (to lock screen in guest), and for future clipboard sharing. The patch doesn't require any changes to spice-server and will require some changes to the vdagent in windows guests. It is tested with the linux driver and a linux ""vdagent"" that is available at git://git.engineering.redhat.com/users/alevy/vdagent_linux. Windows virtio-serial driver is in stages of completion (not there yet).

Alon

Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
---
 Makefile.target |    2 +-
 hw/spice-vmc.c  |  300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 301 insertions(+), 1 deletions(-)
 create mode 100644 hw/spice-vmc.c

diff --git a/Makefile.target b/Makefile.target
index c61c1b7..8c7213b 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -218,7 +218,7 @@ obj-i386-y += testdev.o
 obj-i386-$(CONFIG_KVM_PIT) += i8254-kvm.o
 obj-i386-$(CONFIG_KVM_DEVICE_ASSIGNMENT) += device-assignment.o
 obj-i386-$(CONFIG_SPICE) += spice.o spice-input.o spice-display.o
-obj-i386-$(CONFIG_SPICE) += qxl.o qxl_native_worker.o
+obj-i386-$(CONFIG_SPICE) += qxl.o qxl_native_worker.o spice-vmc.o
 
 # Hardware support
 obj-ia64-y += ide.o pckbd.o vga.o $(SOUND_HW) dma.o $(AUDIODRV)
diff --git a/hw/spice-vmc.c b/hw/spice-vmc.c
new file mode 100644
index 0000000..f7297fa
--- /dev/null
+++ b/hw/spice-vmc.c
@@ -0,0 +1,300 @@
+/*
+
+ Spice Virtual Machine Channel (VMC).
+
+  A virtio-serial port used for spice to guest communication, over which spice client
+ and a daemon in the guest operating system communicate.
+
+  Replaces the old vdi_port PCI device.
+
+*/
+
+#include <spice.h>
+#include <vd_interface.h>
+
+#include "virtio-serial.h"
+#include "qemu-spice.h"
+
+#define SPICE_VM_CHANNEL_GUEST_DEVICE_NAME "org.redhat.spice.0"
+#define SPICE_VM_CHANNEL_DEVICE_NAME       "spicevmc"
+
+/*#define DEBUG_SVMC*/
+
+#ifdef DEBUG_SVMC
+#ifndef INFO_SVMC
+#define INFO_SVMC
+#endif
+#define DEBUG_SVMC_PRINTF(fmt, ...) printf("DEBUG spicevmc: " fmt, __VA_ARGS__)
+#else
+#define DEBUG_SVMC_PRINTF(...)
+#endif
+
+#ifdef INFO_SVMC
+#define INFO_SVMC_PRINTF(fmt, ...) printf("spicevmc: " fmt, __VA_ARGS__)
+#else
+#define INFO_SVMC_PRINTF(...)
+#endif
+
+typedef struct SpiceVirtualChannel {
+    VirtIOSerialPort port;
+    bool running;
+    bool active_interface;
+    VDIPortInterface interface;
+    VDIPortPlug *plug;
+
+    /* buffer the memory written by the guest until spice-server reads */
+    struct {
+        uint8    d[1024*16]; /* 16 KiB */
+        unsigned write_pos;
+        int      bytes;      /* in [0, sizeof(d)] */
+        int      read_pos;
+    } guest_out_ring;
+} SpiceVirtualChannel;
+
+/*
+ * VDIPortInterface callbacks
+ */
+
+static VDObjectRef spice_virtual_channel_interface_plug(
+                VDIPortInterface *port, VDIPortPlug* plug)
+{
+    SpiceVirtualChannel *d = container_of(port, SpiceVirtualChannel, interface);
+    DEBUG_SVMC_PRINTF("%s, d = %p, d->plug = %p\n", __func__, d, d->plug);
+    if (d->plug) {
+        return INVALID_VD_OBJECT_REF;
+    }
+    d->plug = plug;
+    DEBUG_SVMC_PRINTF("%s, d = %p, d->plug = %p\n", __func__, d, d->plug);
+    return (VDObjectRef)plug;
+}
+
+static void spice_virtual_channel_interface_unplug(
+                VDIPortInterface *port, VDObjectRef plug)
+{
+    SpiceVirtualChannel *d = container_of(port, SpiceVirtualChannel, interface);
+    DEBUG_SVMC_PRINTF("%s, d = %p, d->plug = %p\n", __func__, d, d->plug);
+    if (!plug || plug != (VDObjectRef)d->plug) {
+        return;
+    }
+    d->plug = NULL;
+
+    /* XXX - throw away anything the client has not read */
+
+    if (d->guest_out_ring.bytes != 0) {
+        printf("warning: %s: %d unwritten bytes discarded.\n",
+                            __func__, d->guest_out_ring.bytes);
+    }
+    d->guest_out_ring.read_pos = d->guest_out_ring.write_pos;
+
+    if (!d->running) {
+        INFO_SVMC_PRINTF("%s: TODO - notify_guest! what to do??\n", __func__);
+    }
+}
+
+static int spice_virtual_channel_interface_write(
+    VDIPortInterface *port, VDObjectRef plug, const uint8_t *buf, int len)
+{
+    SpiceVirtualChannel *svc = container_of(port, SpiceVirtualChannel, interface);
+    DEBUG_SVMC_PRINTF("%s with %d bytes\n", __func__, len);
+    ssize_t written = virtio_serial_write(&svc->port, buf, len);
+    if (written != len)
+        printf("WARNING: %s short write. %lu of %d\n", __func__, written, len);
+
+   /* TODO:
+    * we always claim the write worked. Reason: otherwise interface gives up
+    * We could try to close/open interface.. but actually better to fix agent?
+    */
+    return len;
+}
+
+static int spice_virtual_channel_interface_read(
+    VDIPortInterface *port, VDObjectRef plug, uint8_t *buf, int len)
+{
+    SpiceVirtualChannel *svc = container_of(port, SpiceVirtualChannel, interface);
+    int actual_read = MIN(len, svc->guest_out_ring.bytes);
+
+    DEBUG_SVMC_PRINTF(
+        "%s with %d bytes, bytes = %d, read_pos = %d, will actually read %d bytes\n",
+        __func__, len, svc->guest_out_ring.read_pos,
+        svc->guest_out_ring.write_pos, actual_read);
+
+    if (actual_read > 0) {
+        if (actual_read + svc->guest_out_ring.read_pos > sizeof(svc->guest_out_ring.d)) {
+            // two parts
+            int first_part = sizeof(svc->guest_out_ring.d) - svc->guest_out_ring.read_pos;
+            memcpy(buf, svc->guest_out_ring.d + svc->guest_out_ring.read_pos,
+                   first_part);
+            memcpy(buf + first_part, svc->guest_out_ring.d, actual_read - first_part);
+            svc->guest_out_ring.read_pos = actual_read - first_part;
+        } else {
+            // one part
+            memcpy(buf, svc->guest_out_ring.d + svc->guest_out_ring.read_pos,
+                   actual_read);
+            svc->guest_out_ring.read_pos += actual_read;
+        }
+        svc->guest_out_ring.bytes -= actual_read;
+    }
+    return actual_read;
+}
+
+static void spice_virtual_channel_register_interface(SpiceVirtualChannel *d)
+{
+    VDIPortInterface *interface = &d->interface;
+    static int interface_id = 0;
+
+    if (d->active_interface ) {
+        return;
+    }
+
+    interface->base.base_version = VM_INTERFACE_VERSION;
+    interface->base.type = VD_INTERFACE_VDI_PORT;
+    interface->base.id = ++interface_id;
+    interface->base.description = "spice virtual channel vdi port";
+    interface->base.major_version = VD_INTERFACE_VDI_PORT_MAJOR;
+    interface->base.minor_version = VD_INTERFACE_VDI_PORT_MINOR;
+
+    interface->plug = spice_virtual_channel_interface_plug;
+    interface->unplug = spice_virtual_channel_interface_unplug;
+    interface->write = spice_virtual_channel_interface_write;
+    interface->read = spice_virtual_channel_interface_read;
+
+    d->active_interface = true;
+    qemu_spice_add_interface(&interface->base);
+}
+
+static void spice_virtual_channel_unregister_interface(SpiceVirtualChannel *d)
+{
+    if (!d->active_interface ) {
+        return;
+    }
+    d->active_interface = false;
+    qemu_spice_remove_interface(&d->interface.base);
+}
+
+
+static void spice_virtual_channel_vm_change_state_handler(
+                        void *opaque, int running, int reason)
+{
+    INFO_SVMC_PRINTF("%s running = %d reason = %d\n", __func__, running, reason);
+    SpiceVirtualChannel* svc=(SpiceVirtualChannel*)opaque;
+
+    if (running) {
+        svc->running = true;
+        if (svc->plug) svc->plug->wakeup(svc->plug);
+    } else {
+        svc->running = false;
+    }
+}
+
+/*
+ * virtio-serial callbacks
+ */
+
+static void spice_virtual_channel_guest_open(VirtIOSerialPort *port)
+{
+    SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+    INFO_SVMC_PRINTF("%s called (svc=%p)\n", __func__, svc);
+    spice_virtual_channel_register_interface(svc);
+}
+
+static void spice_virtual_channel_guest_close(VirtIOSerialPort *port)
+{
+    SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+    INFO_SVMC_PRINTF("%s called (svc=%p)\n", __func__, svc);
+    spice_virtual_channel_unregister_interface(svc);
+}
+
+static void spice_virtual_channel_guest_ready(VirtIOSerialPort *port)
+{
+#ifdef INFO_SVMC
+    SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+    INFO_SVMC_PRINTF("%s called (svc=%p)\n", __func__, svc);
+#endif
+}
+
+static size_t spice_virtual_channel_have_data(
+                VirtIOSerialPort *port, const uint8_t *buf, size_t len)
+{
+    SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+    DEBUG_SVMC_PRINTF("%s: (svc = %llX), %ld bytes\n", __func__,
+        (unsigned long long)svc, len);
+
+    DEBUG_SVMC_PRINTF(
+        "filling guest write ring. Was %u full, pos at %d, trying to write %lu\n",
+        svc->guest_out_ring.bytes, svc->guest_out_ring.write_pos, len);
+
+    if (svc->guest_out_ring.bytes == sizeof(svc->guest_out_ring.d)) {
+        printf("WARNING: %s: throwing away %lu bytes due to ring being full\n",
+            __func__, len);
+        return len;
+    }
+    int bytes_read = MIN(sizeof(svc->guest_out_ring.d) - svc->guest_out_ring.bytes, len);
+    if (svc->guest_out_ring.write_pos + bytes_read > sizeof(svc->guest_out_ring.d)) {
+        /* two parts */
+        size_t first_part = sizeof(svc->guest_out_ring.d) - svc->guest_out_ring.write_pos;
+        memcpy(svc->guest_out_ring.d + svc->guest_out_ring.write_pos, buf, first_part);
+        memcpy(svc->guest_out_ring.d, buf + first_part, bytes_read - first_part);
+        svc->guest_out_ring.write_pos = bytes_read - first_part;
+    } else {
+        /* one part */
+        memcpy(svc->guest_out_ring.d + svc->guest_out_ring.write_pos, buf, bytes_read);
+        svc->guest_out_ring.write_pos += bytes_read;
+    }
+    svc->guest_out_ring.bytes += bytes_read;
+    DEBUG_SVMC_PRINTF("filling guest write ring. Now %d, pos at %d, having written %d\n",
+        svc->guest_out_ring.bytes, svc->guest_out_ring.write_pos, bytes_read);
+    // wakeup spice
+    if (svc->plug) svc->plug->wakeup(svc->plug);
+    return bytes_read;
+}
+
+static int spice_virtual_channel_initfn(VirtIOSerialDevice *dev)
+{
+    VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev);
+    SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+    INFO_SVMC_PRINTF("%s called: %p\n", __func__, svc);
+
+    port->name = (char*)SPICE_VM_CHANNEL_GUEST_DEVICE_NAME;
+
+    port->info = dev->info;
+
+    svc->plug = NULL;
+
+    virtio_serial_open(port);
+
+    qemu_add_vm_change_state_handler(
+        spice_virtual_channel_vm_change_state_handler, svc);
+
+    return 0;
+}
+
+static int spice_virtual_channel_exitfn(VirtIOSerialDevice *dev)
+{
+    VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev);
+    SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+    INFO_SVMC_PRINTF("%s called: %p\n", __func__, svc);
+    spice_virtual_channel_unregister_interface(svc);
+    virtio_serial_close(port);
+    return 0;
+}
+
+static VirtIOSerialPortInfo spice_virtual_channel_info = {
+    .qdev.name     = SPICE_VM_CHANNEL_DEVICE_NAME,
+    .qdev.size     = sizeof(SpiceVirtualChannel),
+    .init          = spice_virtual_channel_initfn,
+    .exit          = spice_virtual_channel_exitfn,
+    .guest_open    = spice_virtual_channel_guest_open,
+    .guest_close   = spice_virtual_channel_guest_close,
+    .guest_ready   = spice_virtual_channel_guest_ready,
+    .have_data     = spice_virtual_channel_have_data,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_STRING("name", SpiceVirtualChannel, port.name),
+        DEFINE_PROP_END_OF_LIST(),
+    },
+};
+
+static void spice_virtual_channel_register(void)
+{
+    virtio_serial_port_qdev_register(&spice_virtual_channel_info);
+}
+device_init(spice_virtual_channel_register)
-- 
1.6.3.rc4.29.g8146

