From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1Mq8AP-0000et-Sk for qemu-devel@nongnu.org; Tue, 22 Sep 2009 12:24:38 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1Mq8AL-0000cb-5Z for qemu-devel@nongnu.org; Tue, 22 Sep 2009 12:24:37 -0400 Received: from [199.232.76.173] (port=40672 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Mq8AL-0000cW-1c for qemu-devel@nongnu.org; Tue, 22 Sep 2009 12:24:33 -0400 Received: from mx1.redhat.com ([209.132.183.28]:27526) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1Mq8AK-0002gu-1J for qemu-devel@nongnu.org; Tue, 22 Sep 2009 12:24:32 -0400 Received: from int-mx08.intmail.prod.int.phx2.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id n8MGOVIe017598 for ; Tue, 22 Sep 2009 12:24:31 -0400 From: Amit Shah Date: Tue, 22 Sep 2009 21:53:46 +0530 Message-Id: <1253636627-12746-4-git-send-email-amit.shah@redhat.com> In-Reply-To: <1253636627-12746-3-git-send-email-amit.shah@redhat.com> References: <1253636627-12746-1-git-send-email-amit.shah@redhat.com> <1253636627-12746-2-git-send-email-amit.shah@redhat.com> <1253636627-12746-3-git-send-email-amit.shah@redhat.com> Subject: [Qemu-devel] [PATCH 3/4] virtio-console: Add support for multiple ports for generic guest-host communication List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: Amit Shah This interface extends the virtio-console device to handle multiple ports that present a char device from which bits can be sent and read. Sample uses for such a device can be obtaining info from the guest like the file systems used, apps installed, etc. for offline usage and logged-in users, clipboard copy-paste, etc. for online usage. For requirements, use-cases and some history see http://www.linux-kvm.org/page/VMchannel_Requirements Signed-off-by: Amit Shah --- Makefile.target | 2 +- hw/pc.c | 9 - hw/qdev.c | 9 +- hw/virtio-console-bus.c | 111 ++++++++ hw/virtio-console.c | 646 +++++++++++++++++++++++++++++++++++++++++++---- hw/virtio-console.h | 73 ++++++ qemu-options.hx | 8 - sysemu.h | 7 +- vl.c | 37 --- 9 files changed, 781 insertions(+), 121 deletions(-) create mode 100644 hw/virtio-console-bus.c diff --git a/Makefile.target b/Makefile.target index 609015b..22a2644 100644 --- a/Makefile.target +++ b/Makefile.target @@ -173,7 +173,7 @@ obj-y = vl.o monitor.o pci.o isa_mmio.o machine.o \ gdbstub.o gdbstub-xml.o # virtio has to be here due to weird dependency between PCI and virtio-net. # need to fix this properly -obj-y += virtio-blk.o virtio-balloon.o virtio-net.o virtio-console.o virtio-pci.o +obj-y += virtio-blk.o virtio-balloon.o virtio-net.o virtio-console.o virtio-console-bus.o virtio-pci.o obj-$(CONFIG_KVM) += kvm.o kvm-all.o LIBS+=-lz diff --git a/hw/pc.c b/hw/pc.c index 240cfe0..70b1ce9 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -1463,15 +1463,6 @@ static void pc_init1(ram_addr_t ram_size, extboot_init(info->bdrv, 1); } - /* Add virtio console devices */ - if (pci_enabled) { - for(i = 0; i < MAX_VIRTIO_CONSOLES; i++) { - if (virtcon_hds[i]) { - pci_create_simple(pci_bus, -1, "virtio-console-pci"); - } - } - } - #ifdef CONFIG_KVM_DEVICE_ASSIGNMENT if (kvm_enabled()) { add_assigned_devices(pci_bus, assigned_devices, assigned_devices_index); diff --git a/hw/qdev.c b/hw/qdev.c index 43b1beb..1d79db0 100644 --- a/hw/qdev.c +++ b/hw/qdev.c @@ -243,13 +243,8 @@ void qdev_free(DeviceState *dev) CharDriverState *qdev_init_chardev(DeviceState *dev) { static int next_serial; - static int next_virtconsole; - /* FIXME: This is a nasty hack that needs to go away. */ - if (strncmp(dev->info->name, "virtio", 6) == 0) { - return virtcon_hds[next_virtconsole++]; - } else { - return serial_hds[next_serial++]; - } + + return serial_hds[next_serial++]; } BusState *qdev_get_parent_bus(DeviceState *dev) diff --git a/hw/virtio-console-bus.c b/hw/virtio-console-bus.c new file mode 100644 index 0000000..d8be5bb --- /dev/null +++ b/hw/virtio-console-bus.c @@ -0,0 +1,111 @@ +/* + * A bus for connecting virtio-console ports + * + * Copyright (c) 2009 Red Hat, Inc. + * + * Author(s): + * Amit Shah + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include "sysbus.h" +#include "virtio-console.h" + +/* The virtio-console bus on top of which the ports will ride as devices */ +struct VirtConBus { + BusState qbus; + uint32_t assigned; +}; +static VirtConBus *virtcon_bus; + +static void virtcon_bus_dev_print(Monitor *mon, DeviceState *dev, int indent); + +static struct BusInfo virtcon_bus_info = { + .name = "virtio-console-bus", + .size = sizeof(VirtConBus), + .print_dev = virtcon_bus_dev_print, +}; + +VirtConBus *virtcon_bus_new(DeviceState *dev) +{ + if (virtcon_bus) { + fprintf(stderr, "Can't create a second virtio-console bus\n"); + return NULL; + } + if (!dev) { + dev = qdev_create(NULL, "virtio-console-sysbus"); + qdev_init(dev); + } + + virtcon_bus = FROM_QBUS(VirtConBus, qbus_create(&virtcon_bus_info, dev, NULL)); + return virtcon_bus; +} + +static int virtcon_port_qdev_init(DeviceState *qdev, DeviceInfo *base) +{ + VirtConDevice *dev = DO_UPCAST(VirtConDevice, qdev, qdev); + VirtConPortDeviceInfo *info = DO_UPCAST(VirtConPortDeviceInfo, qdev, base); + + return info->init(dev); +} + +void virtcon_port_qdev_register(VirtConPortDeviceInfo *info) +{ + info->qdev.init = virtcon_port_qdev_init; + info->qdev.bus_info = &virtcon_bus_info; + qdev_register(&info->qdev); +} + +VirtConDevice *virtcon_create(const char *name) +{ + DeviceState *dev; + + if (!virtcon_bus) { + qemu_error("Tried to create a port %s with no virtio-console bus present.\n", + name); + return NULL; + } + dev = qdev_create(&virtcon_bus->qbus, name); + return DO_UPCAST(VirtConDevice, qdev, dev); +} + +VirtConDevice *virtcon_create_simple(const char *name) +{ + VirtConDevice *dev; + + dev = virtcon_create(name); + if (qdev_init(&dev->qdev) != 0) { + qdev_free(&dev->qdev); + return NULL; + } + return dev; +} + +static void virtcon_bus_dev_print(Monitor *mon, DeviceState *dev, int indent) +{ + VirtConDevice *d = DO_UPCAST(VirtConDevice, qdev, dev); + if (&d->qdev) { + ; + } +} + +static int virtcon_bus_init(SysBusDevice *dev) +{ + return 0; +} + +static SysBusDeviceInfo virtcon_sysbus_info = { + .init = virtcon_bus_init, + .qdev.name = "virtio-console-sysbus", + .qdev.size = sizeof(SysBusDevice), + .qdev.no_user = 1, +}; + +static void virtcon_sysbus_register_devices(void) +{ + sysbus_register_withprop(&virtcon_sysbus_info); +} + +device_init(virtcon_sysbus_register_devices) diff --git a/hw/virtio-console.c b/hw/virtio-console.c index 92c953c..d750c8e 100644 --- a/hw/virtio-console.c +++ b/hw/virtio-console.c @@ -2,9 +2,11 @@ * Virtio Console Device * * Copyright IBM, Corp. 2008 + * Copyright Red Hat, Inc. 2009 * * Authors: * Christian Ehrhardt + * Amit Shah * * This work is licensed under the terms of the GNU GPL, version 2. See * the COPYING file in the top-level directory. @@ -12,39 +14,320 @@ */ #include "hw.h" +#include "monitor.h" +#include "qemu-queue.h" #include "qemu-char.h" +#include "sysbus.h" #include "virtio.h" #include "virtio-console.h" - typedef struct VirtIOConsole { VirtIODevice vdev; VirtQueue *ivq, *ovq; - CharDriverState *chr; + + VirtIOConsolePort* ports[MAX_VIRTIO_CONSOLE_PORTS]; + struct virtio_console_config config; + + uint32_t guest_features; } VirtIOConsole; -static VirtIOConsole *to_virtio_console(VirtIODevice *vdev) +/* This struct holds individual buffers received for each port */ +typedef struct VirtIOConsolePortBuffer { + QTAILQ_ENTRY(VirtIOConsolePortBuffer) next; + + uint8_t *buf; + + size_t len; /* length of the buffer */ + + /* The size of one write request as issued by the guest. The + * buffer could be split in this list but using the size value in + * the first buffer for each write we can identify complete + * writes + */ + size_t size; +} VirtIOConsolePortBuffer; + +struct VirtIOConsolePort { + DeviceState dev; + + VirtIOConsole *vcon; + CharDriverState *hd; + + char *name; + + QTAILQ_HEAD(, VirtIOConsolePortBuffer) unflushed_buffer_head; + + bool guest_connected; + bool host_connected; +}; + +static VirtIOConsole *virtio_console; + +static VirtIOConsolePort *get_port_from_id(VirtIOConsole *vcon, uint32_t id) +{ + if (id > MAX_VIRTIO_CONSOLE_PORTS) + return NULL; + + return vcon->ports[id]; +} + +static int get_id_from_port(VirtIOConsolePort *port) +{ + uint32_t i; + + for (i = 0; i < MAX_VIRTIO_CONSOLE_PORTS; i++) { + if (port == port->vcon->ports[i]) { + return i; + } + } + return VIRTIO_CONSOLE_BAD_ID; +} + +static bool use_multiport(void) +{ + return virtio_console->guest_features & (1 << VIRTIO_CONSOLE_F_MULTIPORT); +} + +static bool is_internal(uint32_t flags) +{ + return flags & VIRTIO_CONSOLE_ID_INTERNAL; +} + +static bool is_console(uint32_t port_nr) +{ + return false; +} + +static bool has_complete_data(VirtIOConsolePort *port) +{ + VirtIOConsolePortBuffer *buf; + size_t len, size; + + len = 0; + size = 0; + QTAILQ_FOREACH(buf, &port->unflushed_buffer_head, next) { + if (!buf->size && buf == QTAILQ_FIRST(&port->unflushed_buffer_head)) { + /* We have a buffer that's lost its way; just flush it */ + return true; + } + if (size && buf->size) { + /* Start of the next write request */ + return true; + } + if (buf->size) { + size = buf->size; + } + len += buf->len; + if (len == size) { + return true; + } + } + return false; +} + +static size_t flush_buf(VirtIOConsolePort *port, const uint8_t *buf, size_t len) +{ + if (!port->hd) { + return 0; + } + return qemu_chr_write(port->hd, buf, len); +} + +static void flush_queue(VirtIOConsolePort *port) +{ + VirtIOConsolePortBuffer *buf, *buf2; + uint8_t *outbuf; + size_t outlen, outsize; + + while (!QTAILQ_EMPTY(&port->unflushed_buffer_head)) { + if (!has_complete_data(port)) { + break; + } + + buf = QTAILQ_FIRST(&port->unflushed_buffer_head); + if (!buf->size) { + /* This is a buf that didn't get consumed as part of a + * previous data stream. Bad thing, shouldn't + * happen. But let's handle it nonetheless + */ + flush_buf(port, buf->buf, buf->len); + QTAILQ_REMOVE(&port->unflushed_buffer_head, buf, next); + qemu_free(buf->buf); + qemu_free(buf); + continue; + } + + outlen = 0; + outsize = buf->size; + outbuf = qemu_mallocz(outsize); + QTAILQ_FOREACH_SAFE(buf, &port->unflushed_buffer_head, next, buf2) { + memcpy(outbuf + outlen, buf->buf, buf->len); + outlen += buf->len; + QTAILQ_REMOVE(&port->unflushed_buffer_head, buf, next); + qemu_free(buf->buf); + qemu_free(buf); + + if (outlen == outsize) + break; + } + flush_buf(port, outbuf, outlen); + qemu_free(outbuf); + } +} + + +static size_t write_to_port(VirtIOConsolePort *port, + const uint8_t *buf, size_t size, uint32_t flags) { - return (VirtIOConsole *)vdev; + VirtQueue *vq = port->vcon->ivq; + VirtQueueElement elem; + size_t offset = 0; + size_t len = 0; + + if (!virtio_queue_ready(vq)) { + return 0; + } + + if (!use_multiport() && is_internal(flags)) { + return 0; + } + + while (offset < size) { + struct virtio_console_header header; + int i, header_len; + + header_len = use_multiport() ? sizeof(header) : 0; + + if (!virtqueue_pop(vq, &elem)) { + break; + } + if (elem.in_sg[0].iov_len < header_len) { + /* We can't even store our port number in this buffer. Bug? */ + fprintf(stderr, "virtio-console: size %zd less than expected\n", + elem.in_sg[0].iov_len); + exit(1); + } + header.id = cpu_to_le32(get_id_from_port(port)); + header.flags = flags; + memcpy(elem.in_sg[0].iov_base, &header, header_len); + + for (i = 0; offset < size && i < elem.in_num; i++) { + len = MIN(elem.in_sg[i].iov_len - header_len, size - offset); + + memcpy(elem.in_sg[i].iov_base + header_len, buf + offset, len); + offset += len; + header_len = 0; + } + header_len = use_multiport() ? sizeof(header) : 0; + virtqueue_push(vq, &elem, len + header_len); + } + virtio_notify(&port->vcon->vdev, vq); + return offset; } +/* Guest wants to notify us of some event */ +static void handle_control_message(VirtIOConsolePort *port, + struct virtio_console_control *cpkt) +{ + uint8_t *buffer; + size_t buffer_len; + + switch(cpkt->event) { + case VIRTIO_CONSOLE_PORT_OPEN: + port->guest_connected = cpkt->value; + break; + case VIRTIO_CONSOLE_PORT_NAME: + if (!port->name) { + const char *default_string = "org.qemu.console"; + + port->name = qemu_malloc(strlen(default_string) + 1); + memcpy(port->name, default_string, strlen(default_string) + 1); + } + buffer_len = sizeof(*cpkt) + strlen(port->name) + 1; + buffer = qemu_malloc(buffer_len); + + memcpy(buffer, cpkt, sizeof(*cpkt)); + memcpy(buffer + sizeof(*cpkt), port->name, strlen(port->name)); + buffer[buffer_len - 1] = 0; + + write_to_port(port, buffer, buffer_len, VIRTIO_CONSOLE_ID_INTERNAL); + qemu_free(buffer); + } +} + +/* Guest wrote something to some port. + * + * Flush the data in the entire chunk that we received rather than + * splitting it into multiple buffers. VNC clients don't consume split + * buffers + */ static void virtio_console_handle_output(VirtIODevice *vdev, VirtQueue *vq) { - VirtIOConsole *s = to_virtio_console(vdev); + VirtIOConsole *vcon; VirtQueueElement elem; + vcon = DO_UPCAST(VirtIOConsole, vdev, vdev); + while (virtqueue_pop(vq, &elem)) { - ssize_t len = 0; - int d; + VirtIOConsolePort *port; + VirtIOConsolePortBuffer *buf; + struct virtio_console_header header; + int header_len; - for (d = 0; d < elem.out_num; d++) { - len += qemu_chr_write(s->chr, (uint8_t *)elem.out_sg[d].iov_base, - elem.out_sg[d].iov_len); + buf = qemu_mallocz(sizeof(*buf)); + + if (use_multiport()) { + header_len = sizeof(header); + + memcpy(&header, elem.out_sg[0].iov_base, header_len); + port = get_port_from_id(vcon, header.id); + if (!port) { + qemu_free(buf); + goto next_buf; + } + } else { + header_len = 0; + port = get_port_from_id(vcon, 0); + } + + /* The guest always sends only one sg */ + buf->len = elem.out_sg[0].iov_len - header_len; + buf->buf = qemu_mallocz(buf->len); + memcpy(buf->buf, elem.out_sg[0].iov_base + header_len, buf->len); + + if (use_multiport() && is_internal(header.flags)) { + handle_control_message(port, + (struct virtio_console_control *)buf->buf); + qemu_free(buf->buf); + qemu_free(buf); + goto next_buf; + } + if (!use_multiport() || is_console(get_id_from_port(port))) { + flush_buf(port, buf->buf, buf->len); + qemu_free(buf->buf); + qemu_free(buf); + goto next_buf; } - virtqueue_push(vq, &elem, len); - virtio_notify(vdev, vq); + QTAILQ_INSERT_TAIL(&port->unflushed_buffer_head, buf, next); + if (use_multiport()) { + /* Only the first buffer in a stream will have this + * set. This will help us identify the first buffer and + * the remaining buffers in the stream based on length + */ + buf->size = header.size; + } else { + /* We always want to flush all the buffers in this case */ + buf->size = buf->len; + } + if (!port->host_connected) { + goto next_buf; + } + flush_queue(port); + next_buf: + virtqueue_push(vq, &elem, elem.out_sg[0].iov_len); } + virtio_notify(vdev, vq); } static void virtio_console_handle_input(VirtIODevice *vdev, VirtQueue *vq) @@ -53,94 +336,351 @@ static void virtio_console_handle_input(VirtIODevice *vdev, VirtQueue *vq) static uint32_t virtio_console_get_features(VirtIODevice *vdev) { - return 0; + return 1 << VIRTIO_CONSOLE_F_MULTIPORT; +} + +static void virtio_console_set_features(VirtIODevice *vdev, uint32_t features) +{ + virtio_console->guest_features = features; } +/* Guest requested config info */ +static void virtio_console_get_config(VirtIODevice *vdev, uint8_t *config_data) +{ + VirtIOConsole *vcon; + + vcon = DO_UPCAST(VirtIOConsole, vdev, vdev); + memcpy(config_data, &vcon->config, sizeof(struct virtio_console_config)); +} + +static void virtio_console_set_config(VirtIODevice *vdev, + const uint8_t *config_data) +{ + struct virtio_console_config config; + + memcpy(&config, config_data, sizeof(config)); +} + +static void send_control_event(VirtIOConsolePort *port, + struct virtio_console_control *cpkt) +{ + write_to_port(port, (uint8_t *)cpkt, sizeof(*cpkt), + VIRTIO_CONSOLE_ID_INTERNAL); +} + +/* Readiness of the guest to accept data on a port */ static int vcon_can_read(void *opaque) { - VirtIOConsole *s = (VirtIOConsole *) opaque; + VirtIOConsolePort *port = opaque; + VirtQueue *vq = port->vcon->ivq; + int size, header_len; - if (!virtio_queue_ready(s->ivq) || - !(s->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK) || - virtio_queue_empty(s->ivq)) - return 0; + if (use_multiport()) { + header_len = sizeof(struct virtio_console_header); + } else { + header_len = 0; + } - /* current implementations have a page sized buffer. - * We fall back to a one byte per read if there is not enough room. - * It would be cool to have a function that returns the available byte - * instead of checking for a limit */ - if (virtqueue_avail_bytes(s->ivq, TARGET_PAGE_SIZE, 0)) - return TARGET_PAGE_SIZE; - if (virtqueue_avail_bytes(s->ivq, 1, 0)) - return 1; + if (!virtio_queue_ready(vq) || + !(port->vcon->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK) || + virtio_queue_empty(vq)) { + return 0; + } + if (!port->guest_connected) { + return 0; + } + size = TARGET_PAGE_SIZE; + if (virtqueue_avail_bytes(vq, size, 0)) { + return size - header_len; + } + size = header_len + 1; + if (virtqueue_avail_bytes(vq, size, 0)) { + return size - header_len; + } return 0; } +/* Send data from a char device over to the guest */ static void vcon_read(void *opaque, const uint8_t *buf, int size) { - VirtIOConsole *s = (VirtIOConsole *) opaque; - VirtQueueElement elem; - int offset = 0; + VirtIOConsolePort *port = opaque; - /* The current kernel implementation has only one outstanding input - * buffer of PAGE_SIZE. Nevertheless, this function is prepared to - * handle multiple buffers with multiple sg element for input */ - while (offset < size) { - int i = 0; - if (!virtqueue_pop(s->ivq, &elem)) - break; - while (offset < size && i < elem.in_num) { - int len = MIN(elem.in_sg[i].iov_len, size - offset); - memcpy(elem.in_sg[i].iov_base, buf + offset, len); - offset += len; - i++; - } - virtqueue_push(s->ivq, &elem, size); - } - virtio_notify(&s->vdev, s->ivq); + write_to_port(port, buf, size, 0); } static void vcon_event(void *opaque, int event) { - /* we will ignore any event for the time being */ + VirtIOConsolePort *port = opaque; + struct virtio_console_control cpkt; + bool update_needed; + + cpkt.event = VIRTIO_CONSOLE_PORT_OPEN; + + update_needed = false; + switch (event) { + case CHR_EVENT_OPENED: { + cpkt.value = 1; + update_needed = true; + port->host_connected = true; + + /* Flush any buffers that were pending while the chardev was + * disconnected + */ + flush_queue(port); + break; + } + case CHR_EVENT_CLOSED: + cpkt.value = 0; + update_needed = true; + port->host_connected = false; + break; + default: + break; + } + if (!update_needed) { + return; + } + send_control_event(port, &cpkt); +} + +static void set_active_in_map(uint32_t *map, uint32_t idx) +{ + int i; + + i = idx / 32; + idx %= 32; + + map[i] |= 1U << idx; +} + +/* + * Get the guest_connected status after a migration since it's all + * packed in the same way as ports_map in the config space is packed. + * + * return VIRTIO_CONSOLE_BAD_ID if nothing was found in the map, else + * return the port number whose guest was connected before the + * migration + */ +static uint32_t find_next_active_in_map(uint32_t *map, unsigned int i) +{ + uint32_t port_nr; + + port_nr = ffs(*map); + if (!port_nr) { + return VIRTIO_CONSOLE_BAD_ID; + } + /* We used ffs above */ + port_nr--; + + *map &= ~(1U << port_nr); + + port_nr += i * 32; + return port_nr; +} + +static int vcon_port_initfn(VirtConDevice *dev) +{ + VirtIOConsolePort *port; + + port = DO_UPCAST(VirtIOConsolePort, dev, &dev->qdev); + + QTAILQ_INIT(&port->unflushed_buffer_head); + + port->vcon = virtio_console; + + qemu_chr_add_handlers(port->hd, vcon_can_read, vcon_read, vcon_event, port); + + port->vcon->ports[port->vcon->config.nr_active_ports++] = port; + + /* Send an update to the guest about this new port added */ + virtio_notify_config(&port->vcon->vdev); + + return 0; } static void virtio_console_save(QEMUFile *f, void *opaque) { VirtIOConsole *s = opaque; + uint32_t guest_connected_map[MAX_VIRTIO_CONSOLE_PORTS / 32]; + unsigned int i, nr_bufs, active_ports; + /* The virtio device */ virtio_save(&s->vdev, f); + /* The config space */ + qemu_put_be16s(f, &s->config.cols); + qemu_put_be16s(f, &s->config.rows); + qemu_put_be32s(f, &s->config.nr_active_ports); + + active_ports = le32_to_cpu(s->config.nr_active_ports); + + /* Items in struct VirtIOConsole */ + qemu_put_be32s(f, &s->guest_features); + + /* + * Items in struct VirtIOConsolePort + * guest_connected is a single bit in each port; pack all these + * bits in uint32_t words and then send them across + */ + for (i = 0; i < active_ports / 32; i++) { + guest_connected_map[i] = 0; + } + for (i = 0; i < active_ports; i++) { + if (s->ports[i]->guest_connected) { + set_active_in_map(guest_connected_map, i); + } + } + for (i = 0; i < active_ports / 32; i++) { + qemu_put_be32s(f, &guest_connected_map[i]); + } + + /* All the pending buffers from active ports */ + for (i = 0; i < active_ports; i++) { + VirtIOConsolePortBuffer *buf; + + nr_bufs = 0; + QTAILQ_FOREACH(buf, &s->ports[i]->unflushed_buffer_head, next) { + nr_bufs++; + } + /* First the port number, then the nr of bufs and then the bufs */ + qemu_put_be32s(f, &i); + qemu_put_be32s(f, &nr_bufs); + if (!nr_bufs) { + continue; + } + QTAILQ_FOREACH(buf, &s->ports[i]->unflushed_buffer_head, next) { + qemu_put_be64s(f, &buf->len); + qemu_put_be64s(f, &buf->size); + qemu_put_buffer(f, buf->buf, buf->len); + } + } } static int virtio_console_load(QEMUFile *f, void *opaque, int version_id) { VirtIOConsole *s = opaque; + uint32_t guest_connected_map[MAX_VIRTIO_CONSOLE_PORTS / 32]; + unsigned int i, active_ports; - if (version_id != 1) + if (version_id > 2) return -EINVAL; + /* The virtio device */ virtio_load(&s->vdev, f); + + if (version_id < 2) + return 0; + + /* The config space */ + qemu_get_be16s(f, &s->config.cols); + qemu_get_be16s(f, &s->config.rows); + s->config.nr_active_ports = cpu_to_le32(qemu_get_be32(f)); + + active_ports = le32_to_cpu(s->config.nr_active_ports); + + /* Items in struct VirtIOConsole */ + qemu_get_be32s(f, &virtio_console->guest_features); + + /* Items in struct VirtIOConsolePort */ + for (i = 0; i < active_ports / 32; i++) { + guest_connected_map[i] = qemu_get_be32(f); + } + + for (i = 0; i < active_ports / 32; i++) { + uint32_t port_nr, map; + + map = guest_connected_map[i]; + while (1) { + port_nr = find_next_active_in_map(&map, i); + if (port_nr == VIRTIO_CONSOLE_BAD_ID) { + break; + } + s->ports[port_nr]->guest_connected = true; + } + } + + /* All the pending buffers from active ports */ + for (i = 0; i < active_ports; i++) { + VirtIOConsolePortBuffer *buf; + unsigned int nr, nr_bufs; + + /* First the port number, then the nr of bufs and then the bufs */ + qemu_get_be32s(f, &nr); + qemu_get_be32s(f, &nr_bufs); + if (!nr_bufs) { + continue; + } + for (; nr_bufs; nr_bufs--) { + buf = qemu_malloc(sizeof(*buf)); + + qemu_get_be64s(f, &buf->len); + qemu_get_be64s(f, &buf->size); + buf->buf = qemu_malloc(buf->len); + qemu_get_buffer(f, buf->buf, buf->len); + QTAILQ_INSERT_TAIL(&s->ports[nr]->unflushed_buffer_head, buf, next); + } + } + return 0; } VirtIODevice *virtio_console_init(DeviceState *dev) { VirtIOConsole *s; + + if (virtio_console) { + /* + * Linux guests don't support more than one virtio-console devices + * at the moment + */ + return NULL; + } + + if (MAX_VIRTIO_CONSOLE_PORTS % 32) { + /* We require MAX_VIRTIO_CONSOLE_PORTS be a multiple of 32: + * We anyway use up that much space for the bitmap and it + * simplifies some calculations + */ + return NULL; + } + s = (VirtIOConsole *)virtio_common_init("virtio-console", VIRTIO_ID_CONSOLE, - 0, sizeof(VirtIOConsole)); - if (s == NULL) - return NULL; + sizeof(struct virtio_console_config), + sizeof(VirtIOConsole)); + virtio_console = s; s->vdev.get_features = virtio_console_get_features; + s->vdev.set_features = virtio_console_set_features; + s->vdev.get_config = virtio_console_get_config; + s->vdev.set_config = virtio_console_set_config; + /* Add queue for host to guest transfers */ s->ivq = virtio_add_queue(&s->vdev, 128, virtio_console_handle_input); + /* Add queue for guest to host transfers */ s->ovq = virtio_add_queue(&s->vdev, 128, virtio_console_handle_output); - s->chr = qdev_init_chardev(dev); - qemu_chr_add_handlers(s->chr, vcon_can_read, vcon_read, vcon_event, s); + register_savevm("virtio-console", -1, 2, virtio_console_save, + virtio_console_load, s); - register_savevm("virtio-console", -1, 1, virtio_console_save, virtio_console_load, s); + /* Spawn a new virtio-console bus on which the ports will ride as devices */ + virtcon_bus_new(NULL); return &s->vdev; } + +static VirtConPortDeviceInfo virtcon_port_info = { + .qdev.name = "virtconport", + .qdev.size = sizeof(VirtIOConsolePort), + .init = vcon_port_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_CHR("chardev", VirtIOConsolePort, hd), + DEFINE_PROP_STRING("name", VirtIOConsolePort, name), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void virtcon_port_register(void) +{ + virtcon_port_qdev_register(&virtcon_port_info); +} +device_init(virtcon_port_register) diff --git a/hw/virtio-console.h b/hw/virtio-console.h index 84d0717..526713e 100644 --- a/hw/virtio-console.h +++ b/hw/virtio-console.h @@ -2,9 +2,11 @@ * Virtio Console Support * * Copyright IBM, Corp. 2008 + * Copyright Red Hat, Inc. 2009 * * Authors: * Christian Ehrhardt + * Amit Shah * * This work is licensed under the terms of the GNU GPL, version 2. See * the COPYING file in the top-level directory. @@ -13,7 +15,78 @@ #ifndef _QEMU_VIRTIO_CONSOLE_H #define _QEMU_VIRTIO_CONSOLE_H +#include "sysemu.h" + +/* Interface shared between the guest kernel and qemu */ + /* The ID for virtio console */ #define VIRTIO_ID_CONSOLE 3 +/* Invalid port number */ +#define VIRTIO_CONSOLE_BAD_ID (~(uint32_t)0) + +/* Features supported */ +#define VIRTIO_CONSOLE_F_MULTIPORT 1 + +struct virtio_console_config +{ + /* + * These two fields are used by VIRTIO_CONSOLE_F_SIZE which + * isn't implemented here yet + */ + uint16_t cols; + uint16_t rows; + + uint32_t nr_active_ports; +} __attribute__((packed)); + +struct virtio_console_control +{ + uint16_t event; + uint16_t value; +}; + +struct virtio_console_header { + uint32_t id; /* Port id */ + uint32_t flags; /* Some message between host and guest */ + uint32_t size; /* Size that's sent with the first buffer of each stream */ +} __attribute__((packed)); + +/* Messages between host and guest */ +#define VIRTIO_CONSOLE_ID_INTERNAL (1 << 0) + +/* Some events for the internal messages (control packets) */ +#define VIRTIO_CONSOLE_PORT_OPEN 0 +#define VIRTIO_CONSOLE_PORT_NAME 1 + +typedef struct VirtConBus VirtConBus; + +/* In-qemu interface */ + +/* Max. virtio console ports per device */ +#define MAX_VIRTIO_CONSOLE_PORTS 64 + +typedef struct VirtConDevice { + DeviceState qdev; +} VirtConDevice; + +typedef int (*virtcon_qdev_initfn)(VirtConDevice *dev); +typedef int (*virtcon_port_qdev_initfn)(VirtConDevice *dev); + +typedef struct VirtConPortDeviceInfo { + DeviceInfo qdev; + virtcon_port_qdev_initfn init; +} VirtConPortDeviceInfo; + +VirtConDevice *virtcon_create(const char *name); +VirtConDevice *virtcon_create_simple(const char *name); + +void virtcon_port_qdev_register(VirtConPortDeviceInfo *info); + +VirtConBus *virtcon_bus_new(DeviceState *dev); + +typedef struct VirtIOConsolePort VirtIOConsolePort; +void virtio_console_monitor_command(Monitor *mon, + const char *command, const char *param); + #endif diff --git a/qemu-options.hx b/qemu-options.hx index 2f4291d..ca412bf 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1604,14 +1604,6 @@ character to Control-t. @end table ETEXI -DEF("virtioconsole", HAS_ARG, QEMU_OPTION_virtiocon, \ - "-virtioconsole c\n" \ - " set virtio console\n") -STEXI -@item -virtioconsole @var{c} -Set virtio console. -ETEXI - DEF("show-cursor", 0, QEMU_OPTION_show_cursor, \ "-show-cursor show cursor\n") STEXI diff --git a/sysemu.h b/sysemu.h index a8d1549..af0e7d8 100644 --- a/sysemu.h +++ b/sysemu.h @@ -2,6 +2,7 @@ #define SYSEMU_H /* Misc. things related to the system emulator. */ +#include #include "qemu-common.h" #include "qemu-option.h" #include "qemu-queue.h" @@ -233,12 +234,6 @@ extern CharDriverState *serial_hds[MAX_SERIAL_PORTS]; extern CharDriverState *parallel_hds[MAX_PARALLEL_PORTS]; -/* virtio consoles */ - -#define MAX_VIRTIO_CONSOLES 1 - -extern CharDriverState *virtcon_hds[MAX_VIRTIO_CONSOLES]; - #define TFR(expr) do { if ((expr) != -1) break; } while (errno == EINTR) #ifdef HAS_AUDIO diff --git a/vl.c b/vl.c index 3b49c56..b5215b2 100644 --- a/vl.c +++ b/vl.c @@ -213,7 +213,6 @@ static int no_frame = 0; int no_quit = 0; CharDriverState *serial_hds[MAX_SERIAL_PORTS]; CharDriverState *parallel_hds[MAX_PARALLEL_PORTS]; -CharDriverState *virtcon_hds[MAX_VIRTIO_CONSOLES]; #ifdef TARGET_I386 int win2k_install_hack = 0; int rtc_td_hack = 0; @@ -4690,8 +4689,6 @@ int main(int argc, char **argv, char **envp) int serial_device_index; const char *parallel_devices[MAX_PARALLEL_PORTS]; int parallel_device_index; - const char *virtio_consoles[MAX_VIRTIO_CONSOLES]; - int virtio_console_index; const char *loadvm = NULL; QEMUMachine *machine; const char *cpu_model; @@ -4765,10 +4762,6 @@ int main(int argc, char **argv, char **envp) parallel_devices[i] = NULL; parallel_device_index = 0; - for(i = 0; i < MAX_VIRTIO_CONSOLES; i++) - virtio_consoles[i] = NULL; - virtio_console_index = 0; - monitor_devices[0] = "vc:80Cx24C"; for (i = 1; i < MAX_MONITOR_DEVICES; i++) { monitor_devices[i] = NULL; @@ -5228,14 +5221,6 @@ int main(int argc, char **argv, char **envp) exit(1); } break; - case QEMU_OPTION_virtiocon: - if (virtio_console_index >= MAX_VIRTIO_CONSOLES) { - fprintf(stderr, "qemu: too many virtio consoles\n"); - exit(1); - } - virtio_consoles[virtio_console_index] = optarg; - virtio_console_index++; - break; case QEMU_OPTION_parallel: if (parallel_device_index >= MAX_PARALLEL_PORTS) { fprintf(stderr, "qemu: too many parallel ports\n"); @@ -5827,20 +5812,6 @@ int main(int argc, char **argv, char **envp) } } - for(i = 0; i < MAX_VIRTIO_CONSOLES; i++) { - const char *devname = virtio_consoles[i]; - if (devname && strcmp(devname, "none")) { - char label[32]; - snprintf(label, sizeof(label), "virtcon%d", i); - virtcon_hds[i] = qemu_chr_open(label, devname, NULL); - if (!virtcon_hds[i]) { - fprintf(stderr, "qemu: could not open virtio console '%s'\n", - devname); - exit(1); - } - } - } - module_call_init(MODULE_INIT_DEVICE); if (watchdog) { @@ -5968,14 +5939,6 @@ int main(int argc, char **argv, char **envp) } } - for(i = 0; i < MAX_VIRTIO_CONSOLES; i++) { - const char *devname = virtio_consoles[i]; - if (virtcon_hds[i] && devname) { - if (strstart(devname, "vc", 0)) - qemu_chr_printf(virtcon_hds[i], "virtio console%d\r\n", i); - } - } - if (gdbstub_dev && gdbserver_start(gdbstub_dev) < 0) { fprintf(stderr, "qemu: could not open gdbserver on device '%s'\n", gdbstub_dev); -- 1.6.2.5