diff --git a/qemu/Makefile.target b/qemu/Makefile.target index 5462092..6cf13f7 100644 --- a/qemu/Makefile.target +++ b/qemu/Makefile.target @@ -612,7 +612,7 @@ OBJS += rtl8139.o OBJS += e1000.o # virtio devices -OBJS += virtio.o virtio-net.o virtio-blk.o virtio-balloon.o +OBJS += virtio.o virtio-net.o virtio-blk.o virtio-balloon.o virtio-vmchannel.o OBJS += device-hotplug.o diff --git a/qemu/hw/pc.c b/qemu/hw/pc.c index 1d42aa7..e8c5531 100644 --- a/qemu/hw/pc.c +++ b/qemu/hw/pc.c @@ -1141,6 +1141,7 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size, drives_table[index].bdrv); unit_id++; } + virtio_vmchannel_init(pci_bus); } if (extboot_drive != -1) { diff --git a/qemu/hw/virtio-vmchannel.c b/qemu/hw/virtio-vmchannel.c new file mode 100644 index 0000000..1ce76ec --- /dev/null +++ b/qemu/hw/virtio-vmchannel.c @@ -0,0 +1,239 @@ +/* + * Virtio VMChannel Device + * + * Copyright RedHat, inc. 2008 + * + * 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-kvm.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; + uint32_t id; + size_t len; +} VMChannel; + +typedef struct VMChannelDesc { + uint32_t id; + uint32_t len; +} VMChannelDesc; + +typedef struct VMChannelCfg { + uint32_t count; + uint32_t ids[MAX_VMCHANNEL_DEVICES]; +} VMChannelCfg; + +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 (vmchannel->rq->vring.avail == NULL) + 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; + + 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 = c->id; + desc->len = size; + + c->elem.in_sg[0].iov_base = desc + 1; + c->elem.in_sg[0].iov_len -= sizeof(VMChannelDesc); + + for (i = 0; i < c->elem.in_num && size; i++) { + struct iovec *iov = &c->elem.in_sg[i]; + size_t len; + + len = MIN(size, iov->iov_len); + memcpy(iov->iov_base, buf, len); + size -= len; + buf += len; + } + + if (size) { + fprintf(stderr, "vmchannel: dropping %d bytes of data\n", size); + exit(1); + } + + virtqueue_push(vmchannel->rq, &c->elem, desc->len); + c->len = 0; + virtio_notify(&vmchannel->vdev, vmchannel->rq); +} + +static void virtio_vmchannel_handle_recv(VirtIODevice *vdev, VirtQueue *outputq) +{ + if (kvm_enabled()) + qemu_kvm_notify_work(); +} + +static VMChannel *vmchannel_lookup(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 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; + + 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; + c = vmchannel_lookup(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); + + elem.out_sg[0].iov_base = desc + 1; + elem.out_sg[0].iov_len -= sizeof(VMChannelDesc); + + for (i = 0; i < elem.out_num; i++) { + struct iovec *iov = &elem.out_sg[i]; + + qemu_chr_write(c->hd, iov->iov_base, iov->iov_len); + len += iov->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, *s = NULL; + int i; + + printf("get_conf: %d %d\n", &s->count, &s->ids); + cfg->count = vmchannel_desc_idx; + for (i = 0; i < vmchannel_desc_idx; i++) + cfg->ids[i] = vmchannel_descs[i].id; +} + +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, + sizeof(VMChannelCfg), sizeof(VirtIOVMChannel)); + + vmchannel->vdev.get_features = virtio_vmchannel_get_features; + vmchannel->vdev.get_config = virtio_vmchannel_update_config; + + 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, uint32_t deviceid) +{ + VMChannel *c = &vmchannel_descs[vmchannel_desc_idx++]; + + c->hd = hd; + c->id = deviceid; + qemu_chr_add_handlers(hd, vmchannel_can_read, vmchannel_read, NULL, c); +} diff --git a/qemu/hw/virtio-vmchannel.h b/qemu/hw/virtio-vmchannel.h new file mode 100644 index 0000000..bb9e6b6 --- /dev/null +++ b/qemu/hw/virtio-vmchannel.h @@ -0,0 +1,16 @@ +/* + * Virtio VMChannel Device + * + * Copyright RedHat, inc. 2008 + * + * 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 + +#endif diff --git a/qemu/sysemu.h b/qemu/sysemu.h index b9abf99..192f8e1 100644 --- a/qemu/sysemu.h +++ b/qemu/sysemu.h @@ -189,6 +189,10 @@ extern CharDriverState *serial_hds[MAX_SERIAL_PORTS]; extern CharDriverState *parallel_hds[MAX_PARALLEL_PORTS]; +#define MAX_VMCHANNEL_DEVICES 4 +void virtio_vmchannel_init(PCIBus *bus); +void vmchannel_init(CharDriverState *hd, uint32_t deviceid); + #ifdef NEED_CPU_H /* loader.c */ int get_image_size(const char *filename); diff --git a/qemu/vl.c b/qemu/vl.c index 36e3bb7..b19fb88 100644 --- a/qemu/vl.c +++ b/qemu/vl.c @@ -210,6 +210,7 @@ int graphic_depth = 15; static int full_screen = 0; static int no_frame = 0; 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 @@ -8584,6 +8585,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 di:DI,dev redirect the vmchannel device with device id DI, 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" @@ -8703,6 +8705,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, @@ -8820,6 +8823,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 }, { "incoming", 1, QEMU_OPTION_incoming }, { "full-screen", 0, QEMU_OPTION_full_screen }, @@ -9247,6 +9251,8 @@ int main(int argc, char **argv) int serial_device_index; const char *parallel_devices[MAX_PARALLEL_PORTS]; int parallel_device_index; + const char *vmchannel_devices[MAX_VMCHANNEL_DEVICES]; + int vmchannel_device_index; const char *loadvm = NULL; QEMUMachine *machine; const char *cpu_model; @@ -9320,6 +9326,10 @@ int main(int argc, char **argv) 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; @@ -9706,6 +9716,12 @@ int main(int argc, char **argv) parallel_devices[parallel_device_index] = optarg; parallel_device_index++; break; + 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++] = optarg; case QEMU_OPTION_loadvm: loadvm = optarg; break; @@ -10232,6 +10248,29 @@ int main(int argc, char **argv) if (kvm_enabled()) kvm_init_ap(); + for(i = 0; i < vmchannel_device_index; i++) { + const char *devname = vmchannel_devices[i]; + int devid; + char *di; + if (!devname) + continue; + + if (strstart(devname, "di:", (const char**)&di)) { + devid = strtol(di, &di, 16); + di++; + } else { + fprintf(stderr, "qemu: could not find vmchannel device id in " + "'%s'\n", devname); + exit(1); + } + vmchannel_hds[i] = qemu_chr_open(di); + if (!vmchannel_hds[i]) { + fprintf(stderr, "qemu: could not open vmchannel device '%s'\n", di); + exit(1); + } + vmchannel_init(vmchannel_hds[i], devid); + } + machine->init(ram_size, vga_ram_size, boot_devices, ds, kernel_filename, kernel_cmdline, initrd_filename, cpu_model);