Hello, Sometimes there is a need to pass various bits of information between host and guest (mostly for management purposes such as host screen resolution changes or runtime statistics of a guest). To do that we need some way to pass data between host and guest. Attached patch implements vmchannel that can be used for this purpose. It is based on virtio infrastructure and support more then one channel. The vmchannel presents itself as PCI device to a guest so guest driver is also required. The one for linux is attached. It uses netlink connector to communicate with userspace.
Comments are welcome. -- Gleb.
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);
ifneq (${KERNELRELEASE},) obj-m += vmchannel.o else KERNEL_SOURCE := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C ${KERNEL_SOURCE} SUBDIRS=$(PWD) modules clean : rm *.o *.ko endif
#include <linux/module.h> #include <linux/interrupt.h> #include <linux/connector.h> #include <linux/virtio.h> #include <linux/scatterlist.h> #include <linux/virtio_config.h> #include <linux/list.h> #include <linux/spinlock.h> #include "vmchannel.h" #define VMCHANNEL_DEBUG #ifdef VMCHANNEL_DEBUG #define vmc_print(A, B...) do { \ if(vmchannel.vdev) \ dev_printk(A, &vmchannel.vdev->dev, B); \ else \ printk(A "vmchannel: " B); \ }while (0) #else #define vmc_print() do {} while(0) #endif #define VMCHANNEL_CONNECTOR_IDX 10 #define MAX_PACKET_LEN 1024 struct vmchannel_info { struct virtio_device *vdev; struct virtqueue *rq; struct virtqueue *sq; struct tasklet_struct tasklet; __u16 channel_count; __u32 *channel_ids; __u32 seq; }; struct vmchannel_desc { __u32 id; __u32 len; }; struct vmchannel_hdr { struct vmchannel_desc desc; struct cn_msg msg; }; static struct vmchannel_info vmchannel; static int add_recq_buf(struct vmchannel_info *vmc, struct vmchannel_hdr *hdr) { struct scatterlist sg[2]; sg_init_table(sg, 2); sg_init_one(&sg[0], hdr, sizeof(struct vmchannel_desc)); sg_init_one(&sg[1], hdr->msg.data, MAX_PACKET_LEN); if (!vmc->rq->vq_ops->add_buf(vmc->rq, sg, 0, 2, hdr)) return 1; kfree(hdr); return 0; } static int try_fill_recvq(struct vmchannel_info *vmc) { int num = 0; for (;;) { struct vmchannel_hdr *hdr; hdr = kmalloc(sizeof(*hdr) + MAX_PACKET_LEN, GFP_KERNEL); if (unlikely(!hdr)) break; if(!add_recq_buf(vmc, hdr)) break; num++; } if (num) vmc->rq->vq_ops->kick(vmc->rq); return num; } static void vmchannel_recv(unsigned long data) { struct vmchannel_info *vmc = (struct vmchannel_info *)data; struct vmchannel_hdr *hdr; unsigned int len; int posted = 0; vmc_print(KERN_WARNING, "vmchannel_recv\n"); while ((hdr = vmc->rq->vq_ops->get_buf(vmc->rq, &len))) { int rc; if (hdr->desc.len == len) { hdr->msg.id.idx = VMCHANNEL_CONNECTOR_IDX; hdr->msg.id.val = hdr->desc.id; hdr->msg.seq = vmc->seq++; hdr->msg.len = len; hdr->msg.ack = random32(); rc = cn_netlink_send(&hdr->msg, VMCHANNEL_CONNECTOR_IDX, GFP_ATOMIC); if (rc < 0) vmc_print(KERN_ERR, "Failed to send netlink message\n"); } else vmc_print(KERN_ERR, "wrong length in descriptor (%d instead of %d)\n", hdr->desc.len, len); posted += add_recq_buf(vmc, hdr); } if (posted) vmc->rq->vq_ops->kick(vmc->rq); } static void recvq_notify(struct virtqueue *recvq) { struct vmchannel_info *vmc = recvq->vdev->priv; vmc_print(KERN_WARNING, "recvq_notify\n"); tasklet_schedule(&vmc->tasklet); } static void cleanup_sendq(struct vmchannel_info *vmc) { char *buf; unsigned int len; while ((buf = vmc->sq->vq_ops->get_buf(vmc->sq, &len))) kfree(buf); } static void sendq_notify(struct virtqueue *sendq) { struct vmchannel_info *vmc = sendq->vdev->priv; vmc_print(KERN_WARNING, "sendq_notify\n"); cleanup_sendq(vmc); } static void vmchannel_cn_callback(void *data) { struct vmchannel_desc *desc; struct cn_msg *msg = data; struct scatterlist sg; char *buf; int err; vmc_print(KERN_WARNING, "cn_callback\n"); desc = kmalloc(msg->len + sizeof(*desc), GFP_KERNEL); if (!desc) return; desc->id = msg->id.val; desc->len = msg->len; buf = (char*)(desc + 1); memcpy(buf, msg->data, msg->len); sg_init_one(&sg, desc, msg->len + sizeof(*desc)); err = vmchannel.sq->vq_ops->add_buf(vmchannel.sq, &sg, 1, 0, desc); if (err) { vmc_print(KERN_ERR, "No space in a send queue\n"); kfree(desc); } else vmchannel.sq->vq_ops->kick(vmchannel.sq); } static int vmchannel_probe(struct virtio_device *vdev) { struct vmchannel_info *vmc = &vmchannel; struct cb_id cn_id; int r, i; __u32 count; cn_id.idx = VMCHANNEL_CONNECTOR_IDX; vdev->priv = vmc; vmc->vdev = vdev; vmc_print(KERN_WARNING, "probe\n"); vdev->config->get(vdev, 0, &count, sizeof(count)); if (count == 0) { vmc_print(KERN_ERR, "no channels present\n"); return -ENODEV; } vmc_print(KERN_WARNING, "%d channel detected\n", count); vmc->channel_count = count; vmc->channel_ids = kmalloc(count * 4, GFP_KERNEL); if (!vmc->channel_ids) return -ENOMEM; vdev->config->get(vdev, sizeof(count), vmc->channel_ids, count * 4); vmc->rq = vdev->config->find_vq(vdev, 0, recvq_notify); if (IS_ERR(vmc->rq)) { r = PTR_ERR(vmc->rq); goto out; } vmc->sq = vdev->config->find_vq(vdev, 1, sendq_notify); if (IS_ERR(vmc->sq)) { r = PTR_ERR(vmc->sq); goto out; } for (i = 0; i < vmc->channel_count; i++) { cn_id.val = vmc->channel_ids[i]; vmc_print(KERN_WARNING, "add callback for channel %d\n", cn_id.val); r = cn_add_callback(&cn_id, "vmchannel", vmchannel_cn_callback); if (r) goto cn_unreg; } tasklet_init(&vmc->tasklet, vmchannel_recv, (unsigned long)vmc); if(!try_fill_recvq(vmc)) { r = -ENOMEM; goto kill_task; } return 0; kill_task: tasklet_kill(&vmc->tasklet); cn_unreg: for (i = 0; i < vmc->channel_count; i++) { cn_id.val = vmc->channel_ids[i]; cn_del_callback(&cn_id); } out: if (vmc->sq) vdev->config->del_vq(vmc->sq); if (vmc->rq) vdev->config->del_vq(vmc->rq); if (vmc) kfree(vmc); return r; } static void vmchannel_remove(struct virtio_device *vdev) { struct vmchannel_info *vmc = vdev->priv; struct cb_id cn_id; int i; vmc_print(KERN_WARNING, "remove\n"); /* Stop all the virtqueues. */ vdev->config->reset(vdev); tasklet_kill(&vmc->tasklet); cn_id.idx = VMCHANNEL_CONNECTOR_IDX; for (i = 0; i < vmc->channel_count; i++) { cn_id.val = vmc->channel_ids[i]; cn_del_callback(&cn_id); } vdev->config->del_vq(vmc->rq); vdev->config->del_vq(vmc->sq); } static struct virtio_device_id id_table[] = { { VIRTIO_ID_VMCHANNEL, VIRTIO_DEV_ANY_ID }, { 0 }, }; static struct virtio_driver virtio_vmchannel = { .driver.name = "virtio-vmchannel", .driver.owner = THIS_MODULE, .id_table = id_table, .probe = vmchannel_probe, .remove = __devexit_p(vmchannel_remove), }; static int __init init(void) { vmc_print(KERN_WARNING, "init\n"); return register_virtio_driver(&virtio_vmchannel); } static void __exit fini(void) { vmc_print(KERN_WARNING, "fini\n"); unregister_virtio_driver(&virtio_vmchannel); } module_init(init); module_exit(fini); MODULE_DEVICE_TABLE(virtio, id_table); MODULE_DESCRIPTION("Virtio vmchannel driver"); MODULE_LICENSE("GPL");
#ifndef VMCHANNEL_H #define VMCHANNEL_H #define VIRTIO_ID_VMCHANNEL 6 #define VMCHANNEL_CHANNELS 32 #endif