[PATCH] Vmchannel PCI device.

Gleb Natapov
2008-12-14T11:49:44Z

There is a need for communication channel between host and various
agents that are running inside a VM guest. The channel will be used
for statistic gathering, logging, cut & paste, host screen resolution
changes notification, guest configuration etc.

It is undesirable to use TCP/IP for this purpose since network
connectivity may not exist between host and guest and if it exists the
traffic can be not routable between host and guest for security reasons
or TCP/IP traffic can be firewalled (by mistake) by unsuspecting VM user.

The patch implements separate PCI device for this type of communication.
To create a channel "-vmchannel channel:dev" option should be specified
on qemu commmand line during VM launch.

Signed-off-by: Gleb Natapov <gleb@redhat.com>
---

Makefile.target | 2
hw/pc.c | 8 +
hw/virtio-vmchannel.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++
hw/virtio-vmchannel.h | 19 +++
sysemu.h | 4 +
vl.c | 35 ++++++
6 files changed, 344 insertions(+), 7 deletions(-)
create mode 100644 hw/virtio-vmchannel.c
create mode 100644 hw/virtio-vmchannel.h

diff --git a/Makefile.target b/Makefile.target
index 8c649be..d9f5aad 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -637,7 +637,7 @@ OBJS+= fdc.o mc146818rtc.o serial.o i8259.o i8254.o pcspk.o pc.o
OBJS+= cirrus_vga.o apic.o parallel.o acpi.o piix_pci.o
OBJS+= usb-uhci.o vmmouse.o vmport.o vmware_vga.o
# virtio support
-OBJS+= virtio.o virtio-blk.o virtio-balloon.o
+OBJS+= virtio.o virtio-blk.o virtio-balloon.o virtio-vmchannel.o
CPPFLAGS += -DHAS_AUDIO -DHAS_AUDIO_CHOICE
endif
ifeq ($(TARGET_BASE_ARCH), ppc)
diff --git a/hw/pc.c b/hw/pc.c
index 73dd8bc..57e3b1d 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -1095,7 +1095,7 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
}
}

- /* Add virtio block devices */
+ /* Add virtio devices */
if (pci_enabled) {
int index;
int unit_id = 0;
@@ -1104,11 +1104,9 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
virtio_blk_init(pci_bus, drives_table[index].bdrv);
unit_id++;
}
- }
-
- /* Add virtio balloon device */
- if (pci_enabled)
virtio_balloon_init(pci_bus);
+ virtio_vmchannel_init(pci_bus);
+ }
}

static void pc_init_pci(ram_addr_t ram_size, int vga_ram_size,
diff --git a/hw/virtio-vmchannel.c b/hw/virtio-vmchannel.c
new file mode 100644
index 0000000..1f5e274
--- /dev/null
+++ b/hw/virtio-vmchannel.c
@@ -0,0 +1,283 @@
+/*
+ * Virtio VMChannel Device
+ *
+ * Copyright RedHat, inc. 2008
+ *
+ * Authors:
+ * Gleb Natapov <gleb@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu-common.h"
+#include "sysemu.h"
+#include "virtio.h"
+#include "pc.h"
+#include "qemu-char.h"
+#include "virtio-vmchannel.h"
+
+//#define DEBUG_VMCHANNEL
+
+#ifdef DEBUG_VMCHANNEL
+#define VMCHANNEL_DPRINTF(fmt, args...) \
+ do { printf("VMCHANNEL: " fmt , ##args); } while (0)
+#else
+#define VMCHANNEL_DPRINTF(fmt, args...)
+#endif
+
+typedef struct VirtIOVMChannel {
+ VirtIODevice vdev;
+ VirtQueue *sq;
+ VirtQueue *rq;
+} VirtIOVMChannel;
+
+typedef struct VMChannel {
+ CharDriverState *hd;
+ VirtQueueElement elem;
+ size_t len;
+ uint32_t id;
+ char name[VMCHANNEL_NAME_MAX];
+} VMChannel;
+
+typedef struct VMChannelDesc {
+ uint32_t id;
+ uint32_t len;
+} VMChannelDesc;
+
+typedef struct VMChannelCfg {
+ uint32_t count;
+ char ids[0];
+} VMChannelCfg;
+
+static uint32_t vmchannel_cfg_size;
+static VirtIOVMChannel *vmchannel;
+
+static VMChannel vmchannel_descs[MAX_VMCHANNEL_DEVICES];
+static int vmchannel_desc_idx;
+
+static int vmchannel_can_read(void *opaque)
+{
+ VMChannel *c = opaque;
+
+ /* device not yet configured */
+ if (!virtio_queue_ready(vmchannel->rq))
+ return 0;
+
+ if (!c->len) {
+ int i;
+
+ if (virtqueue_pop(vmchannel->rq, &c->elem) == 0)
+ return 0;
+
+ if (c->elem.in_num < 1 ||
+ c->elem.in_sg[0].iov_len < sizeof(VMChannelDesc)) {
+ fprintf(stderr, "vmchannel: wrong receive descriptor\n");
+ return 0;
+ }
+
+ for (i = 0; i < c->elem.in_num; i++)
+ c->len += c->elem.in_sg[i].iov_len;
+
+ c->len -= sizeof(VMChannelDesc);
+ }
+
+ return (int)c->len;
+}
+
+static void vmchannel_read(void *opaque, const uint8_t *buf, int size)
+{
+ VMChannel *c = opaque;
+ VMChannelDesc *desc;
+ int i = 0, left = size;
+ size_t iov_len;
+ void *iov_base;
+
+ VMCHANNEL_DPRINTF("read %d bytes from channel %d\n", size, c->id);
+
+ if (!c->len) {
+ fprintf(stderr, "vmchannel: trying to receive into empty descriptor\n");
+ exit(1);
+ }
+
+ if (size <= 0 || size > c->len) {
+ fprintf(stderr, "vmchannel: read size is wrong\n");
+ exit(1);
+ }
+
+ desc = (VMChannelDesc*)c->elem.in_sg[0].iov_base;
+ desc->id = cpu_to_le32(c->id);
+ desc->len = cpu_to_le32(size);
+
+ iov_base = desc + 1;
+ iov_len = c->elem.in_sg[0].iov_len - sizeof(VMChannelDesc);
+
+ for (;;) {
+ size_t len = MIN(left, iov_len);
+ memcpy(iov_base, buf, len);
+ left -= len;
+ buf += len;
+ if (left == 0 || ++i == c->elem.in_num)
+ break;
+ iov_base = c->elem.in_sg[i].iov_base;
+ iov_len = c->elem.in_sg[i].iov_len;
+ }
+
+ if (left) {
+ fprintf(stderr, "vmchannel: dropping %d bytes of data\n", left);
+ exit(1);
+ }
+
+ virtqueue_push(vmchannel->rq, &c->elem, size + sizeof(VMChannelDesc));
+ c->len = 0;
+ virtio_notify(&vmchannel->vdev, vmchannel->rq);
+}
+
+static void virtio_vmchannel_handle_recv(VirtIODevice *vdev, VirtQueue *outputq)
+{
+}
+
+static VMChannel *vmchannel_find_by_id(uint32_t id)
+{
+ int i;
+
+ for (i = 0; i < vmchannel_desc_idx; i++) {
+ if (vmchannel_descs[i].id == id)
+ return &vmchannel_descs[i];
+ }
+ return NULL;
+}
+
+static VMChannel *vmchannel_find_by_name(const char *name)
+{
+ int i;
+
+ for (i = 0; i < vmchannel_desc_idx; i++) {
+ if (!strcmp(vmchannel_descs[i].name, name))
+ return &vmchannel_descs[i];
+ }
+ return NULL;
+}
+
+static void virtio_vmchannel_handle_send(VirtIODevice *vdev, VirtQueue *outputq)
+{
+ VirtQueueElement elem;
+
+ VMCHANNEL_DPRINTF("send\n");
+ while (virtqueue_pop(vmchannel->sq, &elem)) {
+ VMChannelDesc *desc;
+ VMChannel *c;
+ unsigned int len = 0;
+ int i = 0;
+ size_t iov_len;
+ void *iov_base;
+
+ if (elem.out_num < 1 ||
+ elem.out_sg[0].iov_len < sizeof(VMChannelDesc)) {
+ fprintf(stderr, "vmchannel: incorrect send descriptor\n");
+ virtqueue_push(vmchannel->sq, &elem, 0);
+ return;
+ }
+
+ desc = (VMChannelDesc*)elem.out_sg[0].iov_base;
+ desc->len = le32_to_cpu(desc->len);
+ c = vmchannel_find_by_id(le32_to_cpu(desc->id));
+
+ if(!c) {
+ fprintf(stderr, "vmchannel: guest sends to nonexistent channel\n");
+ virtqueue_push(vmchannel->sq, &elem, 0);
+ return;
+ }
+
+ VMCHANNEL_DPRINTF("send to channel %d %d bytes\n", c->id, desc->len);
+
+ iov_base = desc + 1;
+ iov_len = elem.out_sg[0].iov_len - sizeof(VMChannelDesc);
+
+ for (;;) {
+ qemu_chr_write(c->hd, iov_base, iov_len);
+ len += iov_len;
+ if (++i == elem.out_num)
+ break;
+ iov_base = elem.out_sg[i].iov_base;
+ iov_len = elem.out_sg[i].iov_len;
+ }
+
+ if (desc->len != len)
+ fprintf(stderr, "vmchannel: bad descriptor was sent by guest\n");
+
+ virtqueue_push(vmchannel->sq, &elem, len);
+ }
+
+ virtio_notify(&vmchannel->vdev, vmchannel->sq);
+}
+
+static uint32_t virtio_vmchannel_get_features(VirtIODevice *vdev)
+{
+ return 0;
+}
+
+static void virtio_vmchannel_update_config(VirtIODevice *vdev, uint8_t *config)
+{
+ VMChannelCfg *cfg = (VMChannelCfg *)config;
+ int i, offset = 0;
+
+ cfg->count = cpu_to_le32(vmchannel_desc_idx);
+ for (i = 0; i < vmchannel_desc_idx; i++) {
+ uint32_t len = strlen(vmchannel_descs[i].name) + 1;
+ cpu_to_le32w((uint32_t*)(cfg->ids + offset), vmchannel_descs[i].id);
+ offset += 4;
+ cpu_to_le32w((uint32_t*)(cfg->ids + offset), len);
+ offset += 4;
+ strcpy(cfg->ids + offset, vmchannel_descs[i].name);
+ offset += len;
+ }
+}
+
+static void virtio_vmchannel_reset(VirtIODevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < vmchannel_desc_idx; i++)
+ vmchannel_descs[i].len = 0;
+}
+
+void virtio_vmchannel_init(PCIBus *bus)
+{
+
+ if (!vmchannel_desc_idx)
+ return;
+
+ vmchannel = (VirtIOVMChannel *)virtio_init_pci(bus, "virtio-vmchannel",
+ 0x1af4, 0x1003, 0, VIRTIO_ID_VMCHANNEL, 0x5, 0x0, 0x0,
+ vmchannel_cfg_size + 4, sizeof(VirtIOVMChannel));
+
+ vmchannel->vdev.get_features = virtio_vmchannel_get_features;
+ vmchannel->vdev.get_config = virtio_vmchannel_update_config;
+ vmchannel->vdev.reset = virtio_vmchannel_reset;
+
+ vmchannel->rq = virtio_add_queue(&vmchannel->vdev, 128,
+ virtio_vmchannel_handle_recv);
+ vmchannel->sq = virtio_add_queue(&vmchannel->vdev, 128,
+ virtio_vmchannel_handle_send);
+
+ return;
+}
+
+void vmchannel_init(CharDriverState *hd, const char *name)
+{
+ VMChannel *c = &vmchannel_descs[vmchannel_desc_idx];
+
+ if (vmchannel_find_by_name(name)) {
+ fprintf(stderr, "vmchannel with name '%s' already exists\n", name);
+ exit(1);
+ }
+
+ vmchannel_cfg_size += (9 + strlen(name));
+ c->hd = hd;
+ c->id = vmchannel_desc_idx++;
+ strncpy(c->name, name, VMCHANNEL_NAME_MAX);
+ qemu_chr_add_handlers(hd, vmchannel_can_read, vmchannel_read, NULL, c);
+ VMCHANNEL_DPRINTF("Add channel %d with name '%s'\n", c->id, name);
+}
diff --git a/hw/virtio-vmchannel.h b/hw/virtio-vmchannel.h
new file mode 100644
index 0000000..7d7994c
--- /dev/null
+++ b/hw/virtio-vmchannel.h
@@ -0,0 +1,19 @@
+/*
+ * Virtio VMChannel Device
+ *
+ * Copyright RedHat, inc. 2008
+ *
+ * Authors:
+ * Gleb Natapov <gleb@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef VIRTIO_VMCHANNEL_H
+#define VIRTIO_VMCHANNEL_H
+
+#define VIRTIO_ID_VMCHANNEL 6
+#define VMCHANNEL_NAME_MAX 128
+#endif
diff --git a/sysemu.h b/sysemu.h
index 94cffaf..54d9c83 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -157,6 +157,10 @@ extern CharDriverState *parallel_hds[MAX_PARALLEL_PORTS];

#define TFR(expr) do { if ((expr) != -1) break; } while (errno == EINTR)

+#define MAX_VMCHANNEL_DEVICES 4
+void virtio_vmchannel_init(PCIBus *bus);
+void vmchannel_init(CharDriverState *hd, const char *name);
+
#ifdef NEED_CPU_H
/* loader.c */
int get_image_size(const char *filename);
diff --git a/vl.c b/vl.c
index c3a8d8f..9a714c8 100644
--- a/vl.c
+++ b/vl.c
@@ -215,6 +215,7 @@ static int full_screen = 0;
static int no_frame = 0;
#endif
int no_quit = 0;
+CharDriverState *vmchannel_hds[MAX_VMCHANNEL_DEVICES];
CharDriverState *serial_hds[MAX_SERIAL_PORTS];
CharDriverState *parallel_hds[MAX_PARALLEL_PORTS];
#ifdef TARGET_I386
@@ -3939,6 +3940,7 @@ static void help(int exitcode)
"-monitor dev redirect the monitor to char device 'dev'\n"
"-serial dev redirect the serial port to char device 'dev'\n"
"-parallel dev redirect the parallel port to char device 'dev'\n"
+ "-vmchannel channel:dev redirect the vmchannel with name 'channel', to char device 'dev'\n"
"-pidfile file Write PID to 'file'\n"
"-S freeze CPU at startup (use 'c' to start execution)\n"
"-s wait gdb connection to port\n"
@@ -4052,6 +4054,7 @@ enum {
QEMU_OPTION_monitor,
QEMU_OPTION_serial,
QEMU_OPTION_parallel,
+ QEMU_OPTION_vmchannel,
QEMU_OPTION_loadvm,
QEMU_OPTION_full_screen,
QEMU_OPTION_no_frame,
@@ -4160,6 +4163,7 @@ static const QEMUOption qemu_options[] = {
{ "monitor", HAS_ARG, QEMU_OPTION_monitor },
{ "serial", HAS_ARG, QEMU_OPTION_serial },
{ "parallel", HAS_ARG, QEMU_OPTION_parallel },
+ { "vmchannel", 1, QEMU_OPTION_vmchannel },
{ "loadvm", HAS_ARG, QEMU_OPTION_loadvm },
{ "full-screen", 0, QEMU_OPTION_full_screen },
#ifdef CONFIG_SDL
@@ -4484,6 +4488,8 @@ int main(int argc, char **argv, char **envp)
int serial_device_index;
const char *parallel_devices[MAX_PARALLEL_PORTS];
int parallel_device_index;
+ char *vmchannel_devices[MAX_VMCHANNEL_DEVICES];
+ int vmchannel_device_index;
const char *loadvm = NULL;
QEMUMachine *machine;
const char *cpu_model;
@@ -4557,6 +4563,10 @@ int main(int argc, char **argv, char **envp)
parallel_devices[i] = NULL;
parallel_device_index = 0;

+ for(i = 0; i < MAX_VMCHANNEL_DEVICES; i++)
+ vmchannel_devices[i] = NULL;
+ vmchannel_device_index = 0;
+
usb_devices_index = 0;

nb_net_clients = 0;
@@ -4951,7 +4961,13 @@ int main(int argc, char **argv, char **envp)
parallel_devices[parallel_device_index] = optarg;
parallel_device_index++;
break;
- case QEMU_OPTION_loadvm:
+ case QEMU_OPTION_vmchannel:
+ if (vmchannel_device_index >= MAX_VMCHANNEL_DEVICES) {
+ fprintf(stderr, "qemu: too many vmchannel devices\n");
+ exit(1);
+ }
+ vmchannel_devices[vmchannel_device_index++] = strdup(optarg);
+ case QEMU_OPTION_loadvm:
loadvm = optarg;
break;
case QEMU_OPTION_full_screen:
@@ -5453,6 +5469,23 @@ int main(int argc, char **argv, char **envp)
}
}

+ for(i = 0; i < vmchannel_device_index; i++) {
+ char *devname = vmchannel_devices[i];
+ char *name;
+
+ if (!devname)
+ continue;
+
+ name = strsep(&devname, ":");
+ vmchannel_hds[i] = qemu_chr_open(name, devname);
+ if (!vmchannel_hds[i]) {
+ fprintf(stderr, "qemu: could not open vmchannel device '%s'\n", name);
+ exit(1);
+ }
+ vmchannel_init(vmchannel_hds[i], name);
+ free(vmchannel_devices[i]);
+ }
+
machine->init(ram_size, vga_ram_size, boot_devices, ds,
kernel_filename, kernel_cmdline, initrd_filename, cpu_model);