diff options
author | Gerd Hoffmann <kraxel@redhat.com> | 2010-04-20 13:33:54 +0200 |
---|---|---|
committer | Gerd Hoffmann <kraxel@redhat.com> | 2010-08-04 17:05:44 +0200 |
commit | efb5206cc9c3b892196256b2a30b64d1f3193df7 (patch) | |
tree | b510ca92a473d4be412d2b602549918c48360861 | |
parent | 59bd95c8d2f5cc1b3b278abb774df0d7e43e1b18 (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.target | 1 | ||||
-rw-r--r-- | hw/spice-vmc.c | 223 |
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) |