summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGerd Hoffmann <kraxel@redhat.com>2010-04-20 13:33:54 +0200
committerGerd Hoffmann <kraxel@redhat.com>2010-08-04 17:05:44 +0200
commitefb5206cc9c3b892196256b2a30b64d1f3193df7 (patch)
treeb510ca92a473d4be412d2b602549918c48360861
parent59bd95c8d2f5cc1b3b278abb774df0d7e43e1b18 (diff)
spice: add virtio-serial based vdi port backend.
Adds the spicevmc device. This is a communication channel between the spice client and the guest. It is used to send display information and mouse events from the spice clients to the guest.
-rw-r--r--Makefile.target1
-rw-r--r--hw/spice-vmc.c223
2 files changed, 224 insertions, 0 deletions
diff --git a/Makefile.target b/Makefile.target
index acebac10a..0fa37446d 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -199,6 +199,7 @@ obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o
obj-i386-y += debugcon.o multiboot.o
obj-i386-y += pc_piix.o
obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
+obj-i386-$(CONFIG_SPICE) += spice-vmc.o
# shared objects
obj-ppc-y = ppc.o
diff --git a/hw/spice-vmc.c b/hw/spice-vmc.c
new file mode 100644
index 000000000..2ddecf406
--- /dev/null
+++ b/hw/spice-vmc.c
@@ -0,0 +1,223 @@
+/*
+
+ 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 <stdio.h>
+#include <stdbool.h>
+#include <spice.h>
+#include <spice-experimental.h>
+
+#include "virtio-serial.h"
+#include "qemu-spice.h"
+
+#define VMC_GUEST_DEVICE_NAME "com.redhat.spice.0"
+#define VMC_DEVICE_NAME "spicevmc"
+
+/* windows guest driver bug workaround */
+#define VMC_MAX_HOST_WRITE 2048
+
+#define dprintf(_svc, _level, _fmt, ...) \
+ do { \
+ static unsigned __dprintf_counter = 0; \
+ if (_svc->debug >= _level) { \
+ fprintf(stderr, "svc: %3d: " _fmt, ++__dprintf_counter, ## __VA_ARGS__);\
+ } \
+ } while (0)
+
+typedef struct SpiceVirtualChannel {
+ VirtIOSerialPort port;
+ VMChangeStateEntry *vmstate;
+ SpiceVDIPortInstance sin;
+ bool active;
+ uint8_t *buffer;
+ uint8_t *datapos;
+ ssize_t bufsize, datalen;
+ uint32_t debug;
+} SpiceVirtualChannel;
+
+static int vmc_write(SpiceVDIPortInstance *sin, const uint8_t *buf, int len)
+{
+ SpiceVirtualChannel *svc = container_of(sin, SpiceVirtualChannel, sin);
+ ssize_t out = 0;
+ ssize_t last_out;
+ uint8_t* p = (uint8_t*)buf;
+
+ while (len > 0) {
+ last_out = virtio_serial_write(&svc->port, p,
+ MIN(len, VMC_MAX_HOST_WRITE));
+ if (last_out > 0) {
+ out += last_out;
+ len -= last_out;
+ p += last_out;
+ } else {
+ break;
+ }
+ }
+
+ dprintf(svc, 3, "%s: %lu/%zd\n", __func__, out, len + out);
+ return out;
+}
+
+static int vmc_read(SpiceVDIPortInstance *sin, uint8_t *buf, int len)
+{
+ SpiceVirtualChannel *svc = container_of(sin, SpiceVirtualChannel, sin);
+ int bytes = MIN(len, svc->datalen);
+
+ dprintf(svc, 2, "%s: %p %d/%d/%zd\n", __func__, svc->datapos, len, bytes, svc->datalen);
+ if (bytes > 0) {
+ memcpy(buf, svc->datapos, bytes);
+ svc->datapos += bytes;
+ svc->datalen -= bytes;
+ assert(svc->datalen >= 0);
+ if (svc->datalen == 0) {
+ svc->datapos = 0;
+ virtio_serial_throttle_port(&svc->port, false);
+ // ^^^ !!! may call vmc_have_data, so don't touch svc after it!
+ }
+ }
+ return bytes;
+}
+
+static SpiceVDIPortInterface vmc_interface = {
+ .base.type = SPICE_INTERFACE_VDI_PORT,
+ .base.description = "spice virtual channel vdi port",
+ .base.major_version = SPICE_INTERFACE_VDI_PORT_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_VDI_PORT_MINOR,
+ .write = vmc_write,
+ .read = vmc_read,
+};
+
+static void vmc_register_interface(SpiceVirtualChannel *svc)
+{
+ if (svc->active) {
+ return;
+ }
+ dprintf(svc, 1, "%s\n", __func__);
+ svc->sin.base.sif = &vmc_interface.base;
+ spice_server_add_interface(spice_server, &svc->sin.base);
+ svc->active = true;
+}
+
+static void vmc_unregister_interface(SpiceVirtualChannel *svc)
+{
+ if (!svc->active) {
+ return;
+ }
+ dprintf(svc, 1, "%s\n", __func__);
+ spice_server_remove_interface(&svc->sin.base);
+ svc->active = false;
+}
+
+
+static void vmc_change_state_handler(void *opaque, int running, int reason)
+{
+ SpiceVirtualChannel *svc = opaque;
+
+ if (running && svc->active) {
+ spice_server_vdi_port_wakeup(&svc->sin);
+ }
+}
+
+/*
+ * virtio-serial callbacks
+ */
+
+static void vmc_guest_open(VirtIOSerialPort *port)
+{
+ SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+
+ dprintf(svc, 1, "%s\n", __func__);
+ vmc_register_interface(svc);
+}
+
+static void vmc_guest_close(VirtIOSerialPort *port)
+{
+ SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+
+ dprintf(svc, 1, "%s\n", __func__);
+ vmc_unregister_interface(svc);
+}
+
+static void vmc_guest_ready(VirtIOSerialPort *port)
+{
+ SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+
+ dprintf(svc, 1, "%s\n", __func__);
+ if (svc->active)
+ spice_server_vdi_port_wakeup(&svc->sin);
+}
+
+static void vmc_have_data(VirtIOSerialPort *port, const uint8_t *buf, size_t len)
+{
+ SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+
+ dprintf(svc, 2, "%s: %zd\n", __func__, len);
+ assert(svc->datalen == 0);
+ if (svc->bufsize < len) {
+ svc->bufsize = len;
+ svc->buffer = qemu_realloc(svc->buffer, svc->bufsize);
+ }
+ memcpy(svc->buffer, buf, len);
+ svc->datapos = svc->buffer;
+ svc->datalen = len;
+ virtio_serial_throttle_port(&svc->port, true);
+ spice_server_vdi_port_wakeup(&svc->sin);
+}
+
+static int vmc_initfn(VirtIOSerialDevice *dev)
+{
+ VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev);
+ SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+
+ if (!using_spice)
+ return -1;
+
+ dprintf(svc, 1, "%s\n", __func__);
+ port->name = qemu_strdup(VMC_GUEST_DEVICE_NAME);
+ svc->vmstate = qemu_add_vm_change_state_handler(
+ vmc_change_state_handler, svc);
+ virtio_serial_open(port);
+ return 0;
+}
+
+static int vmc_exitfn(VirtIOSerialDevice *dev)
+{
+ VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev);
+ SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port);
+
+ dprintf(svc, 1, "%s\n", __func__);
+ vmc_unregister_interface(svc);
+ qemu_del_vm_change_state_handler(svc->vmstate);
+ virtio_serial_close(port);
+ return 0;
+}
+
+static VirtIOSerialPortInfo vmc_info = {
+ .qdev.name = VMC_DEVICE_NAME,
+ .qdev.size = sizeof(SpiceVirtualChannel),
+ .init = vmc_initfn,
+ .exit = vmc_exitfn,
+ .guest_open = vmc_guest_open,
+ .guest_close = vmc_guest_close,
+ .guest_ready = vmc_guest_ready,
+ .have_data = vmc_have_data,
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT32("nr", SpiceVirtualChannel, port.id, VIRTIO_CONSOLE_BAD_ID),
+ DEFINE_PROP_UINT32("debug", SpiceVirtualChannel, debug, 1),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void vmc_register(void)
+{
+ virtio_serial_port_qdev_register(&vmc_info);
+}
+device_init(vmc_register)