qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 3/6] virtio-console: Add a virtio-console bus, suppo


From: Amit Shah
Subject: [Qemu-devel] [PATCH 3/6] virtio-console: Add a virtio-console bus, support for multiple ports
Date: Tue, 29 Sep 2009 17:34:45 +0530

This patch migrates virtio-console to the qdev infrastructure, and
creates a new virtio-console bus on which multiple ports are exposed as
devices. The bulk of the code now resides in a new file with
virtio-console.c being just a simple qdev device.

This interface extends the virtio-console device to handle
multiple ports from which bits can be sent and read.

The older -virtioconsole argument is now deprecated in favour of:

    -device virtio-console-pci -device virtconsole

The virtconsole device type accepts a chardev as an argument and a 'name'
argument to identify the corresponding consoles on the host as well as the
guest. The name, if given, is exposed via the 'name' sysfs attribute.

Care has been taken to ensure compatibility with kernels that do not
support multiple ports.

Signed-off-by: Amit Shah <address@hidden>
---
 Makefile.target         |    2 +-
 hw/pc.c                 |    9 -
 hw/qdev.c               |    8 +-
 hw/virtio-console-bus.c |  738 +++++++++++++++++++++++++++++++++++++++++++++++
 hw/virtio-console.c     |  148 +++-------
 hw/virtio-console.h     |  130 +++++++++
 qemu-options.hx         |    8 -
 sysemu.h                |    6 -
 vl.c                    |   37 ---
 9 files changed, 907 insertions(+), 179 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..07cb718 100644
--- a/hw/qdev.c
+++ b/hw/qdev.c
@@ -243,13 +243,9 @@ 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..a5b732a
--- /dev/null
+++ b/hw/virtio-console-bus.c
@@ -0,0 +1,738 @@
+/*
+ * A bus for connecting virtio-console ports
+ *
+ * Copyright (c) 2009 Red Hat, Inc.
+ *
+ * Author(s):
+ *  Amit Shah <address@hidden>
+ *
+ * Some earlier parts are:
+ *   Copyright IBM, Corp. 2008
+ * authored by
+ *  Christian Ehrhardt <address@hidden>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#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;
+
+    VirtConDevice* ports[MAX_VIRTIO_CONSOLE_PORTS];
+    struct virtio_console_config config;
+
+    uint32_t guest_features;
+} VirtIOConsole;
+
+/* This struct holds individual buffers received for each port */
+typedef struct VirtConPortBuffer {
+    QTAILQ_ENTRY(VirtConPortBuffer) 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;
+} VirtConPortBuffer;
+
+
+static VirtIOConsole *virtio_console;
+
+static VirtConPort *get_port_from_id(VirtIOConsole *vcon, uint32_t id)
+{
+    VirtConDevice *dev;
+    VirtConPort *port;
+
+    if (id > MAX_VIRTIO_CONSOLE_PORTS)
+        return NULL;
+
+    dev = vcon->ports[id];
+    port = DO_UPCAST(VirtConPort, dev, &dev->qdev);
+    return port;
+}
+
+static uint32_t get_id_from_port(VirtConPort *port)
+{
+    return port->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 has_complete_data(VirtConPort *port)
+{
+    VirtConPortBuffer *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 void flush_queue(VirtConPort *port)
+{
+    VirtConPortBuffer *buf, *buf2;
+    uint8_t *outbuf;
+    size_t outlen, outsize;
+
+    /*
+     * If the app isn't interested in buffering packets till it's
+     * opened, just drop the data guest sends us till a connection is
+     * established.
+     */
+    if (!port->host_connected && !port->flush_buffers)
+        return;
+
+    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
+             */
+            port->info->have_data(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;
+        }
+        if (port->host_connected) {
+            port->info->have_data(port, outbuf, outlen);
+        }
+        qemu_free(outbuf);
+    }
+}
+
+
+static size_t write_to_port(VirtConPort *port,
+                            const uint8_t *buf, size_t size, uint32_t flags)
+{
+    VirtQueue *vq = virtio_console->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? */
+            qemu_error("virtio-console: size %zd less than expected\n",
+                    elem.in_sg[0].iov_len);
+            exit(1);
+        }
+        header.id = 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(&virtio_console->vdev, vq);
+    return offset;
+}
+
+static void send_control_event(VirtConPort *port,
+                               struct virtio_console_control *cpkt)
+{
+    write_to_port(port, (uint8_t *)cpkt, sizeof(*cpkt),
+                  VIRTIO_CONSOLE_ID_INTERNAL);
+}
+
+
+
+/* Functions for use inside qemu to open and read from/write to ports */
+int virtio_console_open(VirtConPort *port)
+{
+    struct virtio_console_control cpkt;
+
+    /* Don't allow opening an already-open port */
+    if (port->host_connected) {
+        return 0;
+    }
+
+    /* Send port open notification to the guest */
+    port->host_connected = true;
+    cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+    cpkt.value = 1;
+    send_control_event(port, &cpkt);
+
+    /* Flush any buffers that were pending while the port was closed */
+    flush_queue(port);
+
+    return 0;
+}
+
+void virtio_console_close(VirtConPort *port)
+{
+    struct virtio_console_control cpkt;
+
+    port->host_connected = false;
+    cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+    cpkt.value = 0;
+    send_control_event(port, &cpkt);
+}
+
+/*
+ * Individual ports call this function to write to the guest.
+ */
+size_t virtio_console_write(VirtConPort *port, const uint8_t *buf, size_t size)
+{
+    if (!port || !port->host_connected) {
+        return 0;
+    }
+    return write_to_port(port, buf, size, false);
+}
+
+
+/* Guest wants to notify us of some event */
+static void handle_control_message(VirtConPort *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;
+        if (cpkt->value && port->info->guest_open) {
+            port->info->guest_open(port);
+        }
+        if (!cpkt->value && port->info->guest_close) {
+            port->info->guest_close(port);
+        }
+        break;
+    case VIRTIO_CONSOLE_PORT_NAME:
+        if (port->name) {
+            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);
+        }
+        /*
+         * Now that we know the guest asked for the port name, we're
+         * sure the guest has initialised whatever state is necessary
+         * for this port. Now's a good time to let the guest know if
+         * this port is a console port so that the guest can hook it
+         * up to hvc.
+         */
+        if (port->is_console) {
+            cpkt->event = VIRTIO_CONSOLE_CONSOLE_PORT;
+            cpkt->value = true;
+            send_control_event(port, cpkt);
+        }
+        break;
+    }
+}
+
+/* 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 *vcon;
+    VirtQueueElement elem;
+
+    vcon = DO_UPCAST(VirtIOConsole, vdev, vdev);
+
+    while (virtqueue_pop(vq, &elem)) {
+        VirtConPort *port;
+        VirtConPortBuffer *buf;
+        struct virtio_console_header header;
+        int header_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;
+        }
+        /*
+         * A port may not have any handler registered for consuming the
+         * data that the guest sends or it may not have a chardev associated
+         * with it. Just ignore the data in that case
+         */
+        if (!port->info->have_data) {
+            goto next_buf;
+        }
+
+        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;
+        }
+        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)
+{
+}
+
+static uint32_t virtio_console_get_features(VirtIODevice *vdev)
+{
+    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));
+}
+
+/* Readiness of the guest to accept data on a port */
+static int vcon_can_read(void *opaque)
+{
+    VirtConPort *port = opaque;
+    VirtQueue *vq = virtio_console->ivq;
+    int size, header_len;
+
+    if (use_multiport()) {
+        header_len = sizeof(struct virtio_console_header);
+    } else {
+        header_len = 0;
+    }
+
+    if (!virtio_queue_ready(vq) ||
+        !(virtio_console->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK) ||
+        virtio_queue_empty(vq)) {
+        return 0;
+    }
+    if (use_multiport() && !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)
+{
+    VirtConPort *port = opaque;
+
+    virtio_console_write(port, buf, size);
+}
+
+static void vcon_event(void *opaque, int event)
+{
+    VirtConPort *port = opaque;
+
+    switch (event) {
+    case CHR_EVENT_OPENED: {
+        virtio_console_open(port);
+        break;
+    }
+    case CHR_EVENT_CLOSED:
+        virtio_console_close(port);
+        break;
+    }
+}
+
+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 void virtio_console_save(QEMUFile *f, void *opaque)
+{
+    VirtIOConsole *s = opaque;
+    VirtConPort *port;
+    uint32_t guest_connected_map[MAX_VIRTIO_CONSOLE_PORTS / 32];
+    unsigned int i, nr_bufs;
+
+    /* 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);
+
+    /* Items in struct VirtIOConsole */
+    qemu_put_be32s(f, &s->guest_features);
+
+    /*
+     * Items in struct VirtConPort
+     *   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 < s->config.nr_active_ports / 32; i++) {
+        guest_connected_map[i] = 0;
+    }
+    for (i = 0; i < s->config.nr_active_ports; i++) {
+        port = get_port_from_id(s, i);
+        if (port->guest_connected) {
+            set_active_in_map(guest_connected_map, i);
+        }
+    }
+    for (i = 0; i < s->config.nr_active_ports / 32; i++) {
+        qemu_put_be32s(f, &guest_connected_map[i]);
+    }
+
+    /* All the pending buffers from active ports */
+    for (i = 0; i < s->config.nr_active_ports; i++) {
+        VirtConPortBuffer *buf;
+
+        nr_bufs = 0;
+        port = get_port_from_id(s, i);
+        QTAILQ_FOREACH(buf, &port->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, &port->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;
+    VirtConPort *port;
+    uint32_t guest_connected_map[MAX_VIRTIO_CONSOLE_PORTS / 32];
+    unsigned int i;
+
+    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 = qemu_get_be32(f);
+
+    /* Items in struct VirtIOConsole */
+    qemu_get_be32s(f, &virtio_console->guest_features);
+
+    /* Items in struct VirtConPort */
+    for (i = 0; i < s->config.nr_active_ports / 32; i++) {
+        guest_connected_map[i] = qemu_get_be32(f);
+    }
+
+    for (i = 0; i < s->config.nr_active_ports / 32; i++) {
+        uint32_t port_nr, map;
+
+        port = get_port_from_id(s, i);
+        map = guest_connected_map[i];
+        while (1) {
+            port_nr = find_next_active_in_map(&map, i);
+            if (port_nr == VIRTIO_CONSOLE_BAD_ID) {
+                break;
+            }
+            port->guest_connected = true;
+        }
+    }
+
+    /* All the pending buffers from active ports */
+    for (i = 0; i < s->config.nr_active_ports; i++) {
+        VirtConPortBuffer *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;
+        }
+        port = get_port_from_id(s, nr);
+        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(&port->unflushed_buffer_head, buf, next);
+        }
+    }
+
+    return 0;
+}
+
+/* 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 struct BusInfo virtcon_bus_info = {
+    .name      = "virtio-console-bus",
+    .size      = sizeof(VirtConBus),
+};
+
+static VirtConBus *virtcon_bus_new(DeviceState *dev)
+{
+    if (virtcon_bus) {
+        qemu_error("Can't create a second virtio-console bus\n");
+        return NULL;
+    }
+
+    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);
+    VirtConPortInfo *info = DO_UPCAST(VirtConPortInfo, qdev, base);
+    VirtConPort *port = DO_UPCAST(VirtConPort, dev, &dev->qdev);
+    int ret;
+
+    if (virtio_console->config.nr_active_ports == MAX_VIRTIO_CONSOLE_PORTS) {
+        qemu_error("virtio-console-bus: Maximum device limit reached\n");
+        return -1;
+    }
+
+    dev->info = info;
+
+    ret = info->init(dev);
+    if (ret) {
+        return ret;
+    }
+    QTAILQ_INIT(&port->unflushed_buffer_head);
+
+    if (port->chr) {
+        qemu_chr_add_handlers(port->chr, vcon_can_read, vcon_read, vcon_event,
+                              port);
+    }
+    port->id = virtio_console->config.nr_active_ports++;
+    virtio_console->ports[port->id] = dev;
+    /* Send an update to the guest about this new port added */
+    virtio_notify_config(&virtio_console->vdev);
+
+    return ret;
+}
+
+void virtcon_port_qdev_register(VirtConPortInfo *info)
+{
+    info->qdev.init = virtcon_port_qdev_init;
+    info->qdev.bus_info = &virtcon_bus_info;
+    qdev_register(&info->qdev);
+}
+
+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,
+                                            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);
+
+    register_savevm("virtio-console", -1, 2, virtio_console_save,
+                    virtio_console_load, s);
+
+    /* Spawn a new virtio-console bus on which the ports will ride as devices 
*/
+    virtcon_bus_new(dev);
+
+    return &s->vdev;
+}
diff --git a/hw/virtio-console.c b/hw/virtio-console.c
index 92c953c..8007279 100644
--- a/hw/virtio-console.c
+++ b/hw/virtio-console.c
@@ -1,10 +1,10 @@
 /*
  * Virtio Console Device
  *
- * Copyright IBM, Corp. 2008
+ * Copyright Red Hat, Inc. 2009
  *
  * Authors:
- *  Christian Ehrhardt <address@hidden>
+ *  Amit Shah <address@hidden>
  *
  * This work is licensed under the terms of the GNU GPL, version 2.  See
  * the COPYING file in the top-level directory.
@@ -16,131 +16,55 @@
 #include "virtio.h"
 #include "virtio-console.h"
 
-
 typedef struct VirtIOConsole
 {
-    VirtIODevice vdev;
-    VirtQueue *ivq, *ovq;
-    CharDriverState *chr;
+    VirtConPort port;
 } VirtIOConsole;
 
-static VirtIOConsole *to_virtio_console(VirtIODevice *vdev)
-{
-    return (VirtIOConsole *)vdev;
-}
-
-static void virtio_console_handle_output(VirtIODevice *vdev, VirtQueue *vq)
-{
-    VirtIOConsole *s = to_virtio_console(vdev);
-    VirtQueueElement elem;
-
-    while (virtqueue_pop(vq, &elem)) {
-        ssize_t len = 0;
-        int d;
-
-        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);
-        }
-        virtqueue_push(vq, &elem, len);
-        virtio_notify(vdev, vq);
-    }
-}
-
-static void virtio_console_handle_input(VirtIODevice *vdev, VirtQueue *vq)
-{
-}
-
-static uint32_t virtio_console_get_features(VirtIODevice *vdev)
-{
-    return 0;
-}
-
-static int vcon_can_read(void *opaque)
+static size_t flush_buf(VirtConPort *port, const uint8_t *buf, size_t len)
 {
-    VirtIOConsole *s = (VirtIOConsole *) opaque;
-
-    if (!virtio_queue_ready(s->ivq) ||
-        !(s->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK) ||
-        virtio_queue_empty(s->ivq))
-        return 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;
-    return 0;
-}
-
-static void vcon_read(void *opaque, const uint8_t *buf, int size)
-{
-    VirtIOConsole *s = (VirtIOConsole *) opaque;
-    VirtQueueElement elem;
-    int offset = 0;
-
-    /* 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);
+    return qemu_chr_write(port->chr, buf, len);
 }
 
-static void vcon_event(void *opaque, int event)
+static int vcon_port_initfn(VirtConDevice *dev)
 {
-    /* we will ignore any event for the time being */
-}
+    VirtConPort *port = DO_UPCAST(VirtConPort, dev, &dev->qdev);
 
-static void virtio_console_save(QEMUFile *f, void *opaque)
-{
-    VirtIOConsole *s = opaque;
+    port->info = dev->info;
 
-    virtio_save(&s->vdev, f);
-}
+    /*
+     * We're not interested in data the guest sends while nothing's
+     * connected on the host side. Just ignore it instead of saving it
+     * for later consumption
+     */
+    port->flush_buffers = 1;
 
-static int virtio_console_load(QEMUFile *f, void *opaque, int version_id)
-{
-    VirtIOConsole *s = opaque;
+    /* Tell the guest we're a console so it attaches us to an hvc console */
+    port->is_console = true;
 
-    if (version_id != 1)
-        return -EINVAL;
+    /*
+     * For console devices, a tty is spawned on /dev/hvc0 and our
+     * /dev/vconNN will never be opened. Set this here.
+     */
+    port->guest_connected = true;
 
-    virtio_load(&s->vdev, f);
     return 0;
 }
 
-VirtIODevice *virtio_console_init(DeviceState *dev)
+static VirtConPortInfo virtcon_console_info = {
+    .qdev.name     = "virtconsole",
+    .qdev.size     = sizeof(VirtConPort),
+    .init          = vcon_port_initfn,
+    .have_data     = flush_buf,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_CHR("chardev", VirtConPort, chr),
+        DEFINE_PROP_STRING("name", VirtConPort, name),
+        DEFINE_PROP_END_OF_LIST(),
+    },
+};
+
+static void virtcon_console_register(void)
 {
-    VirtIOConsole *s;
-    s = (VirtIOConsole *)virtio_common_init("virtio-console",
-                                            VIRTIO_ID_CONSOLE,
-                                            0, sizeof(VirtIOConsole));
-    if (s == NULL)
-        return NULL;
-
-    s->vdev.get_features = virtio_console_get_features;
-
-    s->ivq = virtio_add_queue(&s->vdev, 128, virtio_console_handle_input);
-    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, 1, virtio_console_save, 
virtio_console_load, s);
-
-    return &s->vdev;
+    virtcon_port_qdev_register(&virtcon_console_info);
 }
+device_init(virtcon_console_register)
diff --git a/hw/virtio-console.h b/hw/virtio-console.h
index 84d0717..b0df6c5 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 <address@hidden>
+ *  Amit Shah <address@hidden>
  *
  * 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,135 @@
 #ifndef _QEMU_VIRTIO_CONSOLE_H
 #define _QEMU_VIRTIO_CONSOLE_H
 
+#include <stdbool.h>
+#include "qdev.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
+#define VIRTIO_CONSOLE_CONSOLE_PORT    2
+
+/* In-qemu interface */
+
+/* Max. virtio console ports per device */
+#define MAX_VIRTIO_CONSOLE_PORTS       64
+
+typedef struct VirtConBus VirtConBus;
+typedef struct VirtConPort VirtConPort;
+typedef struct VirtConPortInfo VirtConPortInfo;
+
+typedef struct VirtConDevice {
+    DeviceState qdev;
+    VirtConPortInfo *info;
+} VirtConDevice;
+
+/*
+ * This is the state that's shared between all the ports.  Some of the
+ * state is configurable via command-line options. Some of it can be
+ * set by individual devices in their initfn routines. Some of the
+ * state is set by the generic qdev device init routine.
+ */
+struct VirtConPort {
+    DeviceState dev;
+    VirtConPortInfo *info;
+
+    /* State for the chardevice associated with this port */
+    CharDriverState *chr;
+
+    /*
+     * This name is sent to the guest and exported via sysfs.
+     * The guest could create symlinks based on this information.
+     * The name is in the reverse fqdn format, like org.qemu.console.0
+     */
+    char *name;
+
+    /*
+     * This list holds buffers pushed by the guest in case the guest
+     * sent incomplete messages or the host connection was down and
+     * the device requested to cache the data.
+     */
+    QTAILQ_HEAD(, VirtConPortBuffer) unflushed_buffer_head;
+
+    /*
+     * This id helps identify ports between the guest and the host.
+     * The guest sends a "header" with this id with each data packet
+     * that it sends and the host can then find out which associated
+     * device to send out this data to
+     */
+    uint32_t id;
+
+    /*
+     * This boolean, when set, means "don't queue data that gets sent
+     * to this port when the host is not connected".
+     */
+    int flush_buffers;
+
+    /* Identify if this is a port that binds with hvc in the guest */
+    bool is_console;
+
+    /* Is the corresponding guest device open? */
+    bool guest_connected;
+    /* Is this device open for IO on the host? */
+    bool host_connected;
+};
+
+
+typedef int (*virtcon_port_qdev_initfn)(VirtConDevice *dev);
+
+struct VirtConPortInfo {
+    DeviceInfo qdev;
+    virtcon_port_qdev_initfn init;
+
+    /* Callbacks for guest events */
+    void (*guest_open)(VirtConPort *port);
+    void (*guest_close)(VirtConPort *port);
+
+    size_t (*have_data)(VirtConPort *port, const uint8_t *buf, size_t len);
+};
+
+void virtcon_port_qdev_register(VirtConPortInfo *info);
+
+int virtio_console_open(VirtConPort *port);
+void virtio_console_close(VirtConPort *port);
+size_t virtio_console_write(VirtConPort *port, const uint8_t *buf, size_t 
size);
+
+
 #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
address@hidden -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..f5b287e 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -233,12 +233,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





reply via email to

[Prev in Thread] Current Thread [Next in Thread]