From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1LBpTp-0000Oh-Ak for qemu-devel@nongnu.org; Sun, 14 Dec 2008 06:49:49 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1LBpTl-0000OM-Vy for qemu-devel@nongnu.org; Sun, 14 Dec 2008 06:49:47 -0500 Received: from [199.232.76.173] (port=55815 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1LBpTk-0000OH-M8 for qemu-devel@nongnu.org; Sun, 14 Dec 2008 06:49:45 -0500 Received: from mx2.redhat.com ([66.187.237.31]:42952) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1LBpTk-0006xM-3m for qemu-devel@nongnu.org; Sun, 14 Dec 2008 06:49:44 -0500 From: Gleb Natapov Date: Sun, 14 Dec 2008 13:50:27 +0200 Message-ID: <20081214115027.4028.56164.stgit@dhcp-1-237.tlv.redhat.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Subject: [Qemu-devel] [PATCH] Vmchannel PCI device. Reply-To: qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: kvm@vger.kernel.org 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 --- 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 + * + * 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 + * + * 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);