Hello,
This is the patch that I have for adding support for multiple ports to
virtio_console. It's pretty stable in my testing so far and the memory
corruption that I had earlier has been resolved in linux-next so I'm
proposing this for inclusion.
This currently uses device major number 60 from the experimental range;
Alan could you please reserve a new major number for virtio_console?
Rusty, can you give this a review once more?
Thanks,
Amit
Expose multiple char devices ("ports") for simple communication
between the host userspace and guest.
Sample offline usages can be: poking around in a guest to find out
the file systems used, applications installed, etc. Online usages
can be sharing of clipboard data between the guest and the host,
sending information about logged-in users to the host, locking the
screen or session when a vnc session is closed, and so on.
The interface presented to guest userspace is of a simple char
device, so it can be used like this:
fd = open("/dev/vcon2", O_RDWR);
ret = read(fd, buf, 100);
ret = write(fd, string, strlen(string));
Each port is to be assigned a unique function, for example, the
first 4 ports may be reserved for libvirt usage, the next 4 for
generic streaming data and so on. This port-function mapping
isn't finalised yet.
For requirements, use-cases and some history see
http://www.linux-kvm.org/page/VMchannel_Requirements
Signed-off-by: Amit Shah <[email protected]>
---
drivers/char/Kconfig | 4 +-
drivers/char/virtio_console.c | 1076 +++++++++++++++++++++++++++++++++++-----
include/linux/virtio_console.h | 60 +++-
3 files changed, 1001 insertions(+), 139 deletions(-)
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 6a06913..fe76627 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -678,7 +678,9 @@ config VIRTIO_CONSOLE
select HVC_DRIVER
help
Virtio console for use with lguest and other hypervisors.
-
+ Also serves as a general-purpose serial device for data transfer
+ between the guest and host. Character devices at /dev/vconNN will
+ be created when corresponding ports are found.
config HVCS
tristate "IBM Hypervisor Virtual Console Server support"
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 0d328b5..719d855 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -9,10 +9,8 @@
* functions.
:*/
-/*M:002 The console can be flooded: while the Guest is processing input the
- * Host can send more. Buffering in the Host could alleviate this, but it is a
- * difficult problem in general. :*/
/* Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
+ * Copyright (C) 2009, Amit Shah, Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,116 +26,424 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+
+#include <linux/cdev.h>
+#include <linux/device.h>
#include <linux/err.h>
+#include <linux/fs.h>
#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
#include <linux/virtio.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_console.h>
+#include <linux/workqueue.h>
#include "hvc_console.h"
-/*D:340 These represent our input and output console queues, and the virtio
- * operations for them. */
-static struct virtqueue *in_vq, *out_vq;
-static struct virtio_device *vdev;
+/* This struct stores data that's common to all the ports */
+struct virtio_console_struct {
+ /*
+ * Workqueue handlers where we process deferred work after an
+ * interrupt
+ */
+ struct work_struct rx_work;
+ struct work_struct tx_work;
+ struct work_struct config_work;
-/* This is our input buffer, and how much data is left in it. */
-static unsigned int in_len;
-static char *in, *inbuf;
+ struct list_head port_head;
+ struct list_head unused_read_head;
+ struct list_head unused_write_head;
-/* The operations for our console. */
-static struct hv_ops virtio_cons;
+ /* To protect the list of unused write buffers */
+ spinlock_t write_list_lock;
-/* The hvc device */
-static struct hvc_struct *hvc;
+ struct virtio_device *vdev;
+ struct class *class;
+ /* The input and the output queues */
+ struct virtqueue *in_vq, *out_vq;
-/*D:310 The put_chars() callback is pretty straightforward.
- *
- * We turn the characters into a scatter-gather list, add it to the output
- * queue and then kick the Host. Then we sit here waiting for it to finish:
- * inefficient in theory, but in practice implementations will do it
- * immediately (lguest's Launcher does). */
-static int put_chars(u32 vtermno, const char *buf, int count)
+ /* The current config space is stored here */
+ struct virtio_console_config *config;
+};
+
+/* This struct holds individual buffers received for each port */
+struct virtio_console_port_buffer {
+ struct list_head next;
+
+ char *buf;
+
+ size_t len; /* length of the buffer */
+ size_t offset; /* offset in the buf from which to consume data */
+};
+
+/* This struct holds the per-port data */
+struct virtio_console_port {
+ /* Next port in the list, head is in the virtio_console_struct */
+ struct list_head next;
+
+ /* Buffer management */
+ struct list_head readbuf_head;
+ /* A waitqueue for poll() or blocking read operations */
+ wait_queue_head_t waitqueue;
+
+ /* Each port associates with a separate char device */
+ struct cdev cdev;
+ struct device *dev;
+
+ /* The hvc device, if this port is associated with a console */
+ struct hvc_struct *hvc;
+
+ bool host_connected; /* Is the host device open */
+};
+
+static struct virtio_console_struct virtconsole;
+
+static int major = 60; /* from the experimental range */
+
+static struct virtio_console_port *get_port_from_id(u32 id)
{
- struct scatterlist sg[1];
- unsigned int len;
-
- /* This is a convenient routine to initialize a single-elem sg list */
- sg_init_one(sg, buf, count);
-
- /* add_buf wants a token to identify this buffer: we hand it any
- * non-NULL pointer, since there's only ever one buffer. */
- if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) >= 0) {
- /* Tell Host to go! */
- out_vq->vq_ops->kick(out_vq);
- /* Chill out until it's done with the buffer. */
- while (!out_vq->vq_ops->get_buf(out_vq, &len))
- cpu_relax();
+ struct virtio_console_port *port;
+
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (MINOR(port->dev->devt) == id)
+ return port;
}
+ return NULL;
+}
- /* We're expected to return the amount of data we wrote: all of it. */
- return count;
+static int get_id_from_port(struct virtio_console_port *port)
+{
+ return MINOR(port->dev->devt);
}
-/* Create a scatter-gather list representing our input buffer and put it in the
- * queue. */
-static void add_inbuf(void)
+static bool is_console_port(struct virtio_console_port *port)
{
- struct scatterlist sg[1];
- sg_init_one(sg, inbuf, PAGE_SIZE);
+ u32 port_nr = get_id_from_port(port);
- /* We should always be able to add one buffer to an empty queue. */
- if (in_vq->vq_ops->add_buf(in_vq, sg, 0, 1, inbuf) < 0)
- BUG();
- in_vq->vq_ops->kick(in_vq);
+ if (port_nr == VIRTIO_CONSOLE_CONSOLE_PORT ||
+ port_nr == VIRTIO_CONSOLE_CONSOLE2_PORT)
+ return true;
+ return false;
}
-/*D:350 get_chars() is the callback from the hvc_console infrastructure when
- * an interrupt is received.
- *
- * Most of the code deals with the fact that the hvc_console() infrastructure
- * only asks us for 16 bytes at a time. We keep in_offset and in_used fields
- * for partially-filled buffers. */
-static int get_chars(u32 vtermno, char *buf, int count)
+static inline bool use_multiport(void)
{
- /* If we don't have an input queue yet, we can't get input. */
- BUG_ON(!in_vq);
+ /*
+ * This condition can be true when put_chars is called from
+ * early_init
+ */
+ if (!virtconsole.vdev)
+ return 0;
+ return virtconsole.vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+}
+
+static inline bool is_internal(u32 flags)
+{
+ return flags & VIRTIO_CONSOLE_ID_INTERNAL;
+}
+
+/*
+ * Give out the data that's requested from the buffers that we have
+ * queued up per port
+ */
+static ssize_t fill_readbuf(struct virtio_console_port *port,
+ char *out_buf, size_t out_count, bool to_user)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+ ssize_t out_offset, ret;
- /* No buffer? Try to get one. */
- if (!in_len) {
- in = in_vq->vq_ops->get_buf(in_vq, &in_len);
- if (!in)
+ out_offset = 0;
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ size_t copy_size;
+
+ copy_size = out_count;
+ if (copy_size > buf->len - buf->offset)
+ copy_size = buf->len - buf->offset;
+
+ if (to_user) {
+ ret = copy_to_user(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ /* FIXME: Deal with ret != 0 */
+ } else {
+ memcpy(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ ret = 0; /* Emulate copy_to_user behaviour */
+ }
+
+ /* Return the number of bytes actually copied */
+ ret = copy_size - ret;
+ buf->offset += ret;
+ out_offset += ret;
+ out_count -= ret;
+
+ if (buf->len - buf->offset == 0) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ if (!out_count)
+ break;
+ }
+ return out_offset;
+}
+
+/* The condition that must be true for polling to end */
+static bool wait_is_over(struct virtio_console_port *port)
+{
+ return !list_empty(&port->readbuf_head) || !port->host_connected;
+}
+
+static ssize_t virtconsole_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+ ssize_t ret;
+
+ port = filp->private_data;
+
+ if (list_empty(&port->readbuf_head)) {
+ /*
+ * If nothing's connected on the host just return 0 in
+ * case of list_empty; this tells the userspace app
+ * that there's no connection
+ */
+ if (!port->host_connected)
return 0;
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(port->waitqueue,
+ wait_is_over(port));
+ if (ret < 0)
+ return ret;
+ }
+ /*
+ * We could've received a disconnection message while we were
+ * waiting for more data.
+ *
+ * This check is not clubbed in the if() statement above as we
+ * might receive some data as well as the host could get
+ * disconnected after we got woken up from our wait. So we
+ * really want to give off whatever data we have and only then
+ * check for host_connected
+ */
+ if (list_empty(&port->readbuf_head) && !port->host_connected)
+ return 0;
+
+ return fill_readbuf(port, ubuf, count, true);
+}
+
+static ssize_t send_buf(struct virtio_console_port *port,
+ const char *in_buf, size_t in_count,
+ u32 flags, bool from_user)
+{
+ struct virtqueue *out_vq;
+ struct virtio_console_port_buffer *buf, *buf2;
+ struct scatterlist sg[1];
+ struct virtio_console_header header;
+ size_t in_offset, copy_size;
+ ssize_t ret;
+ unsigned int header_len;
+
+ if (!in_count)
+ return 0;
+
+ out_vq = virtconsole.out_vq;
+ /*
+ * We should not send internal messages to a host that won't
+ * understand them
+ */
+ if (!use_multiport() && is_internal(flags))
+ return 0;
+ header_len = 0;
+ if (use_multiport()) {
+ header.id = get_id_from_port(port);
+ header.flags = flags;
+ header.size = in_count;
+ header_len = sizeof(header);
+ }
+ in_offset = 0; /* offset in the user buffer */
+ while (in_count - in_offset) {
+ copy_size = min(in_count - in_offset + header_len, PAGE_SIZE);
+
+ spin_lock(&virtconsole.write_list_lock);
+ list_for_each_entry_safe(buf, buf2,
+ &virtconsole.unused_write_head,
+ next) {
+ list_del(&buf->next);
+ break;
+ }
+ spin_unlock(&virtconsole.write_list_lock);
+ if (!buf)
+ break;
+ if (header_len) {
+ memcpy(buf->buf, &header, header_len);
+ copy_size -= header_len;
+ }
+ if (from_user)
+ ret = copy_from_user(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ else {
+ /*
+ * Since we're not sure when the host will actually
+ * consume the data and tell us about it, we have
+ * to copy the data here in case the caller
+ * frees the in_buf
+ */
+ memcpy(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ ret = 0; /* Emulate copy_from_user behaviour */
+ }
+ buf->len = header_len + copy_size - ret;
+ sg_init_one(sg, buf->buf, buf->len);
+
+ ret = out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, buf);
+ if (ret < 0) {
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&virtconsole.write_list_lock);
+ list_add_tail(&buf->next,
+ &virtconsole.unused_write_head);
+ spin_unlock(&virtconsole.write_list_lock);
+ break;
+ }
+ in_offset += buf->len - header_len;
+ /*
+ * Only send size with the first buffer. This way
+ * userspace can find out a continuous stream of data
+ * belonging to one write request and consume it
+ * appropriately
+ */
+ header.size = 0;
+
+ /* No space left in the vq anyway */
+ if (!ret)
+ break;
}
+ /* Tell Host to go! */
+ out_vq->vq_ops->kick(out_vq);
- /* You want more than we have to give? Well, try wanting less! */
- if (in_len < count)
- count = in_len;
+ /* We're expected to return the amount of data we wrote */
+ return in_offset;
+}
- /* Copy across to their buffer and increment offset. */
- memcpy(buf, in, count);
- in += count;
- in_len -= count;
+static ssize_t virtconsole_write(struct file *filp, const char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
- /* Finished? Re-register buffer so Host will use it again. */
- if (in_len == 0)
- add_inbuf();
+ port = filp->private_data;
- return count;
+ return send_buf(port, ubuf, count, 0, true);
}
-/*:*/
-/*D:320 Console drivers are initialized very early so boot messages can go out,
- * so we do things slightly differently from the generic virtio initialization
- * of the net and block drivers.
+static unsigned int virtconsole_poll(struct file *filp, poll_table *wait)
+{
+ struct virtio_console_port *port;
+ unsigned int ret;
+
+ port = filp->private_data;
+ poll_wait(filp, &port->waitqueue, wait);
+
+ ret = 0;
+ if (!list_empty(&port->readbuf_head))
+ ret |= POLLIN | POLLRDNORM;
+ if (!port->host_connected)
+ ret |= POLLHUP;
+
+ return ret;
+}
+
+static int virtconsole_release(struct inode *inode, struct file *filp)
+{
+ struct virtio_console_control cpkt;
+
+ /* Notify host of port being closed */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 0;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+ return 0;
+}
+
+static int virtconsole_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+
+ port = container_of(cdev, struct virtio_console_port, cdev);
+ filp->private_data = port;
+
+ /* Notify host of port being opened */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 1;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ return 0;
+}
+
+/*
+ * The file operations that we support: programs in the guest can open
+ * a console device, read from it, write to it, poll for data and
+ * close it. The devices are at /dev/vconNN
+ */
+static const struct file_operations virtconsole_fops = {
+ .owner = THIS_MODULE,
+ .open = virtconsole_open,
+ .read = virtconsole_read,
+ .write = virtconsole_write,
+ .poll = virtconsole_poll,
+ .release = virtconsole_release,
+};
+
+/*D:310
+ * The cons_put_chars() callback is pretty straightforward.
*
- * At this stage, the console is output-only. It's too early to set up a
- * virtqueue, so we let the drivers do some boutique early-output thing. */
-int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+ * We turn the characters into a scatter-gather list, add it to the output
+ * queue and then kick the Host.
+ *
+ * If the data to be outpu spans more than a page, it's split into
+ * page-sized buffers and then individual buffers are pushed to Host.
+ */
+static int cons_put_chars(u32 vtermno, const char *buf, int count)
{
- virtio_cons.put_chars = put_chars;
- return hvc_instantiate(0, 0, &virtio_cons);
+ struct virtio_console_port *port;
+
+ port = get_port_from_id(vtermno);
+ if (!port)
+ return 0;
+
+ return send_buf(port, buf, count, 0, false);
}
+/*D:350
+ * cons_get_chars() is the callback from the hvc_console
+ * infrastructure when an interrupt is received.
+ *
+ * We call out to fill_readbuf that gets us the required data from the
+ * buffers that are queued up.
+ */
+static int cons_get_chars(u32 vtermno, char *buf, int count)
+{
+ struct virtio_console_port *port;
+
+ /* If we don't have an input queue yet, we can't get input. */
+ BUG_ON(!virtconsole.in_vq);
+
+ port = get_port_from_id(vtermno);
+ if (!port)
+ return 0;
+
+ if (list_empty(&port->readbuf_head))
+ return 0;
+
+ return fill_readbuf(port, buf, count, false);
+}
+/*:*/
+
/*
* virtio console configuration. This supports:
* - console resize
@@ -153,98 +459,575 @@ static void virtcons_apply_config(struct virtio_device *dev)
dev->config->get(dev,
offsetof(struct virtio_console_config, rows),
&ws.ws_row, sizeof(u16));
- hvc_resize(hvc, ws);
+ /*
+ * We'll use this way of resizing only for legacy
+ * support. For newer userspace (VIRTIO_CONSOLE_F_MULTPORT+),
+ * use internal messages to indicate console size
+ * changes so that it can be done per-port
+ */
+ hvc_resize(get_port_from_id(VIRTIO_CONSOLE_CONSOLE_PORT)->hvc, ws);
}
}
/*
- * we support only one console, the hvc struct is a global var
* We set the configuration at this point, since we now have a tty
*/
-static int notifier_add_vio(struct hvc_struct *hp, int data)
+static int cons_notifier_add_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 1;
- virtcons_apply_config(vdev);
+ virtcons_apply_config(virtconsole.vdev);
return 0;
}
-static void notifier_del_vio(struct hvc_struct *hp, int data)
+static void cons_notifier_del_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 0;
}
-static void hvc_handle_input(struct virtqueue *vq)
+/* The operations for our console. */
+static struct hv_ops virtio_cons = {
+ .get_chars = cons_get_chars,
+ .put_chars = cons_put_chars,
+ .notifier_add = cons_notifier_add_vio,
+ .notifier_del = cons_notifier_del_vio,
+ .notifier_hangup = cons_notifier_del_vio,
+};
+
+/*D:320
+ * Console drivers are initialized very early so boot messages can go out,
+ * so we do things slightly differently from the generic virtio initialization
+ * of the net and block drivers.
+ *
+ * At this stage, the console is output-only. It's too early to set up a
+ * virtqueue, so we let the drivers do some boutique early-output thing.
+ */
+int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+{
+ virtio_cons.put_chars = put_chars;
+ return hvc_instantiate(0, 0, &virtio_cons);
+}
+
+
+/* Any secret messages that the Host and Guest want to share */
+static void handle_control_message(struct virtio_console_port *port,
+ struct virtio_console_control *cpkt)
+{
+ switch (cpkt->event) {
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->host_connected = cpkt->value;
+ break;
+ }
+}
+
+
+static struct virtio_console_port_buffer *get_buf(size_t buf_size)
+{
+ struct virtio_console_port_buffer *buf;
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ goto out;
+ buf->buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!buf->buf) {
+ kfree(buf);
+ goto out;
+ }
+ buf->len = buf_size;
+out:
+ return buf;
+}
+
+static void fill_queue(struct virtqueue *vq, size_t buf_size,
+ struct list_head *unused_head)
+{
+ struct scatterlist sg[1];
+ struct virtio_console_port_buffer *buf;
+ int ret;
+
+ do {
+ buf = get_buf(buf_size);
+ if (!buf)
+ break;
+ sg_init_one(sg, buf->buf, buf_size);
+
+ ret = vq->vq_ops->add_buf(vq, sg, 0, 1, buf);
+ if (ret < 0) {
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ /* We have to keep track of the unused buffers
+ * so that they can be freed when the module
+ * is being removed
+ */
+ list_add_tail(&buf->next, unused_head);
+ } while (ret > 0);
+ vq->vq_ops->kick(vq);
+}
+
+static void fill_receive_queue(void)
{
- if (hvc_poll(hvc))
+ fill_queue(virtconsole.in_vq, PAGE_SIZE, &virtconsole.unused_read_head);
+}
+
+/*
+ * This function is only called from the init routine so the spinlock
+ * for the unused_write_head list isn't taken
+ */
+static void alloc_write_bufs(void)
+{
+ struct virtio_console_port_buffer *buf;
+ int i;
+
+ for (i = 0; i < 1024; i++) {
+ buf = get_buf(PAGE_SIZE);
+ if (!buf)
+ break;
+ list_add_tail(&buf->next, &virtconsole.unused_write_head);
+ }
+}
+
+/*
+ * The workhandle for any buffers that appear on our input queue.
+ * Pick the buffer; if it's some communication meant for the Guest,
+ * just process it. Otherwise queue it up for the read() or
+ * get_chars() routines to pick the data up later.
+ */
+static void virtio_console_rx_work_handler(struct work_struct *work)
+{
+ struct virtio_console_port *port;
+ struct virtio_console_port_buffer *buf;
+ struct virtio_console_header header;
+ struct virtqueue *vq;
+ unsigned int tmplen, header_len;
+
+ header_len = use_multiport() ? sizeof(header) : 0;
+
+ port = NULL;
+ vq = virtconsole.in_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* The buffer is no longer unused */
+ list_del(&buf->next);
+
+ if (use_multiport()) {
+ memcpy(&header, buf->buf, header_len);
+ port = get_port_from_id(header.id);
+ } else
+ port = get_port_from_id(VIRTIO_CONSOLE_CONSOLE_PORT);
+ if (!port) {
+ /* No valid header at start of buffer. Drop it. */
+ pr_debug("%s: invalid index in buffer, %c %d\n",
+ __func__, buf->buf[0], buf->buf[0]);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free / alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ if (use_multiport() && is_internal(header.flags)) {
+ handle_control_message(port,
+ (struct virtio_console_control *)
+ (buf->buf + header_len));
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free/alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ } else {
+ buf->len = tmplen;
+ buf->offset = header_len;
+ list_add_tail(&buf->next, &port->readbuf_head);
+ /* We might have missed a connection
+ * notification, e.g. before the queues were
+ * initialised.
+ */
+ port->host_connected = true;
+ }
+ wake_up_interruptible(&port->waitqueue);
+ }
+ if (port && is_console_port(port) && hvc_poll(port->hvc))
hvc_kick();
+
+ /* Allocate buffers for all the ones that got used up */
+ fill_receive_queue();
}
-/*D:370 Once we're further in boot, we get probed like any other virtio device.
- * At this stage we set up the output virtqueue.
+/*
+ * This is the workhandler for buffers that get received on the output
+ * virtqueue, which is an indication that Host consumed the data we
+ * sent it. Since all our buffers going out are of a fixed size we can
+ * just reuse them instead of freeing them and allocating new ones.
+ *
+ * Zero out the buffer so that we don't leak any information from
+ * other processes. There's a small optimisation here as well: the
+ * buffers are PAGE_SIZE-sized; but instead of zeroing the entire
+ * page, we just zero the length that was most recently used and we
+ * can be sure the rest of the page is already set to 0s.
+ *
+ * So once we zero them out we add them back to the unused buffers
+ * list
+ */
+
+static void virtio_console_tx_work_handler(struct work_struct *work)
+{
+ struct virtqueue *vq;
+ struct virtio_console_port_buffer *buf;
+ unsigned int tmplen;
+
+ vq = virtconsole.out_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* 0 the buffer to not leak data from other processes */
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&virtconsole.write_list_lock);
+ list_add_tail(&buf->next, &virtconsole.unused_write_head);
+ spin_unlock(&virtconsole.write_list_lock);
+ }
+}
+
+static void rx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.rx_work);
+}
+
+static void tx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.tx_work);
+}
+
+static void config_intr(struct virtio_device *vdev)
+{
+ /* Handle port hot-add */
+ schedule_work(&virtconsole.config_work);
+
+ /* Handle console size changes */
+ virtcons_apply_config(vdev);
+}
+
+/*
+ * Compare the current config and the new config that we just got and
+ * find out where a particular port was added.
+ */
+static u32 virtconsole_get_hot_add_port(struct virtio_console_config *config)
+{
+ u32 i;
+ u32 port_nr;
+
+ for (i = 0; i < virtconsole.config->max_nr_ports / 32; i++) {
+ port_nr = ffs(config->ports_map[i] ^ virtconsole.config->ports_map[i]);
+ if (port_nr)
+ break;
+ }
+ if (unlikely(!port_nr))
+ return VIRTIO_CONSOLE_BAD_ID;
+
+ /* We used ffs above */
+ port_nr--;
+
+ /* FIXME: Do this only when add_port is successful */
+ virtconsole.config->ports_map[i] |= 1U << port_nr;
+
+ port_nr += i * 32;
+ return port_nr;
+}
+
+/*
+ * Cycle throught the list of active ports and return the next port
+ * that has to be activated.
+ */
+static u32 virtconsole_find_next_port(u32 *map, int *map_i)
+{
+ u32 port_nr;
+
+ while (1) {
+ port_nr = ffs(*map);
+ if (port_nr)
+ break;
+
+ if (unlikely(*map_i >= virtconsole.config->max_nr_ports / 32))
+ return VIRTIO_CONSOLE_BAD_ID;
+ ++*map_i;
+ *map = virtconsole.config->ports_map[*map_i];
+ }
+ /* We used ffs above */
+ port_nr--;
+
+ /* FIXME: Do this only when add_port is successful / reset bit
+ * in config space if add_port was unsuccessful
+ */
+ *map &= ~(1U << port_nr);
+
+ port_nr += *map_i * 32;
+ return port_nr;
+}
+
+static int virtconsole_add_port(u32 port_nr)
+{
+ struct virtio_console_port *port;
+ dev_t devt;
+ int ret;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ devt = MKDEV(major, port_nr);
+ cdev_init(&port->cdev, &virtconsole_fops);
+
+ ret = register_chrdev_region(devt, 1, "virtio-console");
+ if (ret < 0) {
+ pr_err("%s: error registering chrdev region, ret = %d\n",
+ __func__, ret);
+ goto free_cdev;
+ }
+ ret = cdev_add(&port->cdev, devt, 1);
+ if (ret < 0) {
+ pr_err("%s: error adding cdev, ret = %d\n", __func__, ret);
+ goto free_cdev;
+ }
+ port->dev = device_create(virtconsole.class, NULL, devt, NULL,
+ "vcon%u", port_nr);
+ if (IS_ERR(port->dev)) {
+ ret = PTR_ERR(port->dev);
+ pr_err("%s: Error creating device, ret = %d\n", __func__, ret);
+ goto free_cdev;
+ }
+ INIT_LIST_HEAD(&port->readbuf_head);
+ init_waitqueue_head(&port->waitqueue);
+
+ list_add_tail(&port->next, &virtconsole.port_head);
+
+ if (is_console_port(port)) {
+ /*
+ * To set up and manage our virtual console, we call
+ * hvc_alloc().
+ *
+ * The first argument of hvc_alloc() is the virtual
+ * console number, so we use zero. The second
+ * argument is the parameter for the notification
+ * mechanism (like irq number). We currently leave
+ * this as zero, virtqueues have implicit
+ * notifications.
+ *
+ * The third argument is a "struct hv_ops" containing
+ * the put_chars() get_chars(), notifier_add() and
+ * notifier_del() pointers. The final argument is the
+ * output buffer size: we can do any size, so we put
+ * PAGE_SIZE here.
+ */
+ port->hvc = hvc_alloc(port_nr, 0, &virtio_cons, PAGE_SIZE);
+ if (IS_ERR(port->hvc)) {
+ ret = PTR_ERR(port->hvc);
+ goto free_cdev;
+ }
+ }
+ pr_info("virtio-console port found at id %u\n", port_nr);
+
+ return 0;
+free_cdev:
+ unregister_chrdev(major, "virtio-console");
+ return ret;
+}
+
+/* max_ports is always a multiple of 32; enforced in the Host */
+static u32 get_ports_map_size(u32 max_ports)
+{
+ return sizeof(u32) * (max_ports / 32);
+}
+
+/* The workhandler for config-space updates
*
- * To set up and manage our virtual console, we call hvc_alloc(). Since we
- * never remove the console device we never need this pointer again.
+ * This is used when new ports are added
+ */
+static void virtio_console_config_work_handler(struct work_struct *work)
+{
+ struct virtio_console_config *virtconconf;
+ struct virtio_device *vdev = virtconsole.vdev;
+ u32 i, port_nr;
+ int ret;
+
+ virtconconf = kzalloc(sizeof(*virtconconf) +
+ get_ports_map_size(virtconsole.config->max_nr_ports),
+ GFP_KERNEL);
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config, nr_active_ports),
+ &virtconconf->nr_active_ports,
+ sizeof(virtconconf->nr_active_ports));
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config, ports_map),
+ virtconconf->ports_map,
+ get_ports_map_size(virtconsole.config->max_nr_ports));
+
+ /* Hot-add ports */
+ for (i = virtconsole.config->nr_active_ports;
+ i < virtconconf->nr_active_ports; i++) {
+ port_nr = virtconsole_get_hot_add_port(virtconconf);
+ if (port_nr == VIRTIO_CONSOLE_BAD_ID)
+ continue;
+ ret = virtconsole_add_port(port_nr);
+ if (!ret)
+ virtconsole.config->nr_active_ports++;
+ }
+ kfree(virtconconf);
+}
+
+/*D:370
+ * Once we're further in boot, we get probed like any other virtio device.
+ * At this stage we set up the output virtqueue.
*
- * Finally we put our input buffer in the input queue, ready to receive. */
-static int __devinit virtcons_probe(struct virtio_device *dev)
+ * Finally we put our input buffer in the input queue, ready to receive.
+ */
+static int __devinit virtcons_probe(struct virtio_device *vdev)
{
- vq_callback_t *callbacks[] = { hvc_handle_input, NULL};
+ vq_callback_t *callbacks[] = { rx_intr, tx_intr };
const char *names[] = { "input", "output" };
struct virtqueue *vqs[2];
- int err;
+ u32 i, map;
+ int ret, map_i;
+ u32 max_nr_ports;
+ bool multiport;
- vdev = dev;
+ virtconsole.vdev = vdev;
- /* This is the scratch page we use to receive console input */
- inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!inbuf) {
- err = -ENOMEM;
- goto fail;
- }
+ multiport = false;
+ if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT)) {
+ multiport = true;
+ vdev->features[0] |= 1 << VIRTIO_CONSOLE_F_MULTIPORT;
+ vdev->config->finalize_features(vdev);
+
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config,
+ max_nr_ports),
+ &max_nr_ports,
+ sizeof(max_nr_ports));
+ /*
+ * We have a variable-sized config space that's dependent
+ * on the maximum number of ports a guest can have.
+ * So we first get the max number of ports we can have
+ * and then allocate the config space
+ */
+ virtconsole.config = kzalloc(sizeof(struct virtio_console_config)
+ + get_ports_map_size(max_nr_ports),
+ GFP_KERNEL);
+ if (!virtconsole.config)
+ return -ENOMEM;
+
+ virtconsole.config->max_nr_ports = max_nr_ports;
+ vdev->config->get(vdev, offsetof(struct virtio_console_config,
+ nr_active_ports),
+ &virtconsole.config->nr_active_ports,
+ sizeof(virtconsole.config->nr_active_ports));
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config,
+ ports_map),
+ virtconsole.config->ports_map,
+ get_ports_map_size(max_nr_ports));
+ }
/* Find the queues. */
/* FIXME: This is why we want to wean off hvc: we do nothing
* when input comes in. */
- err = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
- if (err)
- goto free;
+ ret = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
+ if (ret)
+ goto fail;
- in_vq = vqs[0];
- out_vq = vqs[1];
+ virtconsole.in_vq = vqs[0];
+ virtconsole.out_vq = vqs[1];
- /* Start using the new console output. */
- virtio_cons.get_chars = get_chars;
- virtio_cons.put_chars = put_chars;
- virtio_cons.notifier_add = notifier_add_vio;
- virtio_cons.notifier_del = notifier_del_vio;
- virtio_cons.notifier_hangup = notifier_del_vio;
-
- /* The first argument of hvc_alloc() is the virtual console number, so
- * we use zero. The second argument is the parameter for the
- * notification mechanism (like irq number). We currently leave this
- * as zero, virtqueues have implicit notifications.
- *
- * The third argument is a "struct hv_ops" containing the put_chars()
- * get_chars(), notifier_add() and notifier_del() pointers.
- * The final argument is the output buffer size: we can do any size,
- * so we put PAGE_SIZE here. */
- hvc = hvc_alloc(0, 0, &virtio_cons, PAGE_SIZE);
- if (IS_ERR(hvc)) {
- err = PTR_ERR(hvc);
- goto free_vqs;
- }
+ INIT_LIST_HEAD(&virtconsole.port_head);
+ INIT_LIST_HEAD(&virtconsole.unused_read_head);
+ INIT_LIST_HEAD(&virtconsole.unused_write_head);
+
+ if (multiport) {
+ map_i = 0;
+ map = virtconsole.config->ports_map[map_i];
+ for (i = 0; i < virtconsole.config->nr_active_ports; i++) {
+ u32 port_nr;
+
+ port_nr = virtconsole_find_next_port(&map, &map_i);
+ if (unlikely(port_nr == VIRTIO_CONSOLE_BAD_ID))
+ continue;
+ virtconsole_add_port(port_nr);
+ }
+ } else
+ virtconsole_add_port(VIRTIO_CONSOLE_CONSOLE_PORT);
+
+ INIT_WORK(&virtconsole.rx_work, &virtio_console_rx_work_handler);
+ INIT_WORK(&virtconsole.tx_work, &virtio_console_tx_work_handler);
+ INIT_WORK(&virtconsole.config_work, &virtio_console_config_work_handler);
+ spin_lock_init(&virtconsole.write_list_lock);
- /* Register the input buffer the first time. */
- add_inbuf();
+ fill_receive_queue();
+ alloc_write_bufs();
return 0;
-free_vqs:
- vdev->config->del_vqs(vdev);
-free:
- kfree(inbuf);
fail:
- return err;
+ return ret;
+}
+
+static void virtcons_remove_port_data(struct virtio_console_port *port)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+
+#if 0 /* hvc_console is compiled in, at least on Fedora. */
+ if (is_console_port(port))
+ hvc_remove(hvc);
+#endif
+ device_destroy(virtconsole.class, port->dev->devt);
+ unregister_chrdev_region(port->dev->devt, 1);
+ cdev_del(&port->cdev);
+
+ /* Remove the buffers in which we have unconsumed data */
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+}
+
+static void virtcons_remove(struct virtio_device *vdev)
+{
+ struct virtio_console_port *port, *port2;
+ struct virtio_console_port_buffer *buf, *buf2;
+ char *tmpbuf;
+ int len;
+
+ unregister_chrdev(major, "virtio-console");
+ class_destroy(virtconsole.class);
+
+ cancel_work_sync(&virtconsole.rx_work);
+ /*
+ * Free up the buffers that we queued up for the Host to pass
+ * us data
+ */
+ while ((tmpbuf = virtconsole.in_vq->vq_ops->get_buf(virtconsole.in_vq,
+ &len)))
+ kfree(tmpbuf);
+
+ vdev->config->del_vqs(vdev);
+ /*
+ * Free up the buffers that were sent to us by Host but were
+ * left unused
+ */
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_read_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_write_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(port, port2, &virtconsole.port_head, next) {
+ list_del(&port->next);
+ virtcons_remove_port_data(port);
+ kfree(port);
+ }
+ kfree(virtconsole.config);
}
static struct virtio_device_id id_table[] = {
@@ -254,6 +1037,7 @@ static struct virtio_device_id id_table[] = {
static unsigned int features[] = {
VIRTIO_CONSOLE_F_SIZE,
+ VIRTIO_CONSOLE_F_MULTIPORT,
};
static struct virtio_driver virtio_console = {
@@ -263,14 +1047,34 @@ static struct virtio_driver virtio_console = {
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtcons_probe,
- .config_changed = virtcons_apply_config,
+ .remove = virtcons_remove,
+ .config_changed = config_intr,
};
static int __init init(void)
{
- return register_virtio_driver(&virtio_console);
+ int ret;
+
+ virtconsole.class = class_create(THIS_MODULE, "virtio-console");
+ if (IS_ERR(virtconsole.class)) {
+ pr_err("Error creating virtio-console class\n");
+ ret = PTR_ERR(virtconsole.class);
+ return ret;
+ }
+ ret = register_virtio_driver(&virtio_console);
+ if (ret) {
+ class_destroy(virtconsole.class);
+ return ret;
+ }
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ unregister_virtio_driver(&virtio_console);
}
module_init(init);
+module_exit(fini);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio console driver");
diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h
index b5f5198..7f2444c 100644
--- a/include/linux/virtio_console.h
+++ b/include/linux/virtio_console.h
@@ -2,20 +2,76 @@
#define _LINUX_VIRTIO_CONSOLE_H
#include <linux/types.h>
#include <linux/virtio_config.h>
-/* This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
- * anyone can use the definitions to implement compatible drivers/servers. */
+/*
+ * This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
+ * anyone can use the definitions to implement compatible drivers/servers.
+ *
+ * Copyright (C) Red Hat, Inc., 2009
+ */
/* Feature bits */
#define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */
+#define VIRTIO_CONSOLE_F_MULTIPORT 1 /* Does host provide multiple ports? */
+
+#define VIRTIO_CONSOLE_BAD_ID (~(u32)0) /* Invalid port number */
+
+/* Port at which the virtio console is spawned */
+#define VIRTIO_CONSOLE_CONSOLE_PORT 0
+#define VIRTIO_CONSOLE_CONSOLE2_PORT 1
struct virtio_console_config {
/* colums of the screens */
__u16 cols;
/* rows of the screens */
__u16 rows;
+ /*
+ * max. number of ports supported for each PCI device. Always
+ * a multiple of 32
+ */
+ __u32 max_nr_ports;
+ /* number of ports in use */
+ __u32 nr_active_ports;
+ /*
+ * locations of the ports in use; variable-size array: should
+ * be the last in this struct.
+ */
+ __u32 ports_map[0 /* max_nr_ports / 32 */];
} __attribute__((packed));
+/*
+ * An internal-only message that's passed between the Host and the
+ * Guest for a particular port.
+ */
+struct virtio_console_control {
+ __u16 event;
+ __u16 value;
+};
+
+/* Some events for internal messages (control packets) */
+#define VIRTIO_CONSOLE_PORT_OPEN 0
+
+
+/*
+ * This struct is put in each buffer that gets passed to userspace and
+ * vice-versa
+ */
+struct virtio_console_header {
+ /* Port number */
+ u32 id;
+ /* Some message between host and guest */
+ u32 flags;
+ /*
+ * Complete size of the write request - only sent with the
+ * first buffer for each write request
+ */
+ u32 size;
+} __attribute__((packed));
+
+/* Messages between host and guest ('flags' field in the header above) */
+#define VIRTIO_CONSOLE_ID_INTERNAL (1 << 0)
+
+
#ifdef __KERNEL__
int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int));
#endif /* __KERNEL__ */
--
1.6.2.5
> The interface presented to guest userspace is of a simple char
> device, so it can be used like this:
>
> fd = open("/dev/vcon2", O_RDWR);
> ret = read(fd, buf, 100);
> ret = write(fd, string, strlen(string));
>
> Each port is to be assigned a unique function, for example, the
> first 4 ports may be reserved for libvirt usage, the next 4 for
> generic streaming data and so on. This port-function mapping
> isn't finalised yet.
Unless I am missing something this looks completely bonkers
Every time we have a table of numbers for functionality it ends in
tears. We have to keep tables up to date and managed, we have to
administer the magical number to name space.
Anyway - you don't seem to need a fixed number you can use dynamic
allocation and udev.
There are at least two better ways to do this
- Using sysfs nodes so you have a proper heirarchy of names/functions
- Using a simple file system which provides a heirarchy of nodes whose
enumeration and access is backed by calls to whatever happyvisor you
are using.
it then self enumerates, self populates, doesn't need anyone to keep
updating magic tables of guest code and expands cleanly - yes ?
Alan
On (Fri) Sep 11 2009 [17:00:10], Alan Cox wrote:
> > The interface presented to guest userspace is of a simple char
> > device, so it can be used like this:
> >
> > fd = open("/dev/vcon2", O_RDWR);
> > ret = read(fd, buf, 100);
> > ret = write(fd, string, strlen(string));
> >
> > Each port is to be assigned a unique function, for example, the
> > first 4 ports may be reserved for libvirt usage, the next 4 for
> > generic streaming data and so on. This port-function mapping
> > isn't finalised yet.
>
> Unless I am missing something this looks completely bonkers
>
> Every time we have a table of numbers for functionality it ends in
> tears. We have to keep tables up to date and managed, we have to
> administer the magical number to name space.
Right; there was some discussion about this. A few alternatives were
suggested like
- udev scripts to create symlinks from ports to function, like:
/dev/vcon3 -> /dev/virtio-console/clipboard
- Some fqdn-like hierarchy, like
/dev/virtio-console/com/redhat/clipboard
which again can be created by udev scripts
> Anyway - you don't seem to need a fixed number you can use dynamic
> allocation and udev.
>
> There are at least two better ways to do this
>
> - Using sysfs nodes so you have a proper heirarchy of names/functions
> - Using a simple file system which provides a heirarchy of nodes whose
> enumeration and access is backed by calls to whatever happyvisor you
> are using.
>
> it then self enumerates, self populates, doesn't need anyone to keep
> updating magic tables of guest code and expands cleanly - yes ?
Agreed. I'd prefer udev scripts doing it vs doing it in the code as it
keeps everything simple and the policy isn't laid out in the kernel
module. Is that fine?
Amit
Amit Shah wrote:
> Right; there was some discussion about this. A few alternatives were
> suggested like
>
> - udev scripts to create symlinks from ports to function, like:
>
> /dev/vcon3 -> /dev/virtio-console/clipboard
>
> - Some fqdn-like hierarchy, like
>
> /dev/virtio-console/com/redhat/clipboard
>
> which again can be created by udev scripts
>
And I dislike all of them. What I'd rather have is these devices
exposed as tty's with a sys attribute that exposed the name of the device.
This is not all that different to how usb serial devices work.
Regards,
Anthony Liguori
On (Fri) Sep 11 2009 [12:26:16], Anthony Liguori wrote:
> Amit Shah wrote:
>> Right; there was some discussion about this. A few alternatives were
>> suggested like
>>
>> - udev scripts to create symlinks from ports to function, like:
>>
>> /dev/vcon3 -> /dev/virtio-console/clipboard
>>
>> - Some fqdn-like hierarchy, like
>>
>> /dev/virtio-console/com/redhat/clipboard
>>
>> which again can be created by udev scripts
>>
>
> And I dislike all of them. What I'd rather have is these devices
> exposed as tty's with a sys attribute that exposed the name of the
> device.
A sysfs attribute can even be exposed with a char device.
I didn't want to venture more into tty after the hvc thing and the
unexpected bugs that crept up (memory corruption, which is now fixed in
linux-next). I'd rather just keep it limited to the subsystems I know.
Amit
Amit Shah wrote:
> On (Fri) Sep 11 2009 [12:26:16], Anthony Liguori wrote:
>
>> Amit Shah wrote:
>>
>>> Right; there was some discussion about this. A few alternatives were
>>> suggested like
>>>
>>> - udev scripts to create symlinks from ports to function, like:
>>>
>>> /dev/vcon3 -> /dev/virtio-console/clipboard
>>>
>>> - Some fqdn-like hierarchy, like
>>>
>>> /dev/virtio-console/com/redhat/clipboard
>>>
>>> which again can be created by udev scripts
>>>
>>>
>> And I dislike all of them. What I'd rather have is these devices
>> exposed as tty's with a sys attribute that exposed the name of the
>> device.
>>
>
> A sysfs attribute can even be exposed with a char device.
>
> I didn't want to venture more into tty after the hvc thing and the
> unexpected bugs that crept up (memory corruption, which is now fixed in
> linux-next). I'd rather just keep it limited to the subsystems I know.
>
I don't think that's a good justification.
This device is very much a serial port. I don't see any reason not to
treat it like one.
Regards,
Anthony Liguori
[Adding Greg to the CC list]
On (Fri) Sep 11 2009 [17:00:10], Alan Cox wrote:
> > The interface presented to guest userspace is of a simple char
> > device, so it can be used like this:
> >
> > fd = open("/dev/vcon2", O_RDWR);
> > ret = read(fd, buf, 100);
> > ret = write(fd, string, strlen(string));
> >
> > Each port is to be assigned a unique function, for example, the
> > first 4 ports may be reserved for libvirt usage, the next 4 for
> > generic streaming data and so on. This port-function mapping
> > isn't finalised yet.
>
> Unless I am missing something this looks completely bonkers
>
> Every time we have a table of numbers for functionality it ends in
> tears. We have to keep tables up to date and managed, we have to
> administer the magical number to name space.
>
> Anyway - you don't seem to need a fixed number you can use dynamic
> allocation and udev.
>
> There are at least two better ways to do this
>
> - Using sysfs nodes so you have a proper heirarchy of names/functions
> - Using a simple file system which provides a heirarchy of nodes whose
> enumeration and access is backed by calls to whatever happyvisor you
> are using.
>
> it then self enumerates, self populates, doesn't need anyone to keep
> updating magic tables of guest code and expands cleanly - yes ?
Hey Greg,
Can you tell me how this could work out -- each console port could have
a "role" string associated with it (obtainable from the invoking qemu
process in case of qemu/kvm). Something that I have in mind currently
is:
$ qemu-kvm ... -virtioconsole role=org/qemu/clipboard
and then the guest kernel sees the string, and puts the
"org/qemu/clipboard" in some file in sysfs. Guest userspace should then
be able to open and read/write to
/dev/virtio_console/org/qemu/clipboard
I guess that's an acceptable scheme to all.
I also don't know how this would work -- which sysfs attributes to
export and how would udev pick that up and create the device at the
specified location.
Any pointers appreciated.
Thanks,
Amit
Amit Shah wrote:
> Hey Greg,
>
> Can you tell me how this could work out -- each console port could have
> a "role" string associated with it (obtainable from the invoking qemu
> process in case of qemu/kvm). Something that I have in mind currently
> is:
>
> $ qemu-kvm ... -virtioconsole role=org/qemu/clipboard
>
> and then the guest kernel sees the string, and puts the
> "org/qemu/clipboard" in some file in sysfs. Guest userspace should then
> be able to open and read/write to
>
> /dev/virtio_console/org/qemu/clipboard
>
That's probably not what we want. I imagine what we want is:
/dev/ttyV0
/dev/ttyV1
/dev/ttyVN
And then we want:
/sys/class/virtio-console/ttyV0/name -> "org.qemu.clipboard"
Userspace can detect when new virtio-consoles appear via udev events.
When it sees a new ttyVN, it can then look in sysfs to discover it's name.
Regards,
Anthony Liguori
On (Tue) Sep 15 2009 [07:57:10], Anthony Liguori wrote:
> Amit Shah wrote:
>> Hey Greg,
>>
>> Can you tell me how this could work out -- each console port could have
>> a "role" string associated with it (obtainable from the invoking qemu
>> process in case of qemu/kvm). Something that I have in mind currently
>> is:
>>
>> $ qemu-kvm ... -virtioconsole role=org/qemu/clipboard
>>
>> and then the guest kernel sees the string, and puts the
>> "org/qemu/clipboard" in some file in sysfs. Guest userspace should then
>> be able to open and read/write to
>>
>> /dev/virtio_console/org/qemu/clipboard
>>
>
> That's probably not what we want. I imagine what we want is:
>
> /dev/ttyV0
> /dev/ttyV1
> /dev/ttyVN
>
> And then we want:
>
> /sys/class/virtio-console/ttyV0/name -> "org.qemu.clipboard"
>
> Userspace can detect when new virtio-consoles appear via udev events.
> When it sees a new ttyVN, it can then look in sysfs to discover it's
> name.
OK; but that's kind of roundabout isn't it? An application, instead of
watching for the console port it's interested in, has to instead monitor
all the ports.
So in effect there has to be one app monitoring for new ports and then
that app exec'ing the corresponding app meant for that port.
Amit
Amit Shah wrote:
> On (Tue) Sep 15 2009 [07:57:10], Anthony Liguori wrote:
>
>> Amit Shah wrote:
>>
>>> Hey Greg,
>>>
>>> Can you tell me how this could work out -- each console port could have
>>> a "role" string associated with it (obtainable from the invoking qemu
>>> process in case of qemu/kvm). Something that I have in mind currently
>>> is:
>>>
>>> $ qemu-kvm ... -virtioconsole role=org/qemu/clipboard
>>>
>>> and then the guest kernel sees the string, and puts the
>>> "org/qemu/clipboard" in some file in sysfs. Guest userspace should then
>>> be able to open and read/write to
>>>
>>> /dev/virtio_console/org/qemu/clipboard
>>>
>>>
>> That's probably not what we want. I imagine what we want is:
>>
>> /dev/ttyV0
>> /dev/ttyV1
>> /dev/ttyVN
>>
>> And then we want:
>>
>> /sys/class/virtio-console/ttyV0/name -> "org.qemu.clipboard"
>>
>> Userspace can detect when new virtio-consoles appear via udev events.
>> When it sees a new ttyVN, it can then look in sysfs to discover it's
>> name.
>>
>
> OK; but that's kind of roundabout isn't it? An application, instead of
> watching for the console port it's interested in, has to instead monitor
> all the ports.
>
If you wanted to use /dev/virtio/org/qemu/clipboard you still have the
same problem. You have to use udev or inotify to listen for a new file
in a directory.
The /dev/ path may look nicer from a high level, but the code ends up
being roughly the same for either approach. What I propose has the
advantage of looking like existing subsystems. It also avoids encoding
device information in the device name.
> So in effect there has to be one app monitoring for new ports and then
> that app exec'ing the corresponding app meant for that port.
>
I think if you think through both models, they end up looking the same.
Regards,
Anthony Liguroi
On 09/15/09 14:57, Anthony Liguori wrote:
> That's probably not what we want. I imagine what we want is:
>
> /dev/ttyV0
> /dev/ttyV1
> /dev/ttyVN
Yes.
> And then we want:
>
> /sys/class/virtio-console/ttyV0/name -> "org.qemu.clipboard"
Yes.
> Userspace can detect when new virtio-consoles appear via udev events.
> When it sees a new ttyVN, it can then look in sysfs to discover it's name.
No. udev can create symlinks for you, so apps don't have to dig into
sysfs. You'll need a rule along the lines (untested):
SUBSYSTEM=="virtio", \
DRIVERS="virtio-console", \
SYMLINK+="vcon/$attr{name}"
then udev will create a /dev/vcon/org.qemu.clipboard symlink pointing to
dev/ttyV0. Apps can just open /dev/vcon/$name then.
cheers,
Gerd
Gerd Hoffmann wrote:
>> Userspace can detect when new virtio-consoles appear via udev events.
>> When it sees a new ttyVN, it can then look in sysfs to discover it's
>> name.
>
> No. udev can create symlinks for you, so apps don't have to dig into
> sysfs. You'll need a rule along the lines (untested):
>
> SUBSYSTEM=="virtio", \
> DRIVERS="virtio-console", \
> SYMLINK+="vcon/$attr{name}"
>
> then udev will create a /dev/vcon/org.qemu.clipboard symlink pointing
> to dev/ttyV0. Apps can just open /dev/vcon/$name then.
That works equally well. I don't necessarily think that naming is more
useful.
Regards,
Anthony Liguori
> This device is very much a serial port. I don't see any reason not
> to treat it like one.
Here are a few
- You don't need POSIX multi-open semantics, hangup and the like
- Seek makes sense on some kinds of fixed attributes
- TTY has a relatively large memory overhead per device
- Sysfs is what everything else uses
- Sysfs has some rather complete lifetime management you'll need to
redo by hand
- You don't need idiotic games with numbering spaces
Abusing tty for this is ridiculous. In some ways putting much of it in
kernel is ridiculous too as you can do it with a FUSE fs or simply
export the info guest-guest using SNMP.
Alan
Alan Cox wrote:
>> This device is very much a serial port. I don't see any reason not
>> to treat it like one.
>>
>
> Here are a few
>
> - You don't need POSIX multi-open semantics, hangup and the like
>
We do actually want hangup and a few other of the tty specific ops. The
only thing we really don't want is a baud rate.
> - Seek makes sense on some kinds of fixed attributes
>
I don't think we're dealing with fixed attributes. These are streams.
Fundamentally, this is a paravirtual uart. The improvement over a
standard uart is that there can be a larger number of ports, ports can
have some identification associated with them, and we are not
constrained to the emulated hardware interface which doesn't exist on
certain platforms (like s390).
> - TTY has a relatively large memory overhead per device
> - Sysfs is what everything else uses
> - Sysfs has some rather complete lifetime management you'll need to
> redo by hand
>
sysfs doesn't model streaming data which is what this driver provides.
> - You don't need idiotic games with numbering spaces
>
> Abusing tty for this is ridiculous.
If the argument is that tty is an awkward interface that should only be
used for legacy purposes, then sure, we should just implement a new
userspace interface for this. In fact, this is probably supported by
the very existence of hvc.
On the other hand, this is fundamentally a paravirtual serial device.
Since serial devices are exposed via the tty subsystem, it seems like a
logical choice.
> In some ways putting much of it in
> kernel is ridiculous too as you can do it with a FUSE fs or simply
> export the info guest-guest using SNMP.
>
This device cannot be implemented as-is in userspace because it depends
on DMA which precludes the use of something like uio_pci. We could
modify the device to avoid dma if the feeling was that there was no
interest in putting this in the kernel.
Regards,
Anthony Liguori
Anthony Liguori <[email protected]> writes:
> Alan Cox wrote:
>>> This device is very much a serial port. I don't see any reason not
>>> to treat it like one.
>>>
>>
>> Here are a few
>>
>> - You don't need POSIX multi-open semantics, hangup and the like
>>
>
> We do actually want hangup and a few other of the tty specific ops. The
> only thing we really don't want is a baud rate.
And a line discipline, and messing with the controlling terminal, and
group/session ID, and window size, and software flow control, ...
>> - Seek makes sense on some kinds of fixed attributes
>>
>
> I don't think we're dealing with fixed attributes. These are streams.
> Fundamentally, this is a paravirtual uart. The improvement over a
> standard uart is that there can be a larger number of ports, ports can
> have some identification associated with them, and we are not
> constrained to the emulated hardware interface which doesn't exist on
> certain platforms (like s390).
[...]
Well, really fundamentally, this is just a reliable full-duplex byte
stream, with connect and hangup notification. To me, that sounds more
like TCP with an address family almost, but not quite AF_UNIX, but that
case was thrown out of court long ago, so here we are.
> Well, really fundamentally, this is just a reliable full-duplex byte
> stream, with connect and hangup notification. To me, that sounds more
> like TCP with an address family almost, but not quite AF_UNIX, but that
> case was thrown out of court long ago, so here we are.
Well the tty one has also been thrown out of court because its even dumber
To be honest it sounds more to me like either a pipe or a char device, or
in most cases like sysfs.
Alan
On (Thu) Sep 17 2009 [14:15:31], Alan Cox wrote:
> > Well, really fundamentally, this is just a reliable full-duplex byte
> > stream, with connect and hangup notification. To me, that sounds more
> > like TCP with an address family almost, but not quite AF_UNIX, but that
> > case was thrown out of court long ago, so here we are.
>
> Well the tty one has also been thrown out of court because its even dumber
>
> To be honest it sounds more to me like either a pipe or a char device, or
> in most cases like sysfs.
It's a char device as of now and I've added a 'name' attribute to each
device that can be found in
/sys/class/virtio-console/vconNN/name
Gerd's suggested udev script can then create a symlink from, eg.,
/dev/vconNN -> /dev/virtio/console/org/qemu/clipboard
I've not ripped out the code that allocates the port numbers in
userspace and also the code where the ports can occur in any range
between 0..max_nr_ports. It will result in some simplification.
So here's the code, just an rfc.
Alan, I'm not sure how many ports at a time people would want to use so
allocating one major device for this seems OK?
Amit
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 6a06913..fe76627 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -678,7 +678,9 @@ config VIRTIO_CONSOLE
select HVC_DRIVER
help
Virtio console for use with lguest and other hypervisors.
-
+ Also serves as a general-purpose serial device for data transfer
+ between the guest and host. Character devices at /dev/vconNN will
+ be created when corresponding ports are found.
config HVCS
tristate "IBM Hypervisor Virtual Console Server support"
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 0d328b5..009e1b0 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -9,10 +9,8 @@
* functions.
:*/
-/*M:002 The console can be flooded: while the Guest is processing input the
- * Host can send more. Buffering in the Host could alleviate this, but it is a
- * difficult problem in general. :*/
/* Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
+ * Copyright (C) 2009, Amit Shah, Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,115 +26,456 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+
+#include <linux/cdev.h>
+#include <linux/device.h>
#include <linux/err.h>
+#include <linux/fs.h>
#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
#include <linux/virtio.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_console.h>
+#include <linux/workqueue.h>
#include "hvc_console.h"
-/*D:340 These represent our input and output console queues, and the virtio
- * operations for them. */
-static struct virtqueue *in_vq, *out_vq;
-static struct virtio_device *vdev;
+/* This struct stores data that's common to all the ports */
+struct virtio_console_struct {
+ /*
+ * Workqueue handlers where we process deferred work after an
+ * interrupt
+ */
+ struct work_struct rx_work;
+ struct work_struct tx_work;
+ struct work_struct config_work;
-/* This is our input buffer, and how much data is left in it. */
-static unsigned int in_len;
-static char *in, *inbuf;
+ struct list_head port_head;
+ struct list_head unused_read_head;
+ struct list_head unused_write_head;
-/* The operations for our console. */
-static struct hv_ops virtio_cons;
+ /* To protect the list of unused write buffers */
+ spinlock_t write_list_lock;
-/* The hvc device */
-static struct hvc_struct *hvc;
+ struct virtio_device *vdev;
+ struct class *class;
+ /* The input and the output queues */
+ struct virtqueue *in_vq, *out_vq;
-/*D:310 The put_chars() callback is pretty straightforward.
- *
- * We turn the characters into a scatter-gather list, add it to the output
- * queue and then kick the Host. Then we sit here waiting for it to finish:
- * inefficient in theory, but in practice implementations will do it
- * immediately (lguest's Launcher does). */
-static int put_chars(u32 vtermno, const char *buf, int count)
+ /* The current config space is stored here */
+ struct virtio_console_config *config;
+};
+
+/* This struct holds individual buffers received for each port */
+struct virtio_console_port_buffer {
+ struct list_head next;
+
+ char *buf;
+
+ /* length of the buffer */
+ size_t len;
+ /* offset in the buf from which to consume data */
+ size_t offset;
+};
+
+/* This struct holds the per-port data */
+struct virtio_console_port {
+ /* Next port in the list, head is in the virtio_console_struct */
+ struct list_head next;
+
+ /* Buffer management */
+ struct list_head readbuf_head;
+
+ /* A waitqueue for poll() or blocking read operations */
+ wait_queue_head_t waitqueue;
+
+ /* Each port associates with a separate char device */
+ struct cdev cdev;
+ struct device *dev;
+
+ /* The hvc device, if this port is associated with a console */
+ struct hvc_struct *hvc;
+
+ /* The 'name' of the port that we expose via sysfs properties */
+ char *name;
+
+ /* Is the host device open */
+ bool host_connected;
+};
+
+static struct virtio_console_struct virtconsole;
+
+static int major = 60; /* from the experimental range */
+
+static struct virtio_console_port *get_port_from_id(u32 id)
{
- struct scatterlist sg[1];
- unsigned int len;
-
- /* This is a convenient routine to initialize a single-elem sg list */
- sg_init_one(sg, buf, count);
-
- /* add_buf wants a token to identify this buffer: we hand it any
- * non-NULL pointer, since there's only ever one buffer. */
- if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) >= 0) {
- /* Tell Host to go! */
- out_vq->vq_ops->kick(out_vq);
- /* Chill out until it's done with the buffer. */
- while (!out_vq->vq_ops->get_buf(out_vq, &len))
- cpu_relax();
+ struct virtio_console_port *port;
+
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (MINOR(port->dev->devt) == id)
+ return port;
}
+ return NULL;
+}
- /* We're expected to return the amount of data we wrote: all of it. */
- return count;
+static int get_id_from_port(struct virtio_console_port *port)
+{
+ return MINOR(port->dev->devt);
}
-/* Create a scatter-gather list representing our input buffer and put it in the
- * queue. */
-static void add_inbuf(void)
+static bool is_console_port(struct virtio_console_port *port)
{
- struct scatterlist sg[1];
- sg_init_one(sg, inbuf, PAGE_SIZE);
+ u32 port_nr = get_id_from_port(port);
- /* We should always be able to add one buffer to an empty queue. */
- if (in_vq->vq_ops->add_buf(in_vq, sg, 0, 1, inbuf) < 0)
- BUG();
- in_vq->vq_ops->kick(in_vq);
+ if (port_nr == VIRTIO_CONSOLE_CONSOLE_PORT ||
+ port_nr == VIRTIO_CONSOLE_CONSOLE2_PORT)
+ return true;
+ return false;
}
-/*D:350 get_chars() is the callback from the hvc_console infrastructure when
- * an interrupt is received.
- *
- * Most of the code deals with the fact that the hvc_console() infrastructure
- * only asks us for 16 bytes at a time. We keep in_offset and in_used fields
- * for partially-filled buffers. */
-static int get_chars(u32 vtermno, char *buf, int count)
+static inline bool use_multiport(void)
{
- /* If we don't have an input queue yet, we can't get input. */
- BUG_ON(!in_vq);
+ /*
+ * This condition can be true when put_chars is called from
+ * early_init
+ */
+ if (!virtconsole.vdev)
+ return 0;
+ return virtconsole.vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+}
+
+static inline bool is_internal(u32 flags)
+{
+ return flags & VIRTIO_CONSOLE_ID_INTERNAL;
+}
+
+/*
+ * Give out the data that's requested from the buffers that we have
+ * queued up per port
+ */
+static ssize_t fill_readbuf(struct virtio_console_port *port,
+ char *out_buf, size_t out_count, bool to_user)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+ ssize_t out_offset, ret;
+
+ out_offset = 0;
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ size_t copy_size;
- /* No buffer? Try to get one. */
- if (!in_len) {
- in = in_vq->vq_ops->get_buf(in_vq, &in_len);
- if (!in)
+ copy_size = out_count;
+ if (copy_size > buf->len - buf->offset)
+ copy_size = buf->len - buf->offset;
+
+ if (to_user) {
+ ret = copy_to_user(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ /* FIXME: Deal with ret != 0 */
+ } else {
+ memcpy(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ ret = 0; /* Emulate copy_to_user behaviour */
+ }
+
+ /* Return the number of bytes actually copied */
+ ret = copy_size - ret;
+ buf->offset += ret;
+ out_offset += ret;
+ out_count -= ret;
+
+ if (buf->len - buf->offset == 0) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ if (!out_count)
+ break;
+ }
+ return out_offset;
+}
+
+/* The condition that must be true for polling to end */
+static bool wait_is_over(struct virtio_console_port *port)
+{
+ return !list_empty(&port->readbuf_head) || !port->host_connected;
+}
+
+static ssize_t virtconsole_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+ ssize_t ret;
+
+ port = filp->private_data;
+
+ if (list_empty(&port->readbuf_head)) {
+ /*
+ * If nothing's connected on the host just return 0 in
+ * case of list_empty; this tells the userspace app
+ * that there's no connection
+ */
+ if (!port->host_connected)
return 0;
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(port->waitqueue,
+ wait_is_over(port));
+ if (ret < 0)
+ return ret;
}
+ /*
+ * We could've received a disconnection message while we were
+ * waiting for more data.
+ *
+ * This check is not clubbed in the if() statement above as we
+ * might receive some data as well as the host could get
+ * disconnected after we got woken up from our wait. So we
+ * really want to give off whatever data we have and only then
+ * check for host_connected
+ */
+ if (list_empty(&port->readbuf_head) && !port->host_connected)
+ return 0;
- /* You want more than we have to give? Well, try wanting less! */
- if (in_len < count)
- count = in_len;
+ return fill_readbuf(port, ubuf, count, true);
+}
- /* Copy across to their buffer and increment offset. */
- memcpy(buf, in, count);
- in += count;
- in_len -= count;
+static ssize_t send_buf(struct virtio_console_port *port,
+ const char *in_buf, size_t in_count,
+ u32 flags, bool from_user)
+{
+ struct virtqueue *out_vq;
+ struct virtio_console_port_buffer *buf, *buf2;
+ struct scatterlist sg[1];
+ struct virtio_console_header header;
+ size_t in_offset, copy_size;
+ ssize_t ret;
+ unsigned int header_len;
- /* Finished? Re-register buffer so Host will use it again. */
- if (in_len == 0)
- add_inbuf();
+ if (!in_count)
+ return 0;
- return count;
+ out_vq = virtconsole.out_vq;
+ /*
+ * We should not send internal messages to a host that won't
+ * understand them
+ */
+ if (!use_multiport() && is_internal(flags))
+ return 0;
+ header_len = 0;
+ if (use_multiport()) {
+ header.id = get_id_from_port(port);
+ header.flags = flags;
+ header.size = in_count;
+ header_len = sizeof(header);
+ }
+ in_offset = 0; /* offset in the user buffer */
+ while (in_count - in_offset) {
+ copy_size = min(in_count - in_offset + header_len, PAGE_SIZE);
+
+ spin_lock(&virtconsole.write_list_lock);
+ list_for_each_entry_safe(buf, buf2,
+ &virtconsole.unused_write_head,
+ next) {
+ list_del(&buf->next);
+ break;
+ }
+ spin_unlock(&virtconsole.write_list_lock);
+ if (!buf)
+ break;
+ if (header_len) {
+ memcpy(buf->buf, &header, header_len);
+ copy_size -= header_len;
+ }
+ if (from_user)
+ ret = copy_from_user(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ else {
+ /*
+ * Since we're not sure when the host will actually
+ * consume the data and tell us about it, we have
+ * to copy the data here in case the caller
+ * frees the in_buf
+ */
+ memcpy(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ ret = 0; /* Emulate copy_from_user behaviour */
+ }
+ buf->len = header_len + copy_size - ret;
+ sg_init_one(sg, buf->buf, buf->len);
+
+ ret = out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, buf);
+ if (ret < 0) {
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&virtconsole.write_list_lock);
+ list_add_tail(&buf->next,
+ &virtconsole.unused_write_head);
+ spin_unlock(&virtconsole.write_list_lock);
+ break;
+ }
+ in_offset += buf->len - header_len;
+ /*
+ * Only send size with the first buffer. This way
+ * userspace can find out a continuous stream of data
+ * belonging to one write request and consume it
+ * appropriately
+ */
+ header.size = 0;
+
+ /* No space left in the vq anyway */
+ if (!ret)
+ break;
+ }
+ /* Tell Host to go! */
+ out_vq->vq_ops->kick(out_vq);
+
+ /* We're expected to return the amount of data we wrote */
+ return in_offset;
}
-/*:*/
-/*D:320 Console drivers are initialized very early so boot messages can go out,
- * so we do things slightly differently from the generic virtio initialization
- * of the net and block drivers.
+static ssize_t virtconsole_write(struct file *filp, const char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+
+ port = filp->private_data;
+
+ return send_buf(port, ubuf, count, 0, true);
+}
+
+static unsigned int virtconsole_poll(struct file *filp, poll_table *wait)
+{
+ struct virtio_console_port *port;
+ unsigned int ret;
+
+ port = filp->private_data;
+ poll_wait(filp, &port->waitqueue, wait);
+
+ ret = 0;
+ if (!list_empty(&port->readbuf_head))
+ ret |= POLLIN | POLLRDNORM;
+ if (!port->host_connected)
+ ret |= POLLHUP;
+
+ return ret;
+}
+
+static int virtconsole_release(struct inode *inode, struct file *filp)
+{
+ struct virtio_console_control cpkt;
+
+ /* Notify host of port being closed */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 0;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+ return 0;
+}
+
+static int virtconsole_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+
+ port = container_of(cdev, struct virtio_console_port, cdev);
+ filp->private_data = port;
+
+ /* Notify host of port being opened */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 1;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ return 0;
+}
+
+/*
+ * The file operations that we support: programs in the guest can open
+ * a console device, read from it, write to it, poll for data and
+ * close it. The devices are at /dev/vconNN
+ */
+static const struct file_operations virtconsole_fops = {
+ .owner = THIS_MODULE,
+ .open = virtconsole_open,
+ .read = virtconsole_read,
+ .write = virtconsole_write,
+ .poll = virtconsole_poll,
+ .release = virtconsole_release,
+};
+
+
+static ssize_t show_port_name(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ struct virtio_console_port *port;
+
+ port = get_port_from_id(MINOR(dev->devt));
+ if (!port || !port->name)
+ return 0;
+
+ return sprintf(buffer, "%s\n", port->name);
+}
+
+static DEVICE_ATTR(name, S_IRUGO, show_port_name, NULL);
+
+static struct attribute *virtcon_sysfs_entries[] = {
+ &dev_attr_name.attr,
+ NULL
+};
+
+static struct attribute_group virtcon_attribute_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = virtcon_sysfs_entries,
+};
+
+
+/*D:310
+ * The cons_put_chars() callback is pretty straightforward.
*
- * At this stage, the console is output-only. It's too early to set up a
- * virtqueue, so we let the drivers do some boutique early-output thing. */
-int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+ * We turn the characters into a scatter-gather list, add it to the output
+ * queue and then kick the Host.
+ *
+ * If the data to be outpu spans more than a page, it's split into
+ * page-sized buffers and then individual buffers are pushed to Host.
+ */
+static int cons_put_chars(u32 vtermno, const char *buf, int count)
{
- virtio_cons.put_chars = put_chars;
- return hvc_instantiate(0, 0, &virtio_cons);
+ struct virtio_console_port *port;
+
+ port = get_port_from_id(vtermno);
+ if (!port)
+ return 0;
+
+ return send_buf(port, buf, count, 0, false);
+}
+
+/*D:350
+ * cons_get_chars() is the callback from the hvc_console
+ * infrastructure when an interrupt is received.
+ *
+ * We call out to fill_readbuf that gets us the required data from the
+ * buffers that are queued up.
+ */
+static int cons_get_chars(u32 vtermno, char *buf, int count)
+{
+ struct virtio_console_port *port;
+
+ /* If we don't have an input queue yet, we can't get input. */
+ BUG_ON(!virtconsole.in_vq);
+
+ port = get_port_from_id(vtermno);
+ if (!port)
+ return 0;
+
+ if (list_empty(&port->readbuf_head))
+ return 0;
+
+ return fill_readbuf(port, buf, count, false);
}
+/*:*/
/*
* virtio console configuration. This supports:
@@ -153,98 +492,628 @@ static void virtcons_apply_config(struct virtio_device *dev)
dev->config->get(dev,
offsetof(struct virtio_console_config, rows),
&ws.ws_row, sizeof(u16));
- hvc_resize(hvc, ws);
+ /*
+ * We'll use this way of resizing only for legacy
+ * support. For newer userspace (VIRTIO_CONSOLE_F_MULTPORT+),
+ * use internal messages to indicate console size
+ * changes so that it can be done per-port
+ */
+ hvc_resize(get_port_from_id(VIRTIO_CONSOLE_CONSOLE_PORT)->hvc, ws);
}
}
/*
- * we support only one console, the hvc struct is a global var
* We set the configuration at this point, since we now have a tty
*/
-static int notifier_add_vio(struct hvc_struct *hp, int data)
+static int cons_notifier_add_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 1;
- virtcons_apply_config(vdev);
+ virtcons_apply_config(virtconsole.vdev);
return 0;
}
-static void notifier_del_vio(struct hvc_struct *hp, int data)
+static void cons_notifier_del_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 0;
}
-static void hvc_handle_input(struct virtqueue *vq)
+/* The operations for our console. */
+static struct hv_ops virtio_cons = {
+ .get_chars = cons_get_chars,
+ .put_chars = cons_put_chars,
+ .notifier_add = cons_notifier_add_vio,
+ .notifier_del = cons_notifier_del_vio,
+ .notifier_hangup = cons_notifier_del_vio,
+};
+
+/*D:320
+ * Console drivers are initialized very early so boot messages can go out,
+ * so we do things slightly differently from the generic virtio initialization
+ * of the net and block drivers.
+ *
+ * At this stage, the console is output-only. It's too early to set up a
+ * virtqueue, so we let the drivers do some boutique early-output thing.
+ */
+int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+{
+ virtio_cons.put_chars = put_chars;
+ return hvc_instantiate(0, 0, &virtio_cons);
+}
+
+
+/* Any secret messages that the Host and Guest want to share */
+static void handle_control_message(struct virtio_console_port *port,
+ struct virtio_console_port_buffer *buf)
+{
+ struct virtio_console_control *cpkt;
+ size_t name_size;
+
+ cpkt = (struct virtio_console_control *)(buf->buf + buf->offset);
+
+ switch (cpkt->event) {
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->host_connected = cpkt->value;
+ break;
+ case VIRTIO_CONSOLE_PORT_NAME:
+ /*
+ * Skip the size of the header and the cpkt to get the size
+ * of the name that was sent
+ */
+ name_size = buf->len - buf->offset - sizeof(*cpkt) + 1;
+
+ port->name = kmalloc(name_size, GFP_KERNEL);
+ if (!port->name) {
+ pr_err("%s: not enough space to store port name\n",
+ __func__);
+ break;
+ }
+ strncpy(port->name, buf->buf + buf->offset + sizeof(*cpkt),
+ name_size - 1);
+ port->name[name_size - 1] = 0;
+ break;
+ }
+}
+
+
+static struct virtio_console_port_buffer *get_buf(size_t buf_size)
+{
+ struct virtio_console_port_buffer *buf;
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ goto out;
+ buf->buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!buf->buf) {
+ kfree(buf);
+ goto out;
+ }
+ buf->len = buf_size;
+out:
+ return buf;
+}
+
+static void fill_queue(struct virtqueue *vq, size_t buf_size,
+ struct list_head *unused_head)
+{
+ struct scatterlist sg[1];
+ struct virtio_console_port_buffer *buf;
+ int ret;
+
+ do {
+ buf = get_buf(buf_size);
+ if (!buf)
+ break;
+ sg_init_one(sg, buf->buf, buf_size);
+
+ ret = vq->vq_ops->add_buf(vq, sg, 0, 1, buf);
+ if (ret < 0) {
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ /* We have to keep track of the unused buffers
+ * so that they can be freed when the module
+ * is being removed
+ */
+ list_add_tail(&buf->next, unused_head);
+ } while (ret > 0);
+ vq->vq_ops->kick(vq);
+}
+
+static void fill_receive_queue(void)
+{
+ fill_queue(virtconsole.in_vq, PAGE_SIZE, &virtconsole.unused_read_head);
+}
+
+/*
+ * This function is only called from the init routine so the spinlock
+ * for the unused_write_head list isn't taken
+ */
+static void alloc_write_bufs(void)
+{
+ struct virtio_console_port_buffer *buf;
+ int i;
+
+ for (i = 0; i < 1024; i++) {
+ buf = get_buf(PAGE_SIZE);
+ if (!buf)
+ break;
+ list_add_tail(&buf->next, &virtconsole.unused_write_head);
+ }
+}
+
+/*
+ * The workhandle for any buffers that appear on our input queue.
+ * Pick the buffer; if it's some communication meant for the Guest,
+ * just process it. Otherwise queue it up for the read() or
+ * get_chars() routines to pick the data up later.
+ */
+static void virtio_console_rx_work_handler(struct work_struct *work)
{
- if (hvc_poll(hvc))
+ struct virtio_console_port *port;
+ struct virtio_console_port_buffer *buf;
+ struct virtio_console_header header;
+ struct virtqueue *vq;
+ unsigned int tmplen, header_len;
+
+ header_len = use_multiport() ? sizeof(header) : 0;
+
+ port = NULL;
+ vq = virtconsole.in_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* The buffer is no longer unused */
+ list_del(&buf->next);
+
+ if (use_multiport()) {
+ memcpy(&header, buf->buf, header_len);
+ port = get_port_from_id(header.id);
+ } else
+ port = get_port_from_id(VIRTIO_CONSOLE_CONSOLE_PORT);
+ if (!port) {
+ /* No valid header at start of buffer. Drop it. */
+ pr_debug("%s: invalid index in buffer, %c %d\n",
+ __func__, buf->buf[0], buf->buf[0]);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free / alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ buf->len = tmplen;
+ buf->offset = header_len;
+ if (use_multiport() && is_internal(header.flags)) {
+ handle_control_message(port, buf);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free/alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ } else {
+ list_add_tail(&buf->next, &port->readbuf_head);
+ /*
+ * We might have missed a connection
+ * notification, e.g. before the queues were
+ * initialised.
+ */
+ port->host_connected = true;
+ }
+ wake_up_interruptible(&port->waitqueue);
+ }
+ if (port && is_console_port(port) && hvc_poll(port->hvc))
hvc_kick();
+
+ /* Allocate buffers for all the ones that got used up */
+ fill_receive_queue();
}
-/*D:370 Once we're further in boot, we get probed like any other virtio device.
- * At this stage we set up the output virtqueue.
+/*
+ * This is the workhandler for buffers that get received on the output
+ * virtqueue, which is an indication that Host consumed the data we
+ * sent it. Since all our buffers going out are of a fixed size we can
+ * just reuse them instead of freeing them and allocating new ones.
+ *
+ * Zero out the buffer so that we don't leak any information from
+ * other processes. There's a small optimisation here as well: the
+ * buffers are PAGE_SIZE-sized; but instead of zeroing the entire
+ * page, we just zero the length that was most recently used and we
+ * can be sure the rest of the page is already set to 0s.
+ *
+ * So once we zero them out we add them back to the unused buffers
+ * list
+ */
+
+static void virtio_console_tx_work_handler(struct work_struct *work)
+{
+ struct virtqueue *vq;
+ struct virtio_console_port_buffer *buf;
+ unsigned int tmplen;
+
+ vq = virtconsole.out_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* 0 the buffer to not leak data from other processes */
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&virtconsole.write_list_lock);
+ list_add_tail(&buf->next, &virtconsole.unused_write_head);
+ spin_unlock(&virtconsole.write_list_lock);
+ }
+}
+
+static void rx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.rx_work);
+}
+
+static void tx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.tx_work);
+}
+
+static void config_intr(struct virtio_device *vdev)
+{
+ /* Handle port hot-add */
+ schedule_work(&virtconsole.config_work);
+
+ /* Handle console size changes */
+ virtcons_apply_config(vdev);
+}
+
+/*
+ * Compare the current config and the new config that we just got and
+ * find out where a particular port was added.
+ */
+static u32 virtconsole_get_hot_add_port(struct virtio_console_config *config)
+{
+ u32 i;
+ u32 port_nr;
+
+ for (i = 0; i < virtconsole.config->max_nr_ports / 32; i++) {
+ port_nr = ffs(config->ports_map[i] ^ virtconsole.config->ports_map[i]);
+ if (port_nr)
+ break;
+ }
+ if (unlikely(!port_nr))
+ return VIRTIO_CONSOLE_BAD_ID;
+
+ /* We used ffs above */
+ port_nr--;
+
+ /* FIXME: Do this only when add_port is successful */
+ virtconsole.config->ports_map[i] |= 1U << port_nr;
+
+ port_nr += i * 32;
+ return port_nr;
+}
+
+/*
+ * Cycle throught the list of active ports and return the next port
+ * that has to be activated.
+ */
+static u32 virtconsole_find_next_port(u32 *map, int *map_i)
+{
+ u32 port_nr;
+
+ while (1) {
+ port_nr = ffs(*map);
+ if (port_nr)
+ break;
+
+ if (unlikely(*map_i >= virtconsole.config->max_nr_ports / 32))
+ return VIRTIO_CONSOLE_BAD_ID;
+ ++*map_i;
+ *map = virtconsole.config->ports_map[*map_i];
+ }
+ /* We used ffs above */
+ port_nr--;
+
+ /* FIXME: Do this only when add_port is successful / reset bit
+ * in config space if add_port was unsuccessful
+ */
+ *map &= ~(1U << port_nr);
+
+ port_nr += *map_i * 32;
+ return port_nr;
+}
+
+static int virtconsole_add_port(u32 port_nr)
+{
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+ dev_t devt;
+ int ret;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ devt = MKDEV(major, port_nr);
+ cdev_init(&port->cdev, &virtconsole_fops);
+
+ ret = register_chrdev_region(devt, 1, "virtio-console");
+ if (ret < 0) {
+ pr_err("%s: error registering chrdev region, ret = %d\n",
+ __func__, ret);
+ goto free_port;
+ }
+ ret = cdev_add(&port->cdev, devt, 1);
+ if (ret < 0) {
+ pr_err("%s: error adding cdev, ret = %d\n", __func__, ret);
+ goto free_chrdev;
+ }
+ port->dev = device_create(virtconsole.class, NULL, devt, NULL,
+ "vcon%u", port_nr);
+ if (IS_ERR(port->dev)) {
+ ret = PTR_ERR(port->dev);
+ pr_err("%s: error creating device, ret = %d\n", __func__, ret);
+ goto free_cdev;
+ }
+ ret = sysfs_create_group(&port->dev->kobj, &virtcon_attribute_group);
+ if (ret) {
+ pr_err("%s: error creating sysfs device attributes, ret = %d\n",
+ __func__, ret);
+ goto free_cdev;
+ }
+
+ INIT_LIST_HEAD(&port->readbuf_head);
+ init_waitqueue_head(&port->waitqueue);
+
+ list_add_tail(&port->next, &virtconsole.port_head);
+
+ /*
+ * Ask for the port's name from Host. The string that we
+ * receive in 'name' can be of arbitrary length; so pass the
+ * maximum available buffer size: PAGE_SIZE.
+ */
+ cpkt.event = VIRTIO_CONSOLE_PORT_NAME;
+ send_buf(port, (char *)&cpkt, PAGE_SIZE,
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ if (is_console_port(port)) {
+ /*
+ * To set up and manage our virtual console, we call
+ * hvc_alloc().
+ *
+ * The first argument of hvc_alloc() is the virtual
+ * console number, so we use zero. The second
+ * argument is the parameter for the notification
+ * mechanism (like irq number). We currently leave
+ * this as zero, virtqueues have implicit
+ * notifications.
+ *
+ * The third argument is a "struct hv_ops" containing
+ * the put_chars() get_chars(), notifier_add() and
+ * notifier_del() pointers. The final argument is the
+ * output buffer size: we can do any size, so we put
+ * PAGE_SIZE here.
+ */
+ port->hvc = hvc_alloc(port_nr, 0, &virtio_cons, PAGE_SIZE);
+ if (IS_ERR(port->hvc)) {
+ ret = PTR_ERR(port->hvc);
+ goto free_cdev;
+ }
+ }
+ pr_info("virtio-console port found at id %u\n", port_nr);
+
+ return 0;
+free_cdev:
+ cdev_del(&port->cdev);
+free_chrdev:
+ unregister_chrdev(major, "virtio-console");
+free_port:
+ kfree(port);
+ return ret;
+}
+
+/* max_ports is always a multiple of 32; enforced in the Host */
+static u32 get_ports_map_size(u32 max_ports)
+{
+ return sizeof(u32) * (max_ports / 32);
+}
+
+/* The workhandler for config-space updates
*
- * To set up and manage our virtual console, we call hvc_alloc(). Since we
- * never remove the console device we never need this pointer again.
+ * This is used when new ports are added
+ */
+static void virtio_console_config_work_handler(struct work_struct *work)
+{
+ struct virtio_console_config *virtconconf;
+ struct virtio_device *vdev = virtconsole.vdev;
+ u32 i, port_nr;
+ int ret;
+
+ virtconconf = kzalloc(sizeof(*virtconconf) +
+ get_ports_map_size(virtconsole.config->max_nr_ports),
+ GFP_KERNEL);
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config, nr_active_ports),
+ &virtconconf->nr_active_ports,
+ sizeof(virtconconf->nr_active_ports));
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config, ports_map),
+ virtconconf->ports_map,
+ get_ports_map_size(virtconsole.config->max_nr_ports));
+
+ /* Hot-add ports */
+ for (i = virtconsole.config->nr_active_ports;
+ i < virtconconf->nr_active_ports; i++) {
+ port_nr = virtconsole_get_hot_add_port(virtconconf);
+ if (port_nr == VIRTIO_CONSOLE_BAD_ID)
+ continue;
+ ret = virtconsole_add_port(port_nr);
+ if (!ret)
+ virtconsole.config->nr_active_ports++;
+ }
+ kfree(virtconconf);
+}
+
+/*D:370
+ * Once we're further in boot, we get probed like any other virtio device.
+ * At this stage we set up the output virtqueue.
*
- * Finally we put our input buffer in the input queue, ready to receive. */
-static int __devinit virtcons_probe(struct virtio_device *dev)
+ * Finally we put our input buffer in the input queue, ready to receive.
+ */
+static int __devinit virtcons_probe(struct virtio_device *vdev)
{
- vq_callback_t *callbacks[] = { hvc_handle_input, NULL};
+ vq_callback_t *callbacks[] = { rx_intr, tx_intr };
const char *names[] = { "input", "output" };
struct virtqueue *vqs[2];
- int err;
+ u32 i, map;
+ int ret, map_i;
+ u32 max_nr_ports;
+ bool multiport;
- vdev = dev;
+ virtconsole.vdev = vdev;
- /* This is the scratch page we use to receive console input */
- inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!inbuf) {
- err = -ENOMEM;
- goto fail;
- }
+ multiport = false;
+ if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT)) {
+ multiport = true;
+ vdev->features[0] |= 1 << VIRTIO_CONSOLE_F_MULTIPORT;
+ vdev->config->finalize_features(vdev);
+
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config,
+ max_nr_ports),
+ &max_nr_ports,
+ sizeof(max_nr_ports));
+ /*
+ * We have a variable-sized config space that's dependent
+ * on the maximum number of ports a guest can have.
+ * So we first get the max number of ports we can have
+ * and then allocate the config space
+ */
+ virtconsole.config = kzalloc(sizeof(struct virtio_console_config)
+ + get_ports_map_size(max_nr_ports),
+ GFP_KERNEL);
+ if (!virtconsole.config)
+ return -ENOMEM;
+ virtconsole.config->max_nr_ports = max_nr_ports;
+
+ vdev->config->get(vdev, offsetof(struct virtio_console_config,
+ nr_active_ports),
+ &virtconsole.config->nr_active_ports,
+ sizeof(virtconsole.config->nr_active_ports));
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config,
+ ports_map),
+ virtconsole.config->ports_map,
+ get_ports_map_size(max_nr_ports));
+ }
/* Find the queues. */
/* FIXME: This is why we want to wean off hvc: we do nothing
* when input comes in. */
- err = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
- if (err)
- goto free;
+ ret = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
+ if (ret)
+ goto fail;
- in_vq = vqs[0];
- out_vq = vqs[1];
+ virtconsole.in_vq = vqs[0];
+ virtconsole.out_vq = vqs[1];
- /* Start using the new console output. */
- virtio_cons.get_chars = get_chars;
- virtio_cons.put_chars = put_chars;
- virtio_cons.notifier_add = notifier_add_vio;
- virtio_cons.notifier_del = notifier_del_vio;
- virtio_cons.notifier_hangup = notifier_del_vio;
-
- /* The first argument of hvc_alloc() is the virtual console number, so
- * we use zero. The second argument is the parameter for the
- * notification mechanism (like irq number). We currently leave this
- * as zero, virtqueues have implicit notifications.
- *
- * The third argument is a "struct hv_ops" containing the put_chars()
- * get_chars(), notifier_add() and notifier_del() pointers.
- * The final argument is the output buffer size: we can do any size,
- * so we put PAGE_SIZE here. */
- hvc = hvc_alloc(0, 0, &virtio_cons, PAGE_SIZE);
- if (IS_ERR(hvc)) {
- err = PTR_ERR(hvc);
- goto free_vqs;
+ INIT_LIST_HEAD(&virtconsole.port_head);
+ INIT_LIST_HEAD(&virtconsole.unused_read_head);
+ INIT_LIST_HEAD(&virtconsole.unused_write_head);
+
+ INIT_WORK(&virtconsole.rx_work, &virtio_console_rx_work_handler);
+ INIT_WORK(&virtconsole.tx_work, &virtio_console_tx_work_handler);
+ INIT_WORK(&virtconsole.config_work, &virtio_console_config_work_handler);
+ spin_lock_init(&virtconsole.write_list_lock);
+
+ fill_receive_queue();
+ alloc_write_bufs();
+
+ if (multiport) {
+ map_i = 0;
+ map = virtconsole.config->ports_map[map_i];
+ for (i = 0; i < virtconsole.config->nr_active_ports; i++) {
+ u32 port_nr;
+
+ port_nr = virtconsole_find_next_port(&map, &map_i);
+ if (unlikely(port_nr == VIRTIO_CONSOLE_BAD_ID))
+ continue;
+ virtconsole_add_port(port_nr);
+ }
+ } else
+ virtconsole_add_port(VIRTIO_CONSOLE_CONSOLE_PORT);
+
+ return 0;
+
+fail:
+ return ret;
+}
+
+/*
+ * Remove port-specific data.
+ * In case the port can't be removed, return non-zero. This could
+ * then be used in the port hot-unplug case.
+ */
+static int virtcons_remove_port_data(struct virtio_console_port *port)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+
+ if (is_console_port(port)) {
+ /* hvc_console is compiled in, at least on Fedora. */
+ /* hvc_remove(hvc); */
+ return 1;
}
+ sysfs_remove_group(&port->dev->kobj, &virtcon_attribute_group);
+ device_destroy(virtconsole.class, port->dev->devt);
+ unregister_chrdev_region(port->dev->devt, 1);
+ cdev_del(&port->cdev);
+
+ kfree(port->name);
- /* Register the input buffer the first time. */
- add_inbuf();
+ /* Remove the buffers in which we have unconsumed data */
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
return 0;
+}
+
+static void virtcons_remove(struct virtio_device *vdev)
+{
+ struct virtio_console_port *port, *port2;
+ struct virtio_console_port_buffer *buf, *buf2;
+ char *tmpbuf;
+ int len;
+
+ unregister_chrdev(major, "virtio-console");
+ class_destroy(virtconsole.class);
+
+ cancel_work_sync(&virtconsole.rx_work);
+ /*
+ * Free up the buffers that we queued up for the Host to pass
+ * us data
+ */
+ while ((tmpbuf = virtconsole.in_vq->vq_ops->get_buf(virtconsole.in_vq,
+ &len)))
+ kfree(tmpbuf);
-free_vqs:
vdev->config->del_vqs(vdev);
-free:
- kfree(inbuf);
-fail:
- return err;
+ /*
+ * Free up the buffers that were sent to us by Host but were
+ * left unused
+ */
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_read_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_write_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(port, port2, &virtconsole.port_head, next) {
+ list_del(&port->next);
+ virtcons_remove_port_data(port);
+ kfree(port);
+ }
+ kfree(virtconsole.config);
}
static struct virtio_device_id id_table[] = {
@@ -254,6 +1123,7 @@ static struct virtio_device_id id_table[] = {
static unsigned int features[] = {
VIRTIO_CONSOLE_F_SIZE,
+ VIRTIO_CONSOLE_F_MULTIPORT,
};
static struct virtio_driver virtio_console = {
@@ -263,14 +1133,34 @@ static struct virtio_driver virtio_console = {
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtcons_probe,
- .config_changed = virtcons_apply_config,
+ .remove = virtcons_remove,
+ .config_changed = config_intr,
};
static int __init init(void)
{
- return register_virtio_driver(&virtio_console);
+ int ret;
+
+ virtconsole.class = class_create(THIS_MODULE, "virtio-console");
+ if (IS_ERR(virtconsole.class)) {
+ pr_err("Error creating virtio-console class\n");
+ ret = PTR_ERR(virtconsole.class);
+ return ret;
+ }
+ ret = register_virtio_driver(&virtio_console);
+ if (ret) {
+ class_destroy(virtconsole.class);
+ return ret;
+ }
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ unregister_virtio_driver(&virtio_console);
}
module_init(init);
+module_exit(fini);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio console driver");
diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h
index b5f5198..d0221bd 100644
--- a/include/linux/virtio_console.h
+++ b/include/linux/virtio_console.h
@@ -2,20 +2,77 @@
#define _LINUX_VIRTIO_CONSOLE_H
#include <linux/types.h>
#include <linux/virtio_config.h>
-/* This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
- * anyone can use the definitions to implement compatible drivers/servers. */
+/*
+ * This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
+ * anyone can use the definitions to implement compatible drivers/servers.
+ *
+ * Copyright (C) Red Hat, Inc., 2009
+ */
/* Feature bits */
#define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */
+#define VIRTIO_CONSOLE_F_MULTIPORT 1 /* Does host provide multiple ports? */
+
+#define VIRTIO_CONSOLE_BAD_ID (~(u32)0) /* Invalid port number */
+
+/* Port at which the virtio console is spawned */
+#define VIRTIO_CONSOLE_CONSOLE_PORT 0
+#define VIRTIO_CONSOLE_CONSOLE2_PORT 1
struct virtio_console_config {
/* colums of the screens */
__u16 cols;
/* rows of the screens */
__u16 rows;
+ /*
+ * max. number of ports supported for each PCI device. Always
+ * a multiple of 32
+ */
+ __u32 max_nr_ports;
+ /* number of ports in use */
+ __u32 nr_active_ports;
+ /*
+ * locations of the ports in use; variable-size array: should
+ * be the last in this struct.
+ */
+ __u32 ports_map[0 /* max_nr_ports / 32 */];
} __attribute__((packed));
+/*
+ * An internal-only message that's passed between the Host and the
+ * Guest for a particular port.
+ */
+struct virtio_console_control {
+ __u16 event;
+ __u16 value;
+};
+
+/* Some events for internal messages (control packets) */
+#define VIRTIO_CONSOLE_PORT_OPEN 0
+#define VIRTIO_CONSOLE_PORT_NAME 1
+
+
+/*
+ * This struct is put in each buffer that gets passed to userspace and
+ * vice-versa
+ */
+struct virtio_console_header {
+ /* Port number */
+ u32 id;
+ /* Some message between host and guest */
+ u32 flags;
+ /*
+ * Complete size of the write request - only sent with the
+ * first buffer for each write request
+ */
+ u32 size;
+} __attribute__((packed));
+
+/* Messages between host and guest ('flags' field in the header above) */
+#define VIRTIO_CONSOLE_ID_INTERNAL (1 << 0)
+
+
#ifdef __KERNEL__
int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int));
#endif /* __KERNEL__ */
> Alan, I'm not sure how many ports at a time people would want to use so
> allocating one major device for this seems OK?
We have very large minor number ranges now so one dynamic major should do
you for a while yet. Probably forever but thats always asking for a
"640K.." moment ;)
> +static ssize_t fill_readbuf(struct virtio_console_port *port,
> + char *out_buf, size_t out_count, bool to_user)
> +{
Save yourself serious grief - and the FIXME pain noted below - unless you
are shipping huge objects use a bounce buffer and kill off the to_user
stuff - modern CPUs are so fast doing cache transfers it really isn't
worth the suffering for small stuff.
> + ret = copy_to_user(out_buf + out_offset,
> + buf->buf + buf->offset,
> + copy_size);
> + /* FIXME: Deal with ret != 0 */
On (Thu) Sep 17 2009 [16:57:01], Alan Cox wrote:
> > Alan, I'm not sure how many ports at a time people would want to use so
> > allocating one major device for this seems OK?
>
> We have very large minor number ranges now so one dynamic major should do
> you for a while yet. Probably forever but thats always asking for a
> "640K.." moment ;)
:-) OK.
> > +static ssize_t fill_readbuf(struct virtio_console_port *port,
> > + char *out_buf, size_t out_count, bool to_user)
> > +{
>
> Save yourself serious grief - and the FIXME pain noted below - unless you
> are shipping huge objects use a bounce buffer and kill off the to_user
> stuff - modern CPUs are so fast doing cache transfers it really isn't
> worth the suffering for small stuff.
I would if I knew what it is that you are suggesting. A bounce buffer
for guest kernel - guest userspace communication?
This is what it looks like right now:
For guest kernel - host communication, I'm using virtio - which is a
bounce buffer (guest allocates pages, host fetches, reads/writes, guest
gets an ack).
But I probably didn't get what you meant, so please explain.
> > + ret = copy_to_user(out_buf + out_offset,
> > + buf->buf + buf->offset,
> > + copy_size);
> > + /* FIXME: Deal with ret != 0 */
Amit
> We do actually want hangup and a few other of the tty specific ops.
> The only thing we really don't want is a baud rate.
So you need break, parity ... no be serious please
> This device cannot be implemented as-is in userspace because it
> depends on DMA which precludes the use of something like uio_pci. We
> could modify the device to avoid dma if the feeling was that there
> was no interest in putting this in the kernel.
So you need a tiny kernel side driver to unpack it into a meaningful
fs, or just a user-user channel with a daemon each end and a protocol
over it - nothing kernel in that.
We don't implement tcp/ip http sessions as tty devices with the kernel
as web server and the same logic applies here.
Alan Cox wrote:
>> We do actually want hangup and a few other of the tty specific ops.
>> The only thing we really don't want is a baud rate.
>>
>
> So you need break, parity ... no be serious please
>
Sure, why not?
In QEMU, we have the ability to hook our devices directly to a physical
serial device and we pass through break, parity, and the other serial
device properties.
Again, this is paravirtual serial device and I think it's entirely
reasonable for people to hook up these ports in the guest directly to
physical serial devices in the host.
>> This device cannot be implemented as-is in userspace because it
>> depends on DMA which precludes the use of something like uio_pci. We
>> could modify the device to avoid dma if the feeling was that there
>> was no interest in putting this in the kernel.
>>
>
> So you need a tiny kernel side driver to unpack it into a meaningful
> fs, or just a user-user channel with a daemon each end and a protocol
> over it - nothing kernel in that.
>
I think there's some confusion over what this driver actually is.
From my perspective, this is a paravirtual serial device and nothing
more. All the discussion of things like guest copy/paste support is a
bit silly. This is the wrong way to approach that sort of thing because
it's not something that belongs in the kernel at all. Furthermore, the
current proposal doesn't handle anything like save/restore which is
needed for live migration.
> We don't implement tcp/ip http sessions as tty devices with the kernel
> as web server and the same logic applies here.
>
I fail to see how this is at all relevant. This is a virtual machine,
we're presenting virtual hardware that behaves like a serial device.
Where web servers fit in is completely beyond me.
Regards,
Anthony Liguori
On 09/18/2009 10:55 AM, Anthony Liguori wrote:
>
> I fail to see how this is at all relevant. This is a virtual machine,
> we're presenting virtual hardware that behaves like a serial device.
> Where web servers fit in is completely beyond me.
>
s/virtio_console/virtio_serial/
There is a fairly noticeable difference between a "console device" and a
"serial device". However, something that can be extended and exported
to a physical serial port is definitely the latter.
-hpa
--
H. Peter Anvin, Intel Open Source Technology Center
I work for Intel. I don't speak on their behalf.
H. Peter Anvin wrote:
> On 09/18/2009 10:55 AM, Anthony Liguori wrote:
>
>> I fail to see how this is at all relevant. This is a virtual machine,
>> we're presenting virtual hardware that behaves like a serial device.
>> Where web servers fit in is completely beyond me.
>>
>>
>
> s/virtio_console/virtio_serial/
>
> There is a fairly noticeable difference between a "console device" and a
> "serial device". However, something that can be extended and exported
> to a physical serial port is definitely the latter.
>
Indeed. I think part of the confusion here is that virtio_console
started as just as console and hence it used hvc. As part of the
current reworking, I think it makes sense to rename the driver
virtio_serial.
Regards,
Anthony Liguori
> -hpa
>
>
On 09/18/09 19:55, Anthony Liguori wrote:
>> So you need break, parity ... no be serious please
>
> Sure, why not?
>
> In QEMU, we have the ability to hook our devices directly to a physical
> serial device and we pass through break, parity, and the other serial
> device properties.
Yes for a emulated 16550.
No for virtio-console.
If you want the guest drive some silly piece of hardware which wants a
serial mode != 8N1 or needs breaks you can't use virtio-console.
> Again, this is paravirtual serial device and I think it's entirely
> reasonable for people to hook up these ports in the guest directly to
> physical serial devices in the host.
Except that the paravirtual device named 'virtio-console' simply doesn't
allow to set serial parameters such as parity, data bits and stop bits.
It is *really* just a (single) byte stream piped over a virtio ring.
The guest side happens to be connected to hvc, so you can use that as
console, thus the name 'virtio-console'.
The plan is to extend that to multiple byte streams. The streams can be
hooked up to hvc (and one stream allways will be for backward
compatibility reasons), giving you a text console. Or they can be
linked to a character device with a name tag (aka sysfs attribute),
providing a named bidirectional byte stream for guest<->host communication.
> From my perspective, this is a paravirtual serial device and nothing
> more.
It simply isn't, see above.
> All the discussion of things like guest copy/paste support is a
> bit silly.
Implementing transparent copy/paste support needs some communication
channel between guest and host. The multiport virtio console driver
provides just that.
> This is the wrong way to approach that sort of thing because
> it's not something that belongs in the kernel at all.
Who claimed the copy/paste bits should go into the kernel?
They will not of course.
> Furthermore, the
> current proposal doesn't handle anything like save/restore which is
> needed for live migration.
That is something the host side (i.e. qemu) has to solve.
The guest will not care about it at all ;)
cheers,
Gerd
On (Fri) Sep 18 2009 [12:55:20], Anthony Liguori wrote:
>> So you need a tiny kernel side driver to unpack it into a meaningful
>> fs, or just a user-user channel with a daemon each end and a protocol
>> over it - nothing kernel in that.
>>
>
> I think there's some confusion over what this driver actually is.
>
> From my perspective, this is a paravirtual serial device and nothing
> more. All the discussion of things like guest copy/paste support is a
> bit silly. This is the wrong way to approach that sort of thing because
> it's not something that belongs in the kernel at all. Furthermore, the
> current proposal doesn't handle anything like save/restore which is
> needed for live migration.
You're mistaken. If you see the patches, I'm only providing a transport
in the kernel. No intelligence in the kernel as to what kind of data
actually flows through. The save/restore support has to come from
userspace, in this case qemu. And that support is very much present.
Amit
--
http://log.amitshah.net/
On (Fri) Sep 18 2009 [10:57:59], H. Peter Anvin wrote:
> On 09/18/2009 10:55 AM, Anthony Liguori wrote:
> >
> > I fail to see how this is at all relevant. This is a virtual machine,
> > we're presenting virtual hardware that behaves like a serial device.
> > Where web servers fit in is completely beyond me.
> >
>
> s/virtio_console/virtio_serial/
>
> There is a fairly noticeable difference between a "console device" and a
> "serial device". However, something that can be extended and exported
> to a physical serial port is definitely the latter.
The patch series did start out as a virtio_serial device. The qemu
maintainers had a problem supporting both, the pre-existing
virtio_console device and the new virtio_serial device as in essence
they're the same thing. So even though this patch adds all the support
in the virtio_console driver, it's really a virtio_serial transport with
a console supported on one of the ports (or multiple, if needed).
To maintain backward compatibility in the current scenario, though, I
chose not to rename the virtio_console driver to virtio_serial.
Amit
--
http://log.amitshah.net/
> Again, this is paravirtual serial device and I think it's entirely
> reasonable for people to hook up these ports in the guest directly to
> physical serial devices in the host.
virtio doesn't support all those features.
> I fail to see how this is at all relevant. This is a virtual
> machine, we're presenting virtual hardware that behaves like a serial
> device.
The more important question is what you are using it for, and when you
ask that question it looks remarkably unlike a tty or serial port and
rather more like a message and status passing bus ?
On Sat, 12 Sep 2009 01:30:10 am Alan Cox wrote:
> > The interface presented to guest userspace is of a simple char
> > device, so it can be used like this:
> >
> > fd = open("/dev/vcon2", O_RDWR);
> > ret = read(fd, buf, 100);
> > ret = write(fd, string, strlen(string));
> >
> > Each port is to be assigned a unique function, for example, the
> > first 4 ports may be reserved for libvirt usage, the next 4 for
> > generic streaming data and so on. This port-function mapping
> > isn't finalised yet.
>
> Unless I am missing something this looks completely bonkers
>
> Every time we have a table of numbers for functionality it ends in
> tears. We have to keep tables up to date and managed, we have to
> administer the magical number to name space.
The number comes from the ABI; we need some identifier for the different
ports. Amit started using names, and I said "just use numbers"; they have
to be documented and agreed by all clients anyway.
ie. the host says "here's a port id 7", which might be the cut & paste
port or whatever.
Cheers,
Rusty.
On (Tue) Sep 22 2009 [12:14:04], Rusty Russell wrote:
> On Sat, 12 Sep 2009 01:30:10 am Alan Cox wrote:
> > > The interface presented to guest userspace is of a simple char
> > > device, so it can be used like this:
> > >
> > > fd = open("/dev/vcon2", O_RDWR);
> > > ret = read(fd, buf, 100);
> > > ret = write(fd, string, strlen(string));
> > >
> > > Each port is to be assigned a unique function, for example, the
> > > first 4 ports may be reserved for libvirt usage, the next 4 for
> > > generic streaming data and so on. This port-function mapping
> > > isn't finalised yet.
> >
> > Unless I am missing something this looks completely bonkers
> >
> > Every time we have a table of numbers for functionality it ends in
> > tears. We have to keep tables up to date and managed, we have to
> > administer the magical number to name space.
>
> The number comes from the ABI; we need some identifier for the different
> ports. Amit started using names, and I said "just use numbers"; they have
> to be documented and agreed by all clients anyway.
>
> ie. the host says "here's a port id 7", which might be the cut & paste
> port or whatever.
Yeah; port 0 has to be reserved for a console (and then we might need
to do a bit more for multiple consoles -- hvc operates on a 'vtermno',
so we need to allocate them as well).
Also, a 'name' property can be attached to ports, as has been suggested:
qemu ... -device virtconport,name=org.qemu.clipboard,port=3,...
spawns a port at id 3 and the guest will also place a file:
/sys/class/virtio-console/vcon3/name
which has "org.qemu.clipboard" as contents, so udev scripts could
create a symlink:
/dev/vcon/org.qemu.clipboard -> /dev/vcon3
Amit
On (Tue) Sep 22 2009 [21:15:49], Amit Shah wrote:
> On (Tue) Sep 22 2009 [12:14:04], Rusty Russell wrote:
> > On Sat, 12 Sep 2009 01:30:10 am Alan Cox wrote:
> > > > The interface presented to guest userspace is of a simple char
> > > > device, so it can be used like this:
> > > >
> > > > fd = open("/dev/vcon2", O_RDWR);
> > > > ret = read(fd, buf, 100);
> > > > ret = write(fd, string, strlen(string));
> > > >
> > > > Each port is to be assigned a unique function, for example, the
> > > > first 4 ports may be reserved for libvirt usage, the next 4 for
> > > > generic streaming data and so on. This port-function mapping
> > > > isn't finalised yet.
> > >
> > > Unless I am missing something this looks completely bonkers
> > >
> > > Every time we have a table of numbers for functionality it ends in
> > > tears. We have to keep tables up to date and managed, we have to
> > > administer the magical number to name space.
> >
> > The number comes from the ABI; we need some identifier for the different
> > ports. Amit started using names, and I said "just use numbers"; they have
> > to be documented and agreed by all clients anyway.
> >
> > ie. the host says "here's a port id 7", which might be the cut & paste
> > port or whatever.
>
> Yeah; port 0 has to be reserved for a console (and then we might need
> to do a bit more for multiple consoles -- hvc operates on a 'vtermno',
> so we need to allocate them as well).
OK; how I solved this is: a new internal message between the guest and
the host. If a host is a console port, the host has to indicate that by
passing a message to the guest and the guest then hooks it up with hvc.
I'm also now maintaining a static vtermno int that's incremented
whenever consoles are added so that multiple ports are supported easily.
> Also, a 'name' property can be attached to ports, as has been suggested:
>
> qemu ... -device virtconport,name=org.qemu.clipboard,port=3,...
>
> spawns a port at id 3 and the guest will also place a file:
>
> /sys/class/virtio-console/vcon3/name
>
> which has "org.qemu.clipboard" as contents, so udev scripts could
> create a symlink:
>
> /dev/vcon/org.qemu.clipboard -> /dev/vcon3
Using these concepts, this is the current patch that I have. Please give
it a good review and consider for inclusion.
Amit
>From d58b0cb73963efa5545e985fc3e29127fc37ccb1 Mon Sep 17 00:00:00 2001
From: Amit Shah <[email protected]>
Date: Tue, 29 Sep 2009 14:39:43 +0530
Subject: [PATCH] virtio_console: Add support for multiple ports for generic guest and host communication
Expose multiple char devices ("ports") for simple communication
between the host userspace and guest.
Sample offline usages can be: poking around in a guest to find out
the file systems used, applications installed, etc. Online usages
can be sharing of clipboard data between the guest and the host,
sending information about logged-in users to the host, locking the
screen or session when a vnc session is closed, and so on.
This patch also enables spawning of multiple console ports that
can be attached to hvc consoles.
The interface presented to guest userspace is of a simple char
device, so it can be used like this:
fd = open("/dev/vcon2", O_RDWR);
ret = read(fd, buf, 100);
ret = write(fd, string, strlen(string));
A name property, if set by the host, is exposed in
/sys/class/virtio-console/vconNN/name
that can be used to create symlinks by udev scripts, example:
/dev/vcon/org.libvirt.channel.0 -> /dev/vcon1
For requirements, use-cases and some history see
http://www.linux-kvm.org/page/VMchannel_Requirements
Signed-off-by: Amit Shah <[email protected]>
---
drivers/char/Kconfig | 6 +
drivers/char/virtio_console.c | 1099 +++++++++++++++++++++++++++++++++++-----
include/linux/virtio_console.h | 48 ++-
3 files changed, 1016 insertions(+), 137 deletions(-)
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 6a06913..7b4602f 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -679,6 +679,12 @@ config VIRTIO_CONSOLE
help
Virtio console for use with lguest and other hypervisors.
+ Also serves as a general-purpose serial device for data
+ transfer between the guest and host. Character devices at
+ /dev/vconNN will be created when corresponding ports are
+ found. If specified by the host, a sysfs attribute called
+ 'name' will be populated with a name for the port which can
+ be used by udev scripts to create a symlink to /dev/vconNN.
config HVCS
tristate "IBM Hypervisor Virtual Console Server support"
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 0d328b5..37513e8 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -9,10 +9,8 @@
* functions.
:*/
-/*M:002 The console can be flooded: while the Guest is processing input the
- * Host can send more. Buffering in the Host could alleviate this, but it is a
- * difficult problem in general. :*/
/* Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
+ * Copyright (C) 2009, Amit Shah, Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,115 +26,477 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+
+#include <linux/cdev.h>
+#include <linux/device.h>
#include <linux/err.h>
+#include <linux/fs.h>
#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
#include <linux/virtio.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_console.h>
+#include <linux/workqueue.h>
#include "hvc_console.h"
-/*D:340 These represent our input and output console queues, and the virtio
- * operations for them. */
-static struct virtqueue *in_vq, *out_vq;
-static struct virtio_device *vdev;
+/* This struct stores data that's common to all the ports */
+struct virtio_console_struct {
+ /*
+ * Workqueue handlers where we process deferred work after an
+ * interrupt
+ */
+ struct work_struct rx_work;
+ struct work_struct tx_work;
+ struct work_struct config_work;
-/* This is our input buffer, and how much data is left in it. */
-static unsigned int in_len;
-static char *in, *inbuf;
+ struct list_head port_head;
+ struct list_head unused_read_head;
+ struct list_head unused_write_head;
-/* The operations for our console. */
-static struct hv_ops virtio_cons;
+ /* To protect the list of unused write buffers */
+ spinlock_t write_list_lock;
+
+ struct virtio_device *vdev;
+ struct class *class;
+ /* The input and the output queues */
+ struct virtqueue *in_vq, *out_vq;
+
+ /* The current config space is stored here */
+ struct virtio_console_config config;
+};
+
+/* This struct holds individual buffers received for each port */
+struct virtio_console_port_buffer {
+ struct list_head next;
+
+ char *buf;
+
+ /* length of the buffer */
+ size_t len;
+ /* offset in the buf from which to consume data */
+ size_t offset;
+};
+
+/* This struct holds the per-port data */
+struct virtio_console_port {
+ /* Next port in the list, head is in the virtio_console_struct */
+ struct list_head next;
+
+ /* Buffer management */
+ struct list_head readbuf_head;
+
+ /* A waitqueue for poll() or blocking read operations */
+ wait_queue_head_t waitqueue;
+
+ /* Each port associates with a separate char device */
+ struct cdev cdev;
+ struct device *dev;
+
+ /* The hvc device, if this port is associated with a console */
+ struct hvc_struct *hvc;
+
+ /* The 'name' of the port that we expose via sysfs properties */
+ char *name;
+
+ /* The 'id' to identify the port with the Host */
+ u32 id;
+
+ /* Is the host device open */
+ bool host_connected;
+};
-/* The hvc device */
-static struct hvc_struct *hvc;
+static struct virtio_console_struct virtconsole;
-/*D:310 The put_chars() callback is pretty straightforward.
+/*
+ * This is used to keep track of the number of hvc consoles spawned.
+ * This number is given as first argument to hvc_alloc(). We could as
+ * well pass on the minor number of the char device but to correctly
+ * map an initial console spawned via hvc_instantiate to the console
+ * being hooked up via hvc_alloc, we need to pass the same vtermno.
*
- * We turn the characters into a scatter-gather list, add it to the output
- * queue and then kick the Host. Then we sit here waiting for it to finish:
- * inefficient in theory, but in practice implementations will do it
- * immediately (lguest's Launcher does). */
-static int put_chars(u32 vtermno, const char *buf, int count)
+ * With this int, we just assume the first console being initialised
+ * was the first one that got used as the initial console.
+ */
+static unsigned int hvc_vtermno;
+
+static struct virtio_console_port *get_port_from_devt(dev_t devt)
{
- struct scatterlist sg[1];
- unsigned int len;
-
- /* This is a convenient routine to initialize a single-elem sg list */
- sg_init_one(sg, buf, count);
-
- /* add_buf wants a token to identify this buffer: we hand it any
- * non-NULL pointer, since there's only ever one buffer. */
- if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) >= 0) {
- /* Tell Host to go! */
- out_vq->vq_ops->kick(out_vq);
- /* Chill out until it's done with the buffer. */
- while (!out_vq->vq_ops->get_buf(out_vq, &len))
- cpu_relax();
+ struct virtio_console_port *port;
+
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (port->dev->devt == devt)
+ return port;
}
+ return NULL;
+}
+
+static struct virtio_console_port *get_port_from_id(u32 id)
+{
+ struct virtio_console_port *port;
- /* We're expected to return the amount of data we wrote: all of it. */
- return count;
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (port->id == id)
+ return port;
+ }
+ return NULL;
}
-/* Create a scatter-gather list representing our input buffer and put it in the
- * queue. */
-static void add_inbuf(void)
+static int get_id_from_port(struct virtio_console_port *port)
{
- struct scatterlist sg[1];
- sg_init_one(sg, inbuf, PAGE_SIZE);
+ return port->id;
+}
- /* We should always be able to add one buffer to an empty queue. */
- if (in_vq->vq_ops->add_buf(in_vq, sg, 0, 1, inbuf) < 0)
- BUG();
- in_vq->vq_ops->kick(in_vq);
+static bool is_console_port(struct virtio_console_port *port)
+{
+ if (port->hvc)
+ return true;
+ return false;
}
-/*D:350 get_chars() is the callback from the hvc_console infrastructure when
- * an interrupt is received.
- *
- * Most of the code deals with the fact that the hvc_console() infrastructure
- * only asks us for 16 bytes at a time. We keep in_offset and in_used fields
- * for partially-filled buffers. */
-static int get_chars(u32 vtermno, char *buf, int count)
+static inline bool use_multiport(void)
{
- /* If we don't have an input queue yet, we can't get input. */
- BUG_ON(!in_vq);
+ /*
+ * This condition can be true when put_chars is called from
+ * early_init
+ */
+ if (!virtconsole.vdev)
+ return 0;
+ return virtconsole.vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+}
- /* No buffer? Try to get one. */
- if (!in_len) {
- in = in_vq->vq_ops->get_buf(in_vq, &in_len);
- if (!in)
+static inline bool is_internal(u32 flags)
+{
+ return flags & VIRTIO_CONSOLE_ID_INTERNAL;
+}
+
+/*
+ * Give out the data that's requested from the buffers that we have
+ * queued up per port
+ */
+static ssize_t fill_readbuf(struct virtio_console_port *port,
+ char *out_buf, size_t out_count, bool to_user)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+ ssize_t out_offset, ret;
+
+ out_offset = 0;
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ size_t copy_size;
+
+ copy_size = out_count;
+ if (copy_size > buf->len - buf->offset)
+ copy_size = buf->len - buf->offset;
+
+ if (to_user) {
+ ret = copy_to_user(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ /* FIXME: Deal with ret != 0 */
+ } else {
+ memcpy(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ ret = 0; /* Emulate copy_to_user behaviour */
+ }
+
+ /* Return the number of bytes actually copied */
+ ret = copy_size - ret;
+ buf->offset += ret;
+ out_offset += ret;
+ out_count -= ret;
+
+ if (buf->len - buf->offset == 0) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ if (!out_count)
+ break;
+ }
+ return out_offset;
+}
+
+/* The condition that must be true for polling to end */
+static bool wait_is_over(struct virtio_console_port *port)
+{
+ return !list_empty(&port->readbuf_head) || !port->host_connected;
+}
+
+static ssize_t virtconsole_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+ ssize_t ret;
+
+ port = filp->private_data;
+
+ if (list_empty(&port->readbuf_head)) {
+ /*
+ * If nothing's connected on the host just return 0 in
+ * case of list_empty; this tells the userspace app
+ * that there's no connection
+ */
+ if (!port->host_connected)
return 0;
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(port->waitqueue,
+ wait_is_over(port));
+ if (ret < 0)
+ return ret;
}
+ /*
+ * We could've received a disconnection message while we were
+ * waiting for more data.
+ *
+ * This check is not clubbed in the if() statement above as we
+ * might receive some data as well as the host could get
+ * disconnected after we got woken up from our wait. So we
+ * really want to give off whatever data we have and only then
+ * check for host_connected
+ */
+ if (list_empty(&port->readbuf_head) && !port->host_connected)
+ return 0;
- /* You want more than we have to give? Well, try wanting less! */
- if (in_len < count)
- count = in_len;
+ return fill_readbuf(port, ubuf, count, true);
+}
- /* Copy across to their buffer and increment offset. */
- memcpy(buf, in, count);
- in += count;
- in_len -= count;
+static ssize_t send_buf(struct virtio_console_port *port,
+ const char *in_buf, size_t in_count,
+ u32 flags, bool from_user)
+{
+ struct virtqueue *out_vq;
+ struct virtio_console_port_buffer *buf, *buf2;
+ struct scatterlist sg[1];
+ struct virtio_console_header header;
+ size_t in_offset, copy_size;
+ ssize_t ret;
+ unsigned int header_len;
- /* Finished? Re-register buffer so Host will use it again. */
- if (in_len == 0)
- add_inbuf();
+ if (!in_count)
+ return 0;
+
+ out_vq = virtconsole.out_vq;
+ /*
+ * We should not send internal messages to a host that won't
+ * understand them
+ */
+ if (!use_multiport() && is_internal(flags))
+ return 0;
+ header_len = 0;
+ if (use_multiport()) {
+ header.id = get_id_from_port(port);
+ header.flags = flags;
+ header.size = in_count;
+ header_len = sizeof(header);
+ }
+ in_offset = 0; /* offset in the user buffer */
+ while (in_count - in_offset) {
+ copy_size = min(in_count - in_offset + header_len, PAGE_SIZE);
- return count;
+ spin_lock(&virtconsole.write_list_lock);
+ list_for_each_entry_safe(buf, buf2,
+ &virtconsole.unused_write_head,
+ next) {
+ list_del(&buf->next);
+ break;
+ }
+ spin_unlock(&virtconsole.write_list_lock);
+ if (!buf)
+ break;
+ if (header_len) {
+ memcpy(buf->buf, &header, header_len);
+ copy_size -= header_len;
+ }
+ if (from_user)
+ ret = copy_from_user(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ else {
+ /*
+ * Since we're not sure when the host will actually
+ * consume the data and tell us about it, we have
+ * to copy the data here in case the caller
+ * frees the in_buf
+ */
+ memcpy(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ ret = 0; /* Emulate copy_from_user behaviour */
+ }
+ buf->len = header_len + copy_size - ret;
+ sg_init_one(sg, buf->buf, buf->len);
+
+ ret = out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, buf);
+ if (ret < 0) {
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&virtconsole.write_list_lock);
+ list_add_tail(&buf->next,
+ &virtconsole.unused_write_head);
+ spin_unlock(&virtconsole.write_list_lock);
+ break;
+ }
+ in_offset += buf->len - header_len;
+ /*
+ * Only send size with the first buffer. This way
+ * userspace can find out a continuous stream of data
+ * belonging to one write request and consume it
+ * appropriately
+ */
+ header.size = 0;
+
+ /* No space left in the vq anyway */
+ if (!ret)
+ break;
+ }
+ /* Tell Host to go! */
+ out_vq->vq_ops->kick(out_vq);
+
+ /* We're expected to return the amount of data we wrote */
+ return in_offset;
}
-/*:*/
-/*D:320 Console drivers are initialized very early so boot messages can go out,
- * so we do things slightly differently from the generic virtio initialization
- * of the net and block drivers.
+static ssize_t virtconsole_write(struct file *filp, const char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+
+ port = filp->private_data;
+
+ return send_buf(port, ubuf, count, 0, true);
+}
+
+static unsigned int virtconsole_poll(struct file *filp, poll_table *wait)
+{
+ struct virtio_console_port *port;
+ unsigned int ret;
+
+ port = filp->private_data;
+ poll_wait(filp, &port->waitqueue, wait);
+
+ ret = 0;
+ if (!list_empty(&port->readbuf_head))
+ ret |= POLLIN | POLLRDNORM;
+ if (!port->host_connected)
+ ret |= POLLHUP;
+
+ return ret;
+}
+
+static int virtconsole_release(struct inode *inode, struct file *filp)
+{
+ struct virtio_console_control cpkt;
+
+ /* Notify host of port being closed */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 0;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+ return 0;
+}
+
+static int virtconsole_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+
+ port = container_of(cdev, struct virtio_console_port, cdev);
+ filp->private_data = port;
+
+ /* Notify host of port being opened */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 1;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ return 0;
+}
+
+/*
+ * The file operations that we support: programs in the guest can open
+ * a console device, read from it, write to it, poll for data and
+ * close it. The devices are at /dev/vconNN
+ */
+static const struct file_operations virtconsole_fops = {
+ .owner = THIS_MODULE,
+ .open = virtconsole_open,
+ .read = virtconsole_read,
+ .write = virtconsole_write,
+ .poll = virtconsole_poll,
+ .release = virtconsole_release,
+};
+
+
+static ssize_t show_port_name(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ struct virtio_console_port *port;
+
+ port = get_port_from_devt(dev->devt);
+ if (!port || !port->name)
+ return 0;
+
+ return sprintf(buffer, "%s\n", port->name);
+}
+
+static DEVICE_ATTR(name, S_IRUGO, show_port_name, NULL);
+
+static struct attribute *virtcon_sysfs_entries[] = {
+ &dev_attr_name.attr,
+ NULL
+};
+
+static struct attribute_group virtcon_attribute_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = virtcon_sysfs_entries,
+};
+
+
+/*D:310
+ * The cons_put_chars() callback is pretty straightforward.
*
- * At this stage, the console is output-only. It's too early to set up a
- * virtqueue, so we let the drivers do some boutique early-output thing. */
-int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+ * We turn the characters into a scatter-gather list, add it to the output
+ * queue and then kick the Host.
+ *
+ * If the data to be outpu spans more than a page, it's split into
+ * page-sized buffers and then individual buffers are pushed to Host.
+ */
+static int cons_put_chars(u32 vtermno, const char *buf, int count)
{
- virtio_cons.put_chars = put_chars;
- return hvc_instantiate(0, 0, &virtio_cons);
+ struct virtio_console_port *port;
+
+ port = get_port_from_id(vtermno);
+ if (!port)
+ return 0;
+
+ return send_buf(port, buf, count, 0, false);
+}
+
+/*D:350
+ * cons_get_chars() is the callback from the hvc_console
+ * infrastructure when an interrupt is received.
+ *
+ * We call out to fill_readbuf that gets us the required data from the
+ * buffers that are queued up.
+ */
+static int cons_get_chars(u32 vtermno, char *buf, int count)
+{
+ struct virtio_console_port *port;
+
+ /* If we don't have an input queue yet, we can't get input. */
+ BUG_ON(!virtconsole.in_vq);
+
+ port = get_port_from_id(vtermno);
+ if (!port)
+ return 0;
+
+ if (list_empty(&port->readbuf_head))
+ return 0;
+
+ return fill_readbuf(port, buf, count, false);
}
+/*:*/
/*
* virtio console configuration. This supports:
@@ -153,98 +513,546 @@ static void virtcons_apply_config(struct virtio_device *dev)
dev->config->get(dev,
offsetof(struct virtio_console_config, rows),
&ws.ws_row, sizeof(u16));
- hvc_resize(hvc, ws);
+ /*
+ * We'll use this way of resizing only for legacy
+ * support. For newer userspace (VIRTIO_CONSOLE_F_MULTPORT+),
+ * use internal messages to indicate console size
+ * changes so that it can be done per-port
+ */
+ if (!use_multiport())
+ hvc_resize(get_port_from_id(0)->hvc, ws);
}
}
/*
- * we support only one console, the hvc struct is a global var
* We set the configuration at this point, since we now have a tty
*/
-static int notifier_add_vio(struct hvc_struct *hp, int data)
+static int cons_notifier_add_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 1;
- virtcons_apply_config(vdev);
+ virtcons_apply_config(virtconsole.vdev);
return 0;
}
-static void notifier_del_vio(struct hvc_struct *hp, int data)
+static void cons_notifier_del_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 0;
}
-static void hvc_handle_input(struct virtqueue *vq)
+/* The operations for our console. */
+static struct hv_ops virtio_cons = {
+ .get_chars = cons_get_chars,
+ .put_chars = cons_put_chars,
+ .notifier_add = cons_notifier_add_vio,
+ .notifier_del = cons_notifier_del_vio,
+ .notifier_hangup = cons_notifier_del_vio,
+};
+
+/*D:320
+ * Console drivers are initialized very early so boot messages can go out,
+ * so we do things slightly differently from the generic virtio initialization
+ * of the net and block drivers.
+ *
+ * At this stage, the console is output-only. It's too early to set up a
+ * virtqueue, so we let the drivers do some boutique early-output thing.
+ */
+int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+{
+ virtio_cons.put_chars = put_chars;
+ return hvc_instantiate(0, 0, &virtio_cons);
+}
+
+int init_port_console(struct virtio_console_port *port)
+{
+ int ret = 0;
+
+ /*
+ * The Host's telling us this port is a console port. Hook it
+ * up with an hvc console.
+ *
+ * To set up and manage our virtual console, we call
+ * hvc_alloc().
+ *
+ * The first argument of hvc_alloc() is the virtual console
+ * number. The second argument is the parameter for the
+ * notification mechanism (like irq number). We currently
+ * leave this as zero, virtqueues have implicit notifications.
+ *
+ * The third argument is a "struct hv_ops" containing the
+ * put_chars() get_chars(), notifier_add() and notifier_del()
+ * pointers. The final argument is the output buffer size: we
+ * can do any size, so we put PAGE_SIZE here.
+ */
+ port->hvc = hvc_alloc(hvc_vtermno++, 0, &virtio_cons, PAGE_SIZE);
+ if (IS_ERR(port->hvc)) {
+ ret = PTR_ERR(port->hvc);
+ pr_err("%s: Could not alloc hvc for virtio console port, ret = %d\n",
+ __func__, ret);
+ port->hvc = NULL;
+ }
+ return ret;
+}
+
+/* Any secret messages that the Host and Guest want to share */
+static void handle_control_message(struct virtio_console_port *port,
+ struct virtio_console_port_buffer *buf)
{
- if (hvc_poll(hvc))
- hvc_kick();
+ struct virtio_console_control *cpkt;
+ size_t name_size;
+
+ cpkt = (struct virtio_console_control *)(buf->buf + buf->offset);
+
+ switch (cpkt->event) {
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->host_connected = cpkt->value;
+ break;
+ case VIRTIO_CONSOLE_PORT_NAME:
+ /*
+ * Skip the size of the header and the cpkt to get the size
+ * of the name that was sent
+ */
+ name_size = buf->len - buf->offset - sizeof(*cpkt) + 1;
+
+ port->name = kmalloc(name_size, GFP_KERNEL);
+ if (!port->name) {
+ pr_err("%s: not enough space to store port name\n",
+ __func__);
+ break;
+ }
+ strncpy(port->name, buf->buf + buf->offset + sizeof(*cpkt),
+ name_size - 1);
+ port->name[name_size - 1] = 0;
+ break;
+ case VIRTIO_CONSOLE_CONSOLE_PORT:
+ if (!cpkt->value)
+ break;
+ init_port_console(port);
+ /*
+ * Could remove the port here in case init fails - but
+ * have to notify the host first
+ */
+ break;
+ }
}
-/*D:370 Once we're further in boot, we get probed like any other virtio device.
- * At this stage we set up the output virtqueue.
+
+static struct virtio_console_port_buffer *get_buf(size_t buf_size)
+{
+ struct virtio_console_port_buffer *buf;
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ goto out;
+ buf->buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!buf->buf) {
+ kfree(buf);
+ goto out;
+ }
+ buf->len = buf_size;
+out:
+ return buf;
+}
+
+static void fill_queue(struct virtqueue *vq, size_t buf_size,
+ struct list_head *unused_head)
+{
+ struct scatterlist sg[1];
+ struct virtio_console_port_buffer *buf;
+ int ret;
+
+ do {
+ buf = get_buf(buf_size);
+ if (!buf)
+ break;
+ sg_init_one(sg, buf->buf, buf_size);
+
+ ret = vq->vq_ops->add_buf(vq, sg, 0, 1, buf);
+ if (ret < 0) {
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ /* We have to keep track of the unused buffers
+ * so that they can be freed when the module
+ * is being removed
+ */
+ list_add_tail(&buf->next, unused_head);
+ } while (ret > 0);
+ vq->vq_ops->kick(vq);
+}
+
+static void fill_receive_queue(void)
+{
+ fill_queue(virtconsole.in_vq, PAGE_SIZE, &virtconsole.unused_read_head);
+}
+
+/*
+ * This function is only called from the init routine so the spinlock
+ * for the unused_write_head list isn't taken
+ */
+static void alloc_write_bufs(void)
+{
+ struct virtio_console_port_buffer *buf;
+ int i;
+
+ for (i = 0; i < 1024; i++) {
+ buf = get_buf(PAGE_SIZE);
+ if (!buf)
+ break;
+ list_add_tail(&buf->next, &virtconsole.unused_write_head);
+ }
+}
+
+/*
+ * The workhandler for any buffers that appear on our input queue.
+ * Pick the buffer; if it's some internal communication meant for the
+ * us, just process it. Otherwise queue it up for the read() or
+ * get_chars() routines to pick the data up later.
+ */
+static void virtio_console_rx_work_handler(struct work_struct *work)
+{
+ struct virtio_console_port *port;
+ struct virtio_console_port_buffer *buf;
+ struct virtio_console_header header;
+ struct virtqueue *vq;
+ unsigned int tmplen, header_len;
+
+ header_len = use_multiport() ? sizeof(header) : 0;
+
+ port = NULL;
+ vq = virtconsole.in_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* The buffer is no longer unused */
+ list_del(&buf->next);
+
+ if (use_multiport()) {
+ memcpy(&header, buf->buf, header_len);
+ port = get_port_from_id(header.id);
+ } else
+ port = get_port_from_id(0);
+ if (!port) {
+ /* No valid header at start of buffer. Drop it. */
+ pr_debug("%s: invalid index in buffer, %c %d\n",
+ __func__, buf->buf[0], buf->buf[0]);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free / alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ buf->len = tmplen;
+ buf->offset = header_len;
+ if (use_multiport() && is_internal(header.flags)) {
+ handle_control_message(port, buf);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free/alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ } else {
+ list_add_tail(&buf->next, &port->readbuf_head);
+ /*
+ * We might have missed a connection
+ * notification, e.g. before the queues were
+ * initialised.
+ */
+ port->host_connected = true;
+ }
+ wake_up_interruptible(&port->waitqueue);
+ }
+ if (port && is_console_port(port) && !list_empty(&port->readbuf_head))
+ if (hvc_poll(port->hvc))
+ hvc_kick();
+ /* Allocate buffers for all the ones that got used up */
+ fill_receive_queue();
+}
+
+/*
+ * This is the workhandler for buffers that get received on the output
+ * virtqueue, which is an indication that Host consumed the data we
+ * sent it. Since all our buffers going out are of a fixed size we can
+ * just reuse them instead of freeing them and allocating new ones.
*
- * To set up and manage our virtual console, we call hvc_alloc(). Since we
- * never remove the console device we never need this pointer again.
+ * Zero out the buffer so that we don't leak any information from
+ * other processes. There's a small optimisation here as well: the
+ * buffers are PAGE_SIZE-sized; but instead of zeroing the entire
+ * page, we just zero the length that was most recently used and we
+ * can be sure the rest of the page is already set to 0s.
*
- * Finally we put our input buffer in the input queue, ready to receive. */
-static int __devinit virtcons_probe(struct virtio_device *dev)
+ * So once we zero them out we add them back to the unused buffers
+ * list
+ */
+
+static void virtio_console_tx_work_handler(struct work_struct *work)
{
- vq_callback_t *callbacks[] = { hvc_handle_input, NULL};
+ struct virtqueue *vq;
+ struct virtio_console_port_buffer *buf;
+ unsigned int tmplen;
+
+ vq = virtconsole.out_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* 0 the buffer to not leak data from other processes */
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&virtconsole.write_list_lock);
+ list_add_tail(&buf->next, &virtconsole.unused_write_head);
+ spin_unlock(&virtconsole.write_list_lock);
+ }
+}
+
+static void rx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.rx_work);
+}
+
+static void tx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.tx_work);
+}
+
+static void config_intr(struct virtio_device *vdev)
+{
+ /* Handle port hot-add */
+ schedule_work(&virtconsole.config_work);
+
+ /* Handle console size changes */
+ virtcons_apply_config(vdev);
+}
+
+static int virtconsole_add_port(u32 port_nr)
+{
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+ dev_t devt;
+ int ret;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->id = port_nr;
+
+ cdev_init(&port->cdev, &virtconsole_fops);
+
+ ret = alloc_chrdev_region(&devt, 0, 1, "virtio-console");
+ if (ret < 0) {
+ pr_err("%s: error allocing chrdev region, ret = %d\n",
+ __func__, ret);
+ goto free_port;
+ }
+ ret = cdev_add(&port->cdev, devt, 1);
+ if (ret < 0) {
+ pr_err("%s: error adding cdev, ret = %d\n", __func__, ret);
+ goto free_chrdev;
+ }
+ port->dev = device_create(virtconsole.class, NULL, devt, NULL,
+ "vcon%u", port_nr);
+ if (IS_ERR(port->dev)) {
+ ret = PTR_ERR(port->dev);
+ pr_err("%s: error creating device, ret = %d\n", __func__, ret);
+ goto free_cdev;
+ }
+ ret = sysfs_create_group(&port->dev->kobj, &virtcon_attribute_group);
+ if (ret) {
+ pr_err("%s: error creating sysfs device attributes, ret = %d\n",
+ __func__, ret);
+ goto free_cdev;
+ }
+
+ INIT_LIST_HEAD(&port->readbuf_head);
+ init_waitqueue_head(&port->waitqueue);
+
+ list_add_tail(&port->next, &virtconsole.port_head);
+
+ /*
+ * Ask for the port's name from Host. The string that we
+ * receive in 'name' can be of arbitrary length; so pass the
+ * maximum available buffer size: PAGE_SIZE.
+ */
+ cpkt.event = VIRTIO_CONSOLE_PORT_NAME;
+ send_buf(port, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ /*
+ * If we're not using multiport support, this has to be a console port
+ */
+ if (!use_multiport()) {
+ ret = init_port_console(port);
+ if (ret)
+ goto free_cdev;
+ }
+
+ pr_debug("virtio-console port found at id %u\n", port_nr);
+
+ return 0;
+free_cdev:
+ cdev_del(&port->cdev);
+free_chrdev:
+ unregister_chrdev_region(devt, 1);
+free_port:
+ kfree(port);
+ return ret;
+}
+
+
+/* The workhandler for config-space updates
+ *
+ * This is used when new ports are added
+ */
+static void virtio_console_config_work_handler(struct work_struct *work)
+{
+ struct virtio_console_config virtconconf;
+ struct virtio_device *vdev = virtconsole.vdev;
+ u32 i;
+ int ret;
+
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config, nr_active_ports),
+ &virtconconf.nr_active_ports,
+ sizeof(virtconconf.nr_active_ports));
+
+ /* Hot-add ports */
+ for (i = virtconsole.config.nr_active_ports;
+ i < virtconconf.nr_active_ports; i++) {
+ ret = virtconsole_add_port(virtconsole.config.nr_active_ports + i);
+ if (!ret)
+ virtconsole.config.nr_active_ports++;
+ }
+}
+
+/*D:370
+ * Once we're further in boot, we get probed like any other virtio device.
+ * At this stage we set up the output virtqueue.
+ *
+ * Finally we put our input buffer in the input queue, ready to receive.
+ */
+static int __devinit virtcons_probe(struct virtio_device *vdev)
+{
+ vq_callback_t *callbacks[] = { rx_intr, tx_intr };
const char *names[] = { "input", "output" };
struct virtqueue *vqs[2];
- int err;
+ u32 i;
+ int ret;
+ bool multiport;
- vdev = dev;
+ virtconsole.vdev = vdev;
- /* This is the scratch page we use to receive console input */
- inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!inbuf) {
- err = -ENOMEM;
- goto fail;
- }
+ multiport = false;
+ if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT)) {
+ multiport = true;
+ vdev->features[0] |= 1 << VIRTIO_CONSOLE_F_MULTIPORT;
+ vdev->config->finalize_features(vdev);
+ vdev->config->get(vdev, offsetof(struct virtio_console_config,
+ nr_active_ports),
+ &virtconsole.config.nr_active_ports,
+ sizeof(virtconsole.config.nr_active_ports));
+ }
/* Find the queues. */
/* FIXME: This is why we want to wean off hvc: we do nothing
* when input comes in. */
- err = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
- if (err)
- goto free;
+ ret = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
+ if (ret)
+ goto fail;
- in_vq = vqs[0];
- out_vq = vqs[1];
+ virtconsole.in_vq = vqs[0];
+ virtconsole.out_vq = vqs[1];
- /* Start using the new console output. */
- virtio_cons.get_chars = get_chars;
- virtio_cons.put_chars = put_chars;
- virtio_cons.notifier_add = notifier_add_vio;
- virtio_cons.notifier_del = notifier_del_vio;
- virtio_cons.notifier_hangup = notifier_del_vio;
-
- /* The first argument of hvc_alloc() is the virtual console number, so
- * we use zero. The second argument is the parameter for the
- * notification mechanism (like irq number). We currently leave this
- * as zero, virtqueues have implicit notifications.
- *
- * The third argument is a "struct hv_ops" containing the put_chars()
- * get_chars(), notifier_add() and notifier_del() pointers.
- * The final argument is the output buffer size: we can do any size,
- * so we put PAGE_SIZE here. */
- hvc = hvc_alloc(0, 0, &virtio_cons, PAGE_SIZE);
- if (IS_ERR(hvc)) {
- err = PTR_ERR(hvc);
- goto free_vqs;
+ INIT_LIST_HEAD(&virtconsole.port_head);
+ INIT_LIST_HEAD(&virtconsole.unused_read_head);
+ INIT_LIST_HEAD(&virtconsole.unused_write_head);
+
+ INIT_WORK(&virtconsole.rx_work, &virtio_console_rx_work_handler);
+ INIT_WORK(&virtconsole.tx_work, &virtio_console_tx_work_handler);
+ INIT_WORK(&virtconsole.config_work, &virtio_console_config_work_handler);
+ spin_lock_init(&virtconsole.write_list_lock);
+
+ fill_receive_queue();
+ alloc_write_bufs();
+
+ virtconsole_add_port(0);
+ if (multiport)
+ for (i = 1; i < virtconsole.config.nr_active_ports; i++)
+ virtconsole_add_port(i);
+
+ return 0;
+
+fail:
+ return ret;
+}
+
+/*
+ * Remove port-specific data.
+ * In case the port can't be removed, return non-zero. This could
+ * then be used in the port hot-unplug case.
+ */
+static int virtcons_remove_port_data(struct virtio_console_port *port)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+
+ if (is_console_port(port)) {
+ /* hvc_console is compiled in, at least on Fedora. */
+ /* hvc_remove(hvc); */
+ return 1;
}
- /* Register the input buffer the first time. */
- add_inbuf();
+ sysfs_remove_group(&port->dev->kobj, &virtcon_attribute_group);
+ device_destroy(virtconsole.class, port->dev->devt);
+ unregister_chrdev_region(port->dev->devt, 1);
+ cdev_del(&port->cdev);
+
+ kfree(port->name);
+
+ /* Remove the buffers in which we have unconsumed data */
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
return 0;
+}
+
+static void virtcons_remove(struct virtio_device *vdev)
+{
+ struct virtio_console_port *port, *port2;
+ struct virtio_console_port_buffer *buf, *buf2;
+ char *tmpbuf;
+ int len;
+
+ class_destroy(virtconsole.class);
+
+ cancel_work_sync(&virtconsole.rx_work);
+ /*
+ * Free up the buffers that we queued up for the Host to pass
+ * us data
+ */
+ while ((tmpbuf = virtconsole.in_vq->vq_ops->get_buf(virtconsole.in_vq,
+ &len)))
+ kfree(tmpbuf);
-free_vqs:
vdev->config->del_vqs(vdev);
-free:
- kfree(inbuf);
-fail:
- return err;
+ /*
+ * Free up the buffers that were sent to us by Host but were
+ * left unused
+ */
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_read_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_write_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(port, port2, &virtconsole.port_head, next) {
+ list_del(&port->next);
+ virtcons_remove_port_data(port);
+ kfree(port);
+ }
}
static struct virtio_device_id id_table[] = {
@@ -254,6 +1062,7 @@ static struct virtio_device_id id_table[] = {
static unsigned int features[] = {
VIRTIO_CONSOLE_F_SIZE,
+ VIRTIO_CONSOLE_F_MULTIPORT,
};
static struct virtio_driver virtio_console = {
@@ -263,14 +1072,34 @@ static struct virtio_driver virtio_console = {
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtcons_probe,
- .config_changed = virtcons_apply_config,
+ .remove = virtcons_remove,
+ .config_changed = config_intr,
};
static int __init init(void)
{
- return register_virtio_driver(&virtio_console);
+ int ret;
+
+ virtconsole.class = class_create(THIS_MODULE, "virtio-console");
+ if (IS_ERR(virtconsole.class)) {
+ pr_err("Error creating virtio-console class\n");
+ ret = PTR_ERR(virtconsole.class);
+ return ret;
+ }
+ ret = register_virtio_driver(&virtio_console);
+ if (ret) {
+ class_destroy(virtconsole.class);
+ return ret;
+ }
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ unregister_virtio_driver(&virtio_console);
}
module_init(init);
+module_exit(fini);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio console driver");
diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h
index b5f5198..96bb6f0 100644
--- a/include/linux/virtio_console.h
+++ b/include/linux/virtio_console.h
@@ -2,19 +2,63 @@
#define _LINUX_VIRTIO_CONSOLE_H
#include <linux/types.h>
#include <linux/virtio_config.h>
-/* This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
- * anyone can use the definitions to implement compatible drivers/servers. */
+/*
+ * This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
+ * anyone can use the definitions to implement compatible drivers/servers.
+ *
+ * Copyright (C) Red Hat, Inc., 2009
+ */
/* Feature bits */
#define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */
+#define VIRTIO_CONSOLE_F_MULTIPORT 1 /* Does host provide multiple ports? */
+
+#define VIRTIO_CONSOLE_BAD_ID (~(u32)0) /* Invalid port number */
struct virtio_console_config {
/* colums of the screens */
__u16 cols;
/* rows of the screens */
__u16 rows;
+ /* number of ports in use */
+ __u32 nr_active_ports;
+} __attribute__((packed));
+
+
+/*
+ * An internal-only message that's passed between the Host and the
+ * Guest for a particular port.
+ */
+struct virtio_console_control {
+ __u16 event;
+ __u16 value;
+};
+
+/* Some events for internal messages (control packets) */
+#define VIRTIO_CONSOLE_PORT_OPEN 0
+#define VIRTIO_CONSOLE_PORT_NAME 1
+#define VIRTIO_CONSOLE_CONSOLE_PORT 2
+
+
+/*
+ * This struct is put in each buffer that gets passed to userspace and
+ * vice-versa
+ */
+struct virtio_console_header {
+ /* Port number */
+ u32 id;
+ /* Some message between host and guest */
+ u32 flags;
+ /*
+ * Complete size of the write request - only sent with the
+ * first buffer for each write request
+ */
+ u32 size;
} __attribute__((packed));
+/* Messages between host and guest ('flags' field in the header above) */
+#define VIRTIO_CONSOLE_ID_INTERNAL (1 << 0)
+
#ifdef __KERNEL__
int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int));
--
1.6.2.5
Am Dienstag 29 September 2009 11:24:31 schrieb Amit Shah:
> OK; how I solved this is: a new internal message between the guest and
> the host. If a host is a console port, the host has to indicate that by
> passing a message to the guest and the guest then hooks it up with hvc.
I am curious how you going to make this compatible with existing code that
currently uses virtio console.
Christian
On (Tue) Sep 29 2009 [12:09:25], Christian Borntraeger wrote:
> Am Dienstag 29 September 2009 11:24:31 schrieb Amit Shah:
> > OK; how I solved this is: a new internal message between the guest and
> > the host. If a host is a console port, the host has to indicate that by
> > passing a message to the guest and the guest then hooks it up with hvc.
>
> I am curious how you going to make this compatible with existing code that
> currently uses virtio console.
I've tested old qemu new kernel, new qemu old kernel, new qemu, new
kernel and all the cases work fine.
Please see the virtcons_probe() function for the details.
Amit
Am Dienstag 29 September 2009 12:33:54 schrieben Sie:
> I've tested old qemu new kernel, new qemu old kernel, new qemu, new
> kernel and all the cases work fine.
The patch breaks at least s390 causing the following oops.
<0>------------[ cut here ]------------
<2>Kernel BUG at 000000000087d902 [verbose debug info unavailable]
<4>illegal operation: 0001 [#1] SMP
<4>Modules linked in:
<4>CPU: 0 Not tainted 2.6.31-selfgit-08936-g851b147-dirty #137
<4>Process swapper (pid: 1, task: 000000000c6de758, ksp: 000000000c6e3838)
<4>Krnl PSW : 0404200180000000 000000000087d902 (early_put_chars+0x6/0x98)
<4> R:0 T:1 IO:0 EX:0 Key:0 M:1 W:0 P:0 AS:0 CC:2 PM:0 EA:3
<4>Krnl GPRS: 0000000000000007 000000000087d8fc 0000000000000000 000000000c6e3c68
<4> 0000000000000010 0000000000834408 0000000000000000 0000000000000000
<4> 000000000c6e3c68 0000000000000000 0000000000000010 000000000000001a
<4> 0000000000000010 0000000000558848 00000000003b6b32 000000000c6e3bc0
<4>Krnl Code:>000000000087d902: cccccccccccc unknown
<4> 000000000087d908: cccccccccccc unknown
<4> 000000000087d90e: cccccccccccc unknown
<4> 000000000087d914: cccccccccccc unknown
<4> 000000000087d91a: cccccccccccc unknown
<4>Call Trace:
<4>([<000000000c6e3cc8>] 0xc6e3cc8)
<4> [<000000000013f7cc>] __call_console_drivers+0xcc/0xe8
<4> [<000000000013fdfa>] release_console_sem+0x1e2/0x29c
<4> [<0000000000140606>] vprintk+0x316/0x498
<4> [<000000000051a846>] printk+0x52/0x64
<4> [<0000000000100240>] init_post+0x28/0x1a4
<4> [<000000000085a48e>] kernel_init+0x2d2/0x3b4
<4> [<000000000010a212>] kernel_thread_starter+0x6/0xc
<4> [<000000000010a20c>] kernel_thread_starter+0x0/0xc
<4>Last Breaking-Event-Address:
<4> [<00000000003b6b30>] hvc_console_print+0x8c/0x12c
<4>
<4>---[ end trace 1e5e8c448b0f2e2b ]---
I took a dump and had a look. It looks that this message thing is indeed not
the problem. Our early_put_chars method was marked as __init and the old code
replaced the put_chars method quickly enough to never make a problem.
Removing the __init from early_put_chars solved this kernel bug, but the tty
performance is now horribly slow. I will test, if the put_chars method is
replaced at all.
Christian
Am Dienstag 29 September 2009 13:02:51 schrieb Christian Borntraeger:
> I took a dump and had a look. It looks that this message thing is indeed
> not the problem. Our early_put_chars method was marked as __init and the
> old code replaced the put_chars method quickly enough to never make a
> problem. Removing the __init from early_put_chars solved this kernel bug,
> but the tty performance is now horribly slow. I will test, if the
> put_chars method is replaced at all.
I can confirm that your patch changes virtio_console in a way, that the
early_put_chars function is not replaced by the final one, but I dont see why.
Christian
Am Dienstag 29 September 2009 11:24:31 schrieb Amit Shah:
> -static void hvc_handle_input(struct virtqueue *vq)
> +/* The operations for our console. */
> +static struct hv_ops virtio_cons = {
> + .get_chars = cons_get_chars,
> + .put_chars = cons_put_chars,
> + .notifier_add = cons_notifier_add_vio,
> + .notifier_del = cons_notifier_del_vio,
> + .notifier_hangup = cons_notifier_del_vio,
> +};
[...]
> +int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
> +{
> + virtio_cons.put_chars = put_chars;
> + return hvc_instantiate(0, 0, &virtio_cons);
> +}
[...]
> +static int __devinit virtcons_probe(struct virtio_device *vdev)
[...]
> - /* Start using the new console output. */
> - virtio_cons.get_chars = get_chars;
> - virtio_cons.put_chars = put_chars;
Ok, that wont work for systems that use virtio_cons_early_init. The early
put_chars method was replaced by the final one, but these hunks changed that
behaviour. Something like the following restores the old behaviour: (only
tested on s390)
Signed-off-by: Christian Borntraeger <[email protected]>
If you agree, you can merge this snipped into your big patch.
---
drivers/char/virtio_console.c | 3 +++
1 file changed, 3 insertions(+)
Index: linux-2.6/drivers/char/virtio_console.c
===================================================================
--- linux-2.6.orig/drivers/char/virtio_console.c
+++ linux-2.6/drivers/char/virtio_console.c
@@ -590,6 +590,9 @@ int init_port_console(struct virtio_cons
pr_err("%s: Could not alloc hvc for virtio console port, ret = %d\n",
__func__, ret);
port->hvc = NULL;
+ } else {
+ /* get rid of early put char variants */
+ virtio_cons.put_chars = cons_put_chars;
}
return ret;
}
On (Tue) Sep 29 2009 [14:03:08], Christian Borntraeger wrote:
> Am Dienstag 29 September 2009 11:24:31 schrieb Amit Shah:
> > -static void hvc_handle_input(struct virtqueue *vq)
> > +/* The operations for our console. */
> > +static struct hv_ops virtio_cons = {
> > + .get_chars = cons_get_chars,
> > + .put_chars = cons_put_chars,
> > + .notifier_add = cons_notifier_add_vio,
> > + .notifier_del = cons_notifier_del_vio,
> > + .notifier_hangup = cons_notifier_del_vio,
> > +};
> [...]
> > +int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
> > +{
> > + virtio_cons.put_chars = put_chars;
> > + return hvc_instantiate(0, 0, &virtio_cons);
> > +}
> [...]
> > +static int __devinit virtcons_probe(struct virtio_device *vdev)
> [...]
> > - /* Start using the new console output. */
> > - virtio_cons.get_chars = get_chars;
> > - virtio_cons.put_chars = put_chars;
>
> Ok, that wont work for systems that use virtio_cons_early_init. The early
> put_chars method was replaced by the final one, but these hunks changed that
> behaviour. Something like the following restores the old behaviour: (only
> tested on s390)
Thanks for the testing as well as the patch! Sadly I can only test this
on qemu and x86, so this is very welcome.
However:
> Signed-off-by: Christian Borntraeger <[email protected]>
> If you agree, you can merge this snipped into your big patch.
>
> ---
> drivers/char/virtio_console.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> Index: linux-2.6/drivers/char/virtio_console.c
> ===================================================================
> --- linux-2.6.orig/drivers/char/virtio_console.c
> +++ linux-2.6/drivers/char/virtio_console.c
> @@ -590,6 +590,9 @@ int init_port_console(struct virtio_cons
> pr_err("%s: Could not alloc hvc for virtio console port, ret = %d\n",
> __func__, ret);
> port->hvc = NULL;
> + } else {
> + /* get rid of early put char variants */
> + virtio_cons.put_chars = cons_put_chars;
> }
> return ret;
> }
I changed it to look like this, which initializes the final value even
when hvc_alloc fails. It should work for you I guess.
I'll merge this in my patch when I send it next.
Can you please ack the patch?
Thanks,
Amit
>From 462aa408c2a1140137d0890ca8043f37b447410d Mon Sep 17 00:00:00 2001
From: Amit Shah <[email protected]>
Date: Tue, 29 Sep 2009 17:46:41 +0530
Subject: [PATCH] virtio_console: fix put_chars after early-init
Christian tested the patch on s390 and found that the output was
very slow. He tracked it down to put_chars never getting init'ed
to the final value.
Signed-off-by: Amit Shah <[email protected]>
---
drivers/char/virtio_console.c | 8 ++++++++
1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 37513e8..598bc0d 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -571,6 +571,14 @@ int init_port_console(struct virtio_console_port *port)
* The Host's telling us this port is a console port. Hook it
* up with an hvc console.
*
+ * We had set the virtio_cons put_chars implementation to
+ * put_chars for early_init. Now that we're done with the
+ * early init phase, replace it with our cons_put_chars
+ * implementation.
+ */
+ virtio_cons.put_chars = cons_put_chars;
+
+ /*
* To set up and manage our virtual console, we call
* hvc_alloc().
*
--
1.6.2.5
Am Dienstag 29 September 2009 14:20:06 schrieb Amit Shah:
> Christian tested the patch on s390 and found that the output was
> very slow. He tracked it down to put_chars never getting init'ed
> to the final value.
>
> Signed-off-by: Amit Shah <[email protected]>
Thanks. This fix is
Acked-by: Christian Borntraeger <[email protected]>
Tested-by: Christian Borntraeger <[email protected]>
I am a bit reluctant to Ack the whole change, since my preference would have
been to not merge virtio serial/console and instead keeping both separate.
We have already managed to clutter all other virtio drivers with tons of
configuration stuff and feature bits - and every driver uses a different model
for configuration and commands (feature bits, config space, config_change
indication, extra config virtqueue, commands embedded into the data....).
Using a different device ID for a different use seem like a better way to me.
On the other hand, this patch allows more than one console (I have not tested
this feature) and with this fix applied I dont see any obvious problems.
For the console part I can give a
Acked-by: Christian Borntraeger <[email protected]> (console)
Ignoring my preference for having a separate driver and devids, I have no
opinion about the generic communication stuff - no ack or nack.
> ---
> drivers/char/virtio_console.c | 8 ++++++++
> 1 files changed, 8 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
> index 37513e8..598bc0d 100644
> --- a/drivers/char/virtio_console.c
> +++ b/drivers/char/virtio_console.c
> @@ -571,6 +571,14 @@ int init_port_console(struct virtio_console_port
> *port) * The Host's telling us this port is a console port. Hook it
> * up with an hvc console.
> *
> + * We had set the virtio_cons put_chars implementation to
> + * put_chars for early_init. Now that we're done with the
> + * early init phase, replace it with our cons_put_chars
> + * implementation.
> + */
> + virtio_cons.put_chars = cons_put_chars;
> +
> + /*
> * To set up and manage our virtual console, we call
> * hvc_alloc().
> *
On (Tue) Sep 29 2009 [14:56:56], Christian Borntraeger wrote:
> Am Dienstag 29 September 2009 14:20:06 schrieb Amit Shah:
> > Christian tested the patch on s390 and found that the output was
> > very slow. He tracked it down to put_chars never getting init'ed
> > to the final value.
> >
> > Signed-off-by: Amit Shah <[email protected]>
>
> Thanks. This fix is
> Acked-by: Christian Borntraeger <[email protected]>
> Tested-by: Christian Borntraeger <[email protected]>
Great, thanks. However I was thinking of moving this init to the probe()
routine instead of in the init_conosle routine just because multiple
consoles can be added and we don't want to init this each time.. just
once in probe is fine.
> I am a bit reluctant to Ack the whole change, since my preference would have
> been to not merge virtio serial/console and instead keeping both separate.
> We have already managed to clutter all other virtio drivers with tons of
> configuration stuff and feature bits - and every driver uses a different model
> for configuration and commands (feature bits, config space, config_change
> indication, extra config virtqueue, commands embedded into the data....).
> Using a different device ID for a different use seem like a better way to me.
Well, Anthony described your objection as a comment in passing and that
you weren't strongly against merging the two drivers when I brought up
your argument sometime back.
Also, it was difficult to make progress and just keep fighting about
these issues. So even though I didn't like merging the stuff, I had to.
Rusty too in a recent mail mentioned he sees both the drivers as one
because the functionlities are similar.
> On the other hand, this patch allows more than one console (I have not tested
> this feature) and with this fix applied I dont see any obvious problems.
Note though that to use the multiple consoles, you'll have to modify
your userspace to handle the new messages that get passed between the
host and the guest (one can argue I've done the guest part; you only
have the host part to be done :-))
> For the console part I can give a
> Acked-by: Christian Borntraeger <[email protected]> (console)
Thanks again. I'll put this in the next spin.
> Ignoring my preference for having a separate driver and devids, I have no
> opinion about the generic communication stuff - no ack or nack.
Amit
On Fri, 11 Sep 2009 11:43:06 pm Amit Shah wrote:
> Expose multiple char devices ("ports") for simple communication
> between the host userspace and guest.
OK, I think other comments have died down, so it's time for a review. This
was the latest patch I could find.
The obvious way to do this is to use multiple pairs of virtqueues. Is this
silly for some reason? Yes, we're restricted to 32k ports.
It means that if we see the MULTIQUEUE feature, we can look for the other
queues. Our current MSI-X friendly API makes that a little painful, but
if we resolve that our code should be sweetness, no?
Rusty.
On (Tue) Sep 29 2009 [22:41:45], Rusty Russell wrote:
> On Fri, 11 Sep 2009 11:43:06 pm Amit Shah wrote:
> > Expose multiple char devices ("ports") for simple communication
> > between the host userspace and guest.
>
> OK, I think other comments have died down, so it's time for a review. This
> was the latest patch I could find.
I just sent a new patch in this thread; can you please review that one?
> The obvious way to do this is to use multiple pairs of virtqueues. Is this
> silly for some reason? Yes, we're restricted to 32k ports.
There were a couple of things to note about that:
- Avi, in the past, mentioned separate ivq and ovqs for each port for
the kind of usage intended would be overkill.
- With the MSI rework, we can't init new i/ovqs for hotplugged ports as
we find them. The queues all have to be init'ed at probe() time. I had
some discussion with Michael some time back on virtualization@, but
thought the overhead of implementing that would be high.
> It means that if we see the MULTIQUEUE feature, we can look for the other
> queues. Our current MSI-X friendly API makes that a little painful, but
> if we resolve that our code should be sweetness, no?
If this is to solve the port numbering, etc., issues, I've thought of a
new way this can be done in the newest patch. What's more, Christian
tested it on s390 as well and it worked with a minor tweak. I tested the
new patch with combinations of qemu and kernel (old/new) and they worked
fine.
Thanks,
Amit
Am Dienstag 29 September 2009 15:09:50 schrieb Amit Shah:
> Great, thanks. However I was thinking of moving this init to the probe()
> routine instead of in the init_conosle routine just because multiple
> consoles can be added and we don't want to init this each time.. just
> once in probe is fine.
If you have new patch CC me and I can give it a spin.
> > I am a bit reluctant to Ack the whole change, since my preference would
> > have been to not merge virtio serial/console and instead keeping both
> > separate. We have already managed to clutter all other virtio drivers
> > with tons of configuration stuff and feature bits - and every driver uses
> > a different model for configuration and commands (feature bits, config
> > space, config_change indication, extra config virtqueue, commands
> > embedded into the data....). Using a different device ID for a different
> > use seem like a better way to me.
>
> Well, Anthony described your objection as a comment in passing and that
> you weren't strongly against merging the two drivers when I brought up
> your argument sometime back.
>
> Also, it was difficult to make progress and just keep fighting about
> these issues. So even though I didn't like merging the stuff, I had to.
Sure, you were caught between two stools... I have to admit that I dropped of
this discussion and did not follow up - I simply considered my wedding and
honeymoon more important ;-)
I still belive that I am right and Anthony is wrong about this 8-), but if
merging is the way to go - so be it.
On (Tue) Sep 29 2009 [15:31:23], Christian Borntraeger wrote:
> Am Dienstag 29 September 2009 15:09:50 schrieb Amit Shah:
> > Great, thanks. However I was thinking of moving this init to the probe()
> > routine instead of in the init_conosle routine just because multiple
> > consoles can be added and we don't want to init this each time.. just
> > once in probe is fine.
>
> If you have new patch CC me and I can give it a spin.
Hey Christian,
I have a new patch that changes a few things:
- moves the put_char fix to probe instead of doing it in
init_port_console(), which gets called on each console port found.
- uses port->id instead of a static hvc_vtermno to pass on a value to
hvc_alloc(). Motivation explained within comments in the code.
- A few other changes that introduce and make use of port->vcon instead
of accessing the static virtconsole directly -- aimed at easing future
fix to have multiple virtio-console devices.
It would be great if you could test this.
Amit
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 6a06913..7b4602f 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -679,6 +679,12 @@ config VIRTIO_CONSOLE
help
Virtio console for use with lguest and other hypervisors.
+ Also serves as a general-purpose serial device for data
+ transfer between the guest and host. Character devices at
+ /dev/vconNN will be created when corresponding ports are
+ found. If specified by the host, a sysfs attribute called
+ 'name' will be populated with a name for the port which can
+ be used by udev scripts to create a symlink to /dev/vconNN.
config HVCS
tristate "IBM Hypervisor Virtual Console Server support"
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 0d328b5..16cdcec 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -9,10 +9,8 @@
* functions.
:*/
-/*M:002 The console can be flooded: while the Guest is processing input the
- * Host can send more. Buffering in the Host could alleviate this, but it is a
- * difficult problem in general. :*/
/* Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
+ * Copyright (C) 2009, Amit Shah, Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,115 +26,468 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+
+#include <linux/cdev.h>
+#include <linux/device.h>
#include <linux/err.h>
+#include <linux/fs.h>
#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
#include <linux/virtio.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_console.h>
+#include <linux/workqueue.h>
#include "hvc_console.h"
-/*D:340 These represent our input and output console queues, and the virtio
- * operations for them. */
-static struct virtqueue *in_vq, *out_vq;
-static struct virtio_device *vdev;
+/* This struct stores data that's common to all the ports */
+struct virtio_console_struct {
+ /*
+ * Workqueue handlers where we process deferred work after an
+ * interrupt
+ */
+ struct work_struct rx_work;
+ struct work_struct tx_work;
+ struct work_struct config_work;
-/* This is our input buffer, and how much data is left in it. */
-static unsigned int in_len;
-static char *in, *inbuf;
+ struct list_head port_head;
+ struct list_head unused_read_head;
+ struct list_head unused_write_head;
-/* The operations for our console. */
-static struct hv_ops virtio_cons;
+ /* To protect the list of unused write buffers */
+ spinlock_t write_list_lock;
-/* The hvc device */
-static struct hvc_struct *hvc;
+ struct virtio_device *vdev;
+ struct class *class;
+ /* The input and the output queues */
+ struct virtqueue *in_vq, *out_vq;
-/*D:310 The put_chars() callback is pretty straightforward.
- *
- * We turn the characters into a scatter-gather list, add it to the output
- * queue and then kick the Host. Then we sit here waiting for it to finish:
- * inefficient in theory, but in practice implementations will do it
- * immediately (lguest's Launcher does). */
-static int put_chars(u32 vtermno, const char *buf, int count)
+ /* The current config space is stored here */
+ struct virtio_console_config config;
+};
+
+/* This struct holds individual buffers received for each port */
+struct virtio_console_port_buffer {
+ struct list_head next;
+
+ char *buf;
+
+ /* length of the buffer */
+ size_t len;
+ /* offset in the buf from which to consume data */
+ size_t offset;
+};
+
+/* This struct holds the per-port data */
+struct virtio_console_port {
+ /* Next port in the list, head is in the virtio_console_struct */
+ struct list_head next;
+
+ /* Pointer to the virtio_console device */
+ struct virtio_console_struct *vcon;
+
+ /* Buffer management */
+ struct list_head readbuf_head;
+
+ /* A waitqueue for poll() or blocking read operations */
+ wait_queue_head_t waitqueue;
+
+ /* Each port associates with a separate char device */
+ struct cdev cdev;
+ struct device *dev;
+
+ /* The hvc device, if this port is associated with a console */
+ struct hvc_struct *hvc;
+
+ /* The 'name' of the port that we expose via sysfs properties */
+ char *name;
+
+ /* The 'id' to identify the port with the Host */
+ u32 id;
+
+ /* Is the host device open */
+ bool host_connected;
+};
+
+static struct virtio_console_struct virtconsole;
+
+static struct virtio_console_port *get_port_from_devt(dev_t devt)
{
- struct scatterlist sg[1];
- unsigned int len;
-
- /* This is a convenient routine to initialize a single-elem sg list */
- sg_init_one(sg, buf, count);
-
- /* add_buf wants a token to identify this buffer: we hand it any
- * non-NULL pointer, since there's only ever one buffer. */
- if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) >= 0) {
- /* Tell Host to go! */
- out_vq->vq_ops->kick(out_vq);
- /* Chill out until it's done with the buffer. */
- while (!out_vq->vq_ops->get_buf(out_vq, &len))
- cpu_relax();
+ struct virtio_console_port *port;
+
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (port->dev->devt == devt)
+ return port;
}
+ return NULL;
+}
+
+static struct virtio_console_port *get_port_from_id(u32 id)
+{
+ struct virtio_console_port *port;
- /* We're expected to return the amount of data we wrote: all of it. */
- return count;
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (port->id == id)
+ return port;
+ }
+ return NULL;
}
-/* Create a scatter-gather list representing our input buffer and put it in the
- * queue. */
-static void add_inbuf(void)
+static int get_id_from_port(struct virtio_console_port *port)
{
- struct scatterlist sg[1];
- sg_init_one(sg, inbuf, PAGE_SIZE);
+ return port->id;
+}
- /* We should always be able to add one buffer to an empty queue. */
- if (in_vq->vq_ops->add_buf(in_vq, sg, 0, 1, inbuf) < 0)
- BUG();
- in_vq->vq_ops->kick(in_vq);
+static bool is_console_port(struct virtio_console_port *port)
+{
+ if (port->hvc)
+ return true;
+ return false;
}
-/*D:350 get_chars() is the callback from the hvc_console infrastructure when
- * an interrupt is received.
- *
- * Most of the code deals with the fact that the hvc_console() infrastructure
- * only asks us for 16 bytes at a time. We keep in_offset and in_used fields
- * for partially-filled buffers. */
-static int get_chars(u32 vtermno, char *buf, int count)
+static inline bool use_multiport(void)
{
- /* If we don't have an input queue yet, we can't get input. */
- BUG_ON(!in_vq);
+ /*
+ * This condition can be true when put_chars is called from
+ * early_init
+ */
+ if (!virtconsole.vdev)
+ return 0;
+ return virtconsole.vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+}
+
+static inline bool is_internal(u32 flags)
+{
+ return flags & VIRTIO_CONSOLE_ID_INTERNAL;
+}
+
+/*
+ * Give out the data that's requested from the buffers that we have
+ * queued up per port
+ */
+static ssize_t fill_readbuf(struct virtio_console_port *port,
+ char *out_buf, size_t out_count, bool to_user)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+ ssize_t out_offset, ret;
+
+ out_offset = 0;
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ size_t copy_size;
+
+ copy_size = out_count;
+ if (copy_size > buf->len - buf->offset)
+ copy_size = buf->len - buf->offset;
+
+ if (to_user) {
+ ret = copy_to_user(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ /* FIXME: Deal with ret != 0 */
+ } else {
+ memcpy(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ ret = 0; /* Emulate copy_to_user behaviour */
+ }
+
+ /* Return the number of bytes actually copied */
+ ret = copy_size - ret;
+ buf->offset += ret;
+ out_offset += ret;
+ out_count -= ret;
+
+ if (buf->len - buf->offset == 0) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ if (!out_count)
+ break;
+ }
+ return out_offset;
+}
+
+/* The condition that must be true for polling to end */
+static bool wait_is_over(struct virtio_console_port *port)
+{
+ return !list_empty(&port->readbuf_head) || !port->host_connected;
+}
+
+static ssize_t virtconsole_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+ ssize_t ret;
+
+ port = filp->private_data;
- /* No buffer? Try to get one. */
- if (!in_len) {
- in = in_vq->vq_ops->get_buf(in_vq, &in_len);
- if (!in)
+ if (list_empty(&port->readbuf_head)) {
+ /*
+ * If nothing's connected on the host just return 0 in
+ * case of list_empty; this tells the userspace app
+ * that there's no connection
+ */
+ if (!port->host_connected)
return 0;
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(port->waitqueue,
+ wait_is_over(port));
+ if (ret < 0)
+ return ret;
}
+ /*
+ * We could've received a disconnection message while we were
+ * waiting for more data.
+ *
+ * This check is not clubbed in the if() statement above as we
+ * might receive some data as well as the host could get
+ * disconnected after we got woken up from our wait. So we
+ * really want to give off whatever data we have and only then
+ * check for host_connected
+ */
+ if (list_empty(&port->readbuf_head) && !port->host_connected)
+ return 0;
- /* You want more than we have to give? Well, try wanting less! */
- if (in_len < count)
- count = in_len;
+ return fill_readbuf(port, ubuf, count, true);
+}
+
+static ssize_t send_buf(struct virtio_console_port *port,
+ const char *in_buf, size_t in_count,
+ u32 flags, bool from_user)
+{
+ struct virtqueue *out_vq;
+ struct virtio_console_port_buffer *buf, *buf2;
+ struct scatterlist sg[1];
+ struct virtio_console_header header;
+ size_t in_offset, copy_size;
+ ssize_t ret;
+ unsigned int header_len;
+
+ if (!in_count)
+ return 0;
+
+ out_vq = virtconsole.out_vq;
+ /*
+ * We should not send internal messages to a host that won't
+ * understand them
+ */
+ if (!use_multiport() && is_internal(flags))
+ return 0;
+ header_len = 0;
+ if (use_multiport()) {
+ header.id = get_id_from_port(port);
+ header.flags = flags;
+ header.size = in_count;
+ header_len = sizeof(header);
+ }
+ in_offset = 0; /* offset in the user buffer */
+ while (in_count - in_offset) {
+ copy_size = min(in_count - in_offset + header_len, PAGE_SIZE);
- /* Copy across to their buffer and increment offset. */
- memcpy(buf, in, count);
- in += count;
- in_len -= count;
+ spin_lock(&virtconsole.write_list_lock);
+ list_for_each_entry_safe(buf, buf2,
+ &virtconsole.unused_write_head,
+ next) {
+ list_del(&buf->next);
+ break;
+ }
+ spin_unlock(&virtconsole.write_list_lock);
+ if (!buf)
+ break;
+ if (header_len) {
+ memcpy(buf->buf, &header, header_len);
+ copy_size -= header_len;
+ }
+ if (from_user)
+ ret = copy_from_user(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ else {
+ /*
+ * Since we're not sure when the host will actually
+ * consume the data and tell us about it, we have
+ * to copy the data here in case the caller
+ * frees the in_buf
+ */
+ memcpy(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ ret = 0; /* Emulate copy_from_user behaviour */
+ }
+ buf->len = header_len + copy_size - ret;
+ sg_init_one(sg, buf->buf, buf->len);
- /* Finished? Re-register buffer so Host will use it again. */
- if (in_len == 0)
- add_inbuf();
+ ret = out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, buf);
+ if (ret < 0) {
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&virtconsole.write_list_lock);
+ list_add_tail(&buf->next,
+ &virtconsole.unused_write_head);
+ spin_unlock(&virtconsole.write_list_lock);
+ break;
+ }
+ in_offset += buf->len - header_len;
+ /*
+ * Only send size with the first buffer. This way
+ * userspace can find out a continuous stream of data
+ * belonging to one write request and consume it
+ * appropriately
+ */
+ header.size = 0;
- return count;
+ /* No space left in the vq anyway */
+ if (!ret)
+ break;
+ }
+ /* Tell Host to go! */
+ out_vq->vq_ops->kick(out_vq);
+
+ /* We're expected to return the amount of data we wrote */
+ return in_offset;
}
-/*:*/
-/*D:320 Console drivers are initialized very early so boot messages can go out,
- * so we do things slightly differently from the generic virtio initialization
- * of the net and block drivers.
+static ssize_t virtconsole_write(struct file *filp, const char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+
+ port = filp->private_data;
+
+ return send_buf(port, ubuf, count, 0, true);
+}
+
+static unsigned int virtconsole_poll(struct file *filp, poll_table *wait)
+{
+ struct virtio_console_port *port;
+ unsigned int ret;
+
+ port = filp->private_data;
+ poll_wait(filp, &port->waitqueue, wait);
+
+ ret = 0;
+ if (!list_empty(&port->readbuf_head))
+ ret |= POLLIN | POLLRDNORM;
+ if (!port->host_connected)
+ ret |= POLLHUP;
+
+ return ret;
+}
+
+static int virtconsole_release(struct inode *inode, struct file *filp)
+{
+ struct virtio_console_control cpkt;
+
+ /* Notify host of port being closed */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 0;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+ return 0;
+}
+
+static int virtconsole_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+
+ port = container_of(cdev, struct virtio_console_port, cdev);
+ filp->private_data = port;
+
+ /* Notify host of port being opened */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 1;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ return 0;
+}
+
+/*
+ * The file operations that we support: programs in the guest can open
+ * a console device, read from it, write to it, poll for data and
+ * close it. The devices are at /dev/vconNN
+ */
+static const struct file_operations virtconsole_fops = {
+ .owner = THIS_MODULE,
+ .open = virtconsole_open,
+ .read = virtconsole_read,
+ .write = virtconsole_write,
+ .poll = virtconsole_poll,
+ .release = virtconsole_release,
+};
+
+
+static ssize_t show_port_name(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ struct virtio_console_port *port;
+
+ port = get_port_from_devt(dev->devt);
+ if (!port || !port->name)
+ return 0;
+
+ return sprintf(buffer, "%s\n", port->name);
+}
+
+static DEVICE_ATTR(name, S_IRUGO, show_port_name, NULL);
+
+static struct attribute *virtcon_sysfs_entries[] = {
+ &dev_attr_name.attr,
+ NULL
+};
+
+static struct attribute_group virtcon_attribute_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = virtcon_sysfs_entries,
+};
+
+
+/*D:310
+ * The cons_put_chars() callback is pretty straightforward.
*
- * At this stage, the console is output-only. It's too early to set up a
- * virtqueue, so we let the drivers do some boutique early-output thing. */
-int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+ * We turn the characters into a scatter-gather list, add it to the output
+ * queue and then kick the Host.
+ *
+ * If the data to be outpu spans more than a page, it's split into
+ * page-sized buffers and then individual buffers are pushed to Host.
+ */
+static int cons_put_chars(u32 vtermno, const char *buf, int count)
{
- virtio_cons.put_chars = put_chars;
- return hvc_instantiate(0, 0, &virtio_cons);
+ struct virtio_console_port *port;
+
+ port = get_port_from_id(vtermno);
+ if (!port)
+ return 0;
+
+ return send_buf(port, buf, count, 0, false);
+}
+
+/*D:350
+ * cons_get_chars() is the callback from the hvc_console
+ * infrastructure when an interrupt is received.
+ *
+ * We call out to fill_readbuf that gets us the required data from the
+ * buffers that are queued up.
+ */
+static int cons_get_chars(u32 vtermno, char *buf, int count)
+{
+ struct virtio_console_port *port;
+
+ /* If we don't have an input queue yet, we can't get input. */
+ BUG_ON(!virtconsole.in_vq);
+
+ port = get_port_from_id(vtermno);
+ if (!port)
+ return 0;
+
+ if (list_empty(&port->readbuf_head))
+ return 0;
+
+ return fill_readbuf(port, buf, count, false);
}
+/*:*/
/*
* virtio console configuration. This supports:
@@ -153,98 +504,579 @@ static void virtcons_apply_config(struct virtio_device *dev)
dev->config->get(dev,
offsetof(struct virtio_console_config, rows),
&ws.ws_row, sizeof(u16));
- hvc_resize(hvc, ws);
+ /*
+ * We'll use this way of resizing only for legacy
+ * support. For newer userspace (VIRTIO_CONSOLE_F_MULTPORT+),
+ * use internal messages to indicate console size
+ * changes so that it can be done per-port
+ */
+ if (!use_multiport())
+ hvc_resize(get_port_from_id(0)->hvc, ws);
}
}
/*
- * we support only one console, the hvc struct is a global var
* We set the configuration at this point, since we now have a tty
*/
-static int notifier_add_vio(struct hvc_struct *hp, int data)
+static int cons_notifier_add_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 1;
- virtcons_apply_config(vdev);
+ virtcons_apply_config(virtconsole.vdev);
return 0;
}
-static void notifier_del_vio(struct hvc_struct *hp, int data)
+static void cons_notifier_del_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 0;
}
-static void hvc_handle_input(struct virtqueue *vq)
+/* The operations for our console. */
+static struct hv_ops virtio_cons = {
+ .get_chars = cons_get_chars,
+ .put_chars = cons_put_chars,
+ .notifier_add = cons_notifier_add_vio,
+ .notifier_del = cons_notifier_del_vio,
+ .notifier_hangup = cons_notifier_del_vio,
+};
+
+/*D:320
+ * Console drivers are initialized very early so boot messages can go out,
+ * so we do things slightly differently from the generic virtio initialization
+ * of the net and block drivers.
+ *
+ * At this stage, the console is output-only. It's too early to set up a
+ * virtqueue, so we let the drivers do some boutique early-output thing.
+ */
+int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
{
- if (hvc_poll(hvc))
- hvc_kick();
+ virtio_cons.put_chars = put_chars;
+ return hvc_instantiate(0, 0, &virtio_cons);
}
-/*D:370 Once we're further in boot, we get probed like any other virtio device.
- * At this stage we set up the output virtqueue.
+int init_port_console(struct virtio_console_port *port)
+{
+ int ret = 0;
+
+ /*
+ * The Host's telling us this port is a console port. Hook it
+ * up with an hvc console.
+ *
+ * To set up and manage our virtual console, we call
+ * hvc_alloc().
+ *
+ * The first argument of hvc_alloc() is the virtual console
+ * number. hvc_alloc() checks if a console with the same value
+ * was used in hvc_instantiate(). We may not end up passing
+ * the same value here (we use the value 0 in
+ * hvc_instantiate() but the console port may not be at id
+ * 0). This isn't a problem, though. Nothing in hvc really
+ * depends on these numbers and since this number is passed on
+ * to us when cons_get/put_chars() is called, it's preferable
+ * to pass on the port->id so that we can get the port struct
+ * via get_port_from_id().
+ *
+ * The second argument is the parameter for the notification
+ * mechanism (like irq number). We currently leave this as
+ * zero, virtqueues have implicit notifications.
+ *
+ * The third argument is a "struct hv_ops" containing the
+ * put_chars() get_chars(), notifier_add() and notifier_del()
+ * pointers. The final argument is the output buffer size: we
+ * can do any size, so we put PAGE_SIZE here.
+ */
+ port->hvc = hvc_alloc(port->id, 0, &virtio_cons, PAGE_SIZE);
+ if (IS_ERR(port->hvc)) {
+ ret = PTR_ERR(port->hvc);
+ pr_err("%s: Could not alloc hvc for virtio console port, ret = %d\n",
+ __func__, ret);
+ port->hvc = NULL;
+ }
+ return ret;
+}
+
+/* Any secret messages that the Host and Guest want to share */
+static void handle_control_message(struct virtio_console_port *port,
+ struct virtio_console_port_buffer *buf)
+{
+ struct virtio_console_control *cpkt;
+ size_t name_size;
+
+ cpkt = (struct virtio_console_control *)(buf->buf + buf->offset);
+
+ switch (cpkt->event) {
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->host_connected = cpkt->value;
+ break;
+ case VIRTIO_CONSOLE_PORT_NAME:
+ /*
+ * Skip the size of the header and the cpkt to get the size
+ * of the name that was sent
+ */
+ name_size = buf->len - buf->offset - sizeof(*cpkt) + 1;
+
+ port->name = kmalloc(name_size, GFP_KERNEL);
+ if (!port->name) {
+ pr_err("%s: not enough space to store port name\n",
+ __func__);
+ break;
+ }
+ strncpy(port->name, buf->buf + buf->offset + sizeof(*cpkt),
+ name_size - 1);
+ port->name[name_size - 1] = 0;
+ break;
+ case VIRTIO_CONSOLE_CONSOLE_PORT:
+ if (!cpkt->value)
+ break;
+ init_port_console(port);
+ /*
+ * Could remove the port here in case init fails - but
+ * have to notify the host first
+ */
+ break;
+ }
+}
+
+
+static struct virtio_console_port_buffer *get_buf(size_t buf_size)
+{
+ struct virtio_console_port_buffer *buf;
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ goto out;
+ buf->buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!buf->buf) {
+ kfree(buf);
+ goto out;
+ }
+ buf->len = buf_size;
+out:
+ return buf;
+}
+
+static void fill_queue(struct virtqueue *vq, size_t buf_size,
+ struct list_head *unused_head)
+{
+ struct scatterlist sg[1];
+ struct virtio_console_port_buffer *buf;
+ int ret;
+
+ do {
+ buf = get_buf(buf_size);
+ if (!buf)
+ break;
+ sg_init_one(sg, buf->buf, buf_size);
+
+ ret = vq->vq_ops->add_buf(vq, sg, 0, 1, buf);
+ if (ret < 0) {
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ /* We have to keep track of the unused buffers
+ * so that they can be freed when the module
+ * is being removed
+ */
+ list_add_tail(&buf->next, unused_head);
+ } while (ret > 0);
+ vq->vq_ops->kick(vq);
+}
+
+static void fill_receive_queue(struct virtio_console_struct *vcon)
+{
+ fill_queue(vcon->in_vq, PAGE_SIZE, &vcon->unused_read_head);
+}
+
+/*
+ * This function is only called from the init routine so the spinlock
+ * for the unused_write_head list isn't taken
+ */
+static void alloc_write_bufs(struct virtio_console_struct *vcon)
+{
+ struct virtio_console_port_buffer *buf;
+ int i;
+
+ for (i = 0; i < 1024; i++) {
+ buf = get_buf(PAGE_SIZE);
+ if (!buf)
+ break;
+ list_add_tail(&buf->next, &vcon->unused_write_head);
+ }
+}
+
+/*
+ * The workhandler for any buffers that appear on our input queue.
+ * Pick the buffer; if it's some internal communication meant for the
+ * us, just process it. Otherwise queue it up for the read() or
+ * get_chars() routines to pick the data up later.
+ */
+static void virtio_console_rx_work_handler(struct work_struct *work)
+{
+ struct virtio_console_struct *vcon;
+ struct virtio_console_port *port;
+ struct virtio_console_port_buffer *buf;
+ struct virtio_console_header header;
+ struct virtqueue *vq;
+ unsigned int tmplen, header_len;
+
+ vcon = container_of(work, struct virtio_console_struct, rx_work);
+ header_len = use_multiport() ? sizeof(header) : 0;
+
+ port = NULL;
+ vq = vcon->in_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* The buffer is no longer unused */
+ list_del(&buf->next);
+
+ if (use_multiport()) {
+ memcpy(&header, buf->buf, header_len);
+ port = get_port_from_id(header.id);
+ } else
+ port = get_port_from_id(0);
+ if (!port) {
+ /* No valid header at start of buffer. Drop it. */
+ pr_debug("%s: invalid index in buffer, %c %d\n",
+ __func__, buf->buf[0], buf->buf[0]);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free / alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ buf->len = tmplen;
+ buf->offset = header_len;
+ if (use_multiport() && is_internal(header.flags)) {
+ handle_control_message(port, buf);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free/alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ } else {
+ list_add_tail(&buf->next, &port->readbuf_head);
+ /*
+ * We might have missed a connection
+ * notification, e.g. before the queues were
+ * initialised.
+ */
+ port->host_connected = true;
+ }
+ wake_up_interruptible(&port->waitqueue);
+ }
+ if (port) {
+ if(is_console_port(port) && !list_empty(&port->readbuf_head))
+ if (hvc_poll(port->hvc))
+ hvc_kick();
+ /* Allocate buffers for all the ones that got used up */
+ fill_receive_queue(port->vcon);
+ }
+}
+
+/*
+ * This is the workhandler for buffers that get received on the output
+ * virtqueue, which is an indication that Host consumed the data we
+ * sent it. Since all our buffers going out are of a fixed size we can
+ * just reuse them instead of freeing them and allocating new ones.
*
- * To set up and manage our virtual console, we call hvc_alloc(). Since we
- * never remove the console device we never need this pointer again.
+ * Zero out the buffer so that we don't leak any information from
+ * other processes. There's a small optimisation here as well: the
+ * buffers are PAGE_SIZE-sized; but instead of zeroing the entire
+ * page, we just zero the length that was most recently used and we
+ * can be sure the rest of the page is already set to 0s.
*
- * Finally we put our input buffer in the input queue, ready to receive. */
-static int __devinit virtcons_probe(struct virtio_device *dev)
+ * So once we zero them out we add them back to the unused buffers
+ * list
+ */
+static void virtio_console_tx_work_handler(struct work_struct *work)
{
- vq_callback_t *callbacks[] = { hvc_handle_input, NULL};
+ struct virtio_console_struct *vcon;
+ struct virtqueue *vq;
+ struct virtio_console_port_buffer *buf;
+ unsigned int tmplen;
+
+ vcon = container_of(work, struct virtio_console_struct, tx_work);
+
+ vq = vcon->out_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* 0 the buffer to not leak data from other processes */
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&vcon->write_list_lock);
+ list_add_tail(&buf->next, &vcon->unused_write_head);
+ spin_unlock(&vcon->write_list_lock);
+ }
+}
+
+static void rx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.rx_work);
+}
+
+static void tx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.tx_work);
+}
+
+static void config_intr(struct virtio_device *vdev)
+{
+ /* Handle port hot-add */
+ schedule_work(&virtconsole.config_work);
+
+ /* Handle console size changes */
+ virtcons_apply_config(vdev);
+}
+
+static int virtconsole_add_port(u32 port_nr)
+{
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+ dev_t devt;
+ int ret;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->vcon = &virtconsole;
+ port->id = port_nr;
+
+ cdev_init(&port->cdev, &virtconsole_fops);
+
+ ret = alloc_chrdev_region(&devt, 0, 1, "virtio-console");
+ if (ret < 0) {
+ pr_err("%s: error allocing chrdev region, ret = %d\n",
+ __func__, ret);
+ goto free_port;
+ }
+ ret = cdev_add(&port->cdev, devt, 1);
+ if (ret < 0) {
+ pr_err("%s: error adding cdev, ret = %d\n", __func__, ret);
+ goto free_chrdev;
+ }
+ port->dev = device_create(port->vcon->class, NULL, devt, NULL,
+ "vcon%u", port_nr);
+ if (IS_ERR(port->dev)) {
+ ret = PTR_ERR(port->dev);
+ pr_err("%s: error creating device, ret = %d\n", __func__, ret);
+ goto free_cdev;
+ }
+ ret = sysfs_create_group(&port->dev->kobj, &virtcon_attribute_group);
+ if (ret) {
+ pr_err("%s: error creating sysfs device attributes, ret = %d\n",
+ __func__, ret);
+ goto free_cdev;
+ }
+
+ INIT_LIST_HEAD(&port->readbuf_head);
+ init_waitqueue_head(&port->waitqueue);
+
+ list_add_tail(&port->next, &port->vcon->port_head);
+
+ /*
+ * Ask for the port's name from Host. The string that we
+ * receive in 'name' can be of arbitrary length; so pass the
+ * maximum available buffer size: PAGE_SIZE.
+ */
+ cpkt.event = VIRTIO_CONSOLE_PORT_NAME;
+ send_buf(port, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ /*
+ * If we're not using multiport support, this has to be a console port
+ */
+ if (!use_multiport()) {
+ ret = init_port_console(port);
+ if (ret)
+ goto free_cdev;
+ }
+
+ pr_debug("virtio-console port found at id %u\n", port_nr);
+
+ return 0;
+free_cdev:
+ cdev_del(&port->cdev);
+free_chrdev:
+ unregister_chrdev_region(devt, 1);
+free_port:
+ kfree(port);
+ return ret;
+}
+
+
+/* The workhandler for config-space updates
+ *
+ * This is used when new ports are added
+ */
+static void virtio_console_config_work_handler(struct work_struct *work)
+{
+ struct virtio_console_struct *vcon;
+ struct virtio_console_config virtconconf;
+ struct virtio_device *vdev;
+ u32 i;
+ int ret;
+
+ vcon = container_of(work, struct virtio_console_struct, config_work);
+
+ vdev = vcon->vdev;
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config, nr_active_ports),
+ &virtconconf.nr_active_ports,
+ sizeof(virtconconf.nr_active_ports));
+
+ /* Hot-add ports */
+ for (i = virtconsole.config.nr_active_ports;
+ i < virtconconf.nr_active_ports; i++) {
+ ret = virtconsole_add_port(virtconsole.config.nr_active_ports + i);
+ if (!ret)
+ virtconsole.config.nr_active_ports++;
+ }
+}
+
+/*D:370
+ * Once we're further in boot, we get probed like any other virtio device.
+ * At this stage we set up the output virtqueue.
+ *
+ * Finally we put our input buffer in the input queue, ready to receive.
+ */
+static int __devinit virtcons_probe(struct virtio_device *vdev)
+{
+ vq_callback_t *callbacks[] = { rx_intr, tx_intr };
const char *names[] = { "input", "output" };
struct virtqueue *vqs[2];
- int err;
+ u32 i;
+ int ret;
+ bool multiport;
- vdev = dev;
-
- /* This is the scratch page we use to receive console input */
- inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!inbuf) {
- err = -ENOMEM;
- goto fail;
+ if (virtconsole.vdev) {
+ pr_err("Multiple virtio-console devices not supported yet\n");
+ return -EEXIST;
}
+ virtconsole.vdev = vdev;
+ multiport = false;
+ if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT)) {
+ multiport = true;
+ vdev->features[0] |= 1 << VIRTIO_CONSOLE_F_MULTIPORT;
+ vdev->config->finalize_features(vdev);
+
+ vdev->config->get(vdev, offsetof(struct virtio_console_config,
+ nr_active_ports),
+ &virtconsole.config.nr_active_ports,
+ sizeof(virtconsole.config.nr_active_ports));
+ }
/* Find the queues. */
/* FIXME: This is why we want to wean off hvc: we do nothing
* when input comes in. */
- err = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
- if (err)
- goto free;
+ ret = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
+ if (ret)
+ goto fail;
- in_vq = vqs[0];
- out_vq = vqs[1];
+ virtconsole.in_vq = vqs[0];
+ virtconsole.out_vq = vqs[1];
- /* Start using the new console output. */
- virtio_cons.get_chars = get_chars;
- virtio_cons.put_chars = put_chars;
- virtio_cons.notifier_add = notifier_add_vio;
- virtio_cons.notifier_del = notifier_del_vio;
- virtio_cons.notifier_hangup = notifier_del_vio;
-
- /* The first argument of hvc_alloc() is the virtual console number, so
- * we use zero. The second argument is the parameter for the
- * notification mechanism (like irq number). We currently leave this
- * as zero, virtqueues have implicit notifications.
- *
- * The third argument is a "struct hv_ops" containing the put_chars()
- * get_chars(), notifier_add() and notifier_del() pointers.
- * The final argument is the output buffer size: we can do any size,
- * so we put PAGE_SIZE here. */
- hvc = hvc_alloc(0, 0, &virtio_cons, PAGE_SIZE);
- if (IS_ERR(hvc)) {
- err = PTR_ERR(hvc);
- goto free_vqs;
+ /*
+ * We had set the virtio_cons put_chars implementation to
+ * put_chars for early_init. Now that we're done with the
+ * early init phase, replace it with our cons_put_chars
+ * implementation.
+ */
+ virtio_cons.put_chars = cons_put_chars;
+
+ INIT_LIST_HEAD(&virtconsole.port_head);
+ INIT_LIST_HEAD(&virtconsole.unused_read_head);
+ INIT_LIST_HEAD(&virtconsole.unused_write_head);
+
+ INIT_WORK(&virtconsole.rx_work, &virtio_console_rx_work_handler);
+ INIT_WORK(&virtconsole.tx_work, &virtio_console_tx_work_handler);
+ INIT_WORK(&virtconsole.config_work, &virtio_console_config_work_handler);
+ spin_lock_init(&virtconsole.write_list_lock);
+
+ fill_receive_queue(&virtconsole);
+ alloc_write_bufs(&virtconsole);
+
+ virtconsole_add_port(0);
+ if (multiport)
+ for (i = 1; i < virtconsole.config.nr_active_ports; i++)
+ virtconsole_add_port(i);
+
+ return 0;
+
+fail:
+ return ret;
+}
+
+/*
+ * Remove port-specific data.
+ * In case the port can't be removed, return non-zero. This could
+ * then be used in the port hot-unplug case.
+ */
+static int virtcons_remove_port_data(struct virtio_console_port *port)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+
+ if (is_console_port(port)) {
+ /* hvc_console is compiled in, at least on Fedora. */
+ /* hvc_remove(hvc); */
+ return 1;
}
- /* Register the input buffer the first time. */
- add_inbuf();
+ sysfs_remove_group(&port->dev->kobj, &virtcon_attribute_group);
+ device_destroy(virtconsole.class, port->dev->devt);
+ unregister_chrdev_region(port->dev->devt, 1);
+ cdev_del(&port->cdev);
+
+ kfree(port->name);
+
+ /* Remove the buffers in which we have unconsumed data */
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
return 0;
+}
+
+static void virtcons_remove(struct virtio_device *vdev)
+{
+ struct virtio_console_port *port, *port2;
+ struct virtio_console_port_buffer *buf, *buf2;
+ char *tmpbuf;
+ int len;
+
+ class_destroy(virtconsole.class);
+
+ cancel_work_sync(&virtconsole.rx_work);
+ /*
+ * Free up the buffers that we queued up for the Host to pass
+ * us data
+ */
+ while ((tmpbuf = virtconsole.in_vq->vq_ops->get_buf(virtconsole.in_vq,
+ &len)))
+ kfree(tmpbuf);
-free_vqs:
vdev->config->del_vqs(vdev);
-free:
- kfree(inbuf);
-fail:
- return err;
+ /*
+ * Free up the buffers that were sent to us by Host but were
+ * left unused
+ */
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_read_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_write_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(port, port2, &virtconsole.port_head, next) {
+ list_del(&port->next);
+ virtcons_remove_port_data(port);
+ kfree(port);
+ }
}
static struct virtio_device_id id_table[] = {
@@ -254,6 +1086,7 @@ static struct virtio_device_id id_table[] = {
static unsigned int features[] = {
VIRTIO_CONSOLE_F_SIZE,
+ VIRTIO_CONSOLE_F_MULTIPORT,
};
static struct virtio_driver virtio_console = {
@@ -263,14 +1096,34 @@ static struct virtio_driver virtio_console = {
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtcons_probe,
- .config_changed = virtcons_apply_config,
+ .remove = virtcons_remove,
+ .config_changed = config_intr,
};
static int __init init(void)
{
- return register_virtio_driver(&virtio_console);
+ int ret;
+
+ virtconsole.class = class_create(THIS_MODULE, "virtio-console");
+ if (IS_ERR(virtconsole.class)) {
+ pr_err("Error creating virtio-console class\n");
+ ret = PTR_ERR(virtconsole.class);
+ return ret;
+ }
+ ret = register_virtio_driver(&virtio_console);
+ if (ret) {
+ class_destroy(virtconsole.class);
+ return ret;
+ }
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ unregister_virtio_driver(&virtio_console);
}
module_init(init);
+module_exit(fini);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio console driver");
diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h
index b5f5198..96bb6f0 100644
--- a/include/linux/virtio_console.h
+++ b/include/linux/virtio_console.h
@@ -2,19 +2,63 @@
#define _LINUX_VIRTIO_CONSOLE_H
#include <linux/types.h>
#include <linux/virtio_config.h>
-/* This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
- * anyone can use the definitions to implement compatible drivers/servers. */
+/*
+ * This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
+ * anyone can use the definitions to implement compatible drivers/servers.
+ *
+ * Copyright (C) Red Hat, Inc., 2009
+ */
/* Feature bits */
#define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */
+#define VIRTIO_CONSOLE_F_MULTIPORT 1 /* Does host provide multiple ports? */
+
+#define VIRTIO_CONSOLE_BAD_ID (~(u32)0) /* Invalid port number */
struct virtio_console_config {
/* colums of the screens */
__u16 cols;
/* rows of the screens */
__u16 rows;
+ /* number of ports in use */
+ __u32 nr_active_ports;
+} __attribute__((packed));
+
+
+/*
+ * An internal-only message that's passed between the Host and the
+ * Guest for a particular port.
+ */
+struct virtio_console_control {
+ __u16 event;
+ __u16 value;
+};
+
+/* Some events for internal messages (control packets) */
+#define VIRTIO_CONSOLE_PORT_OPEN 0
+#define VIRTIO_CONSOLE_PORT_NAME 1
+#define VIRTIO_CONSOLE_CONSOLE_PORT 2
+
+
+/*
+ * This struct is put in each buffer that gets passed to userspace and
+ * vice-versa
+ */
+struct virtio_console_header {
+ /* Port number */
+ u32 id;
+ /* Some message between host and guest */
+ u32 flags;
+ /*
+ * Complete size of the write request - only sent with the
+ * first buffer for each write request
+ */
+ u32 size;
} __attribute__((packed));
+/* Messages between host and guest ('flags' field in the header above) */
+#define VIRTIO_CONSOLE_ID_INTERNAL (1 << 0)
+
#ifdef __KERNEL__
int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int));
Am Mittwoch 30 September 2009 19:13:21 schrieb Amit Shah:
> On (Tue) Sep 29 2009 [15:31:23], Christian Borntraeger wrote:
> > Am Dienstag 29 September 2009 15:09:50 schrieb Amit Shah:
> > > Great, thanks. However I was thinking of moving this init to the
> > > probe() routine instead of in the init_conosle routine just because
> > > multiple consoles can be added and we don't want to init this each
> > > time.. just once in probe is fine.
> >
> > If you have new patch CC me and I can give it a spin.
>
> Hey Christian,
>
> I have a new patch that changes a few things:
> - moves the put_char fix to probe instead of doing it in
> init_port_console(), which gets called on each console port found.
> - uses port->id instead of a static hvc_vtermno to pass on a value to
> hvc_alloc(). Motivation explained within comments in the code.
> - A few other changes that introduce and make use of port->vcon instead
> of accessing the static virtconsole directly -- aimed at easing future
> fix to have multiple virtio-console devices.
>
> It would be great if you could test this.
The basic stuff seems to work, but when I did a console resize I get:
<0>------------[ cut here ]------------
<2>kernel BUG at drivers/s390/kvm/kvm_virtio.c:117!
<4>illegal operation: 0001 [#1] SMP
<4>Modules linked in: dm_multipath scsi_dh sunrpc ipv6 binfmt_misc dm_mod virtio_rng rng_core
<4>CPU: 0 Not tainted 2.6.31-selfgit-08936-g851b147-dirty #172
<4>Process events/0 (pid: 19, task: 000000000c4f3040, ksp: 000000000c305cc8)
<4>Krnl PSW : 0704200180000000 0000000000460bf2 (kvm_get+0x86/0x90)
<4> R:0 T:1 IO:1 EX:1 Key:0 M:1 W:0 P:0 AS:0 CC:2 PM:0 EA:3
<4>Krnl GPRS: 0000000300000001 0000000000000004 0000000000000008 0000000000000004
<4> 000000000c305e2c 0000000000000004 0000000000000000 0000000000c56308
<4> 00000000003b863c 000000000c4f3040 0000000000460b6c 0000000000903e28
<4> 000000000c800000 0000000000525a90 00000000003b8682 000000000c305d88
<4>Krnl Code: 0000000000460be6: a737fff9 brctg %r3,460bd8
<4> 0000000000460bea: a7f4ffee brc 15,460bc6
<4> 0000000000460bee: a7f40001 brc 15,460bf0
<4> >0000000000460bf2: a7f40000 brc 15,460bf2
<4> 0000000000460bf6: d20040001000 mvc 0(1,%r4),0(%r1)
<4> 0000000000460bfc: ebaff0680024 stmg %r10,%r15,104(%r15)
<4> 0000000000460c02: c0d000081b7f larl %r13,564300
<4> 0000000000460c08: a7f11fc0 tmll %r15,8128
<4>Call Trace:
<4>([<0000000000000000>] 0x0)
<4> [<000000000015d43e>] worker_thread+0x1ea/0x2d0
<4> [<0000000000163666>] kthread+0x9a/0xa4
<4> [<000000000010a212>] kernel_thread_starter+0x6/0xc
<4> [<000000000010a20c>] kernel_thread_starter+0x0/0xc
<4>Last Breaking-Event-Address:
<4> [<0000000000460bee>] kvm_get+0x82/0x90
<4>
One of the config->get() related changes is probably wrong.
On (Thu) Oct 01 2009 [10:17:29], Christian Borntraeger wrote:
> Am Mittwoch 30 September 2009 19:13:21 schrieb Amit Shah:
> > On (Tue) Sep 29 2009 [15:31:23], Christian Borntraeger wrote:
> > > Am Dienstag 29 September 2009 15:09:50 schrieb Amit Shah:
> > > > Great, thanks. However I was thinking of moving this init to the
> > > > probe() routine instead of in the init_conosle routine just because
> > > > multiple consoles can be added and we don't want to init this each
> > > > time.. just once in probe is fine.
> > >
> > > If you have new patch CC me and I can give it a spin.
> >
> > Hey Christian,
> >
> > I have a new patch that changes a few things:
> > - moves the put_char fix to probe instead of doing it in
> > init_port_console(), which gets called on each console port found.
> > - uses port->id instead of a static hvc_vtermno to pass on a value to
> > hvc_alloc(). Motivation explained within comments in the code.
> > - A few other changes that introduce and make use of port->vcon instead
> > of accessing the static virtconsole directly -- aimed at easing future
> > fix to have multiple virtio-console devices.
> >
> > It would be great if you could test this.
>
> The basic stuff seems to work, but when I did a console resize I get:
...
> One of the config->get() related changes is probably wrong.
I see it. The following should fix it.
Amit
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 30361c1..b2de2df 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -822,9 +822,10 @@ static void tx_intr(struct virtqueue *vq)
static void config_intr(struct virtio_device *vdev)
{
- /* Handle port hot-add */
- schedule_work(&virtconsole.config_work);
-
+ if (use_multiport()) {
+ /* Handle port hot-add */
+ schedule_work(&virtconsole.config_work);
+ }
/* Handle console size changes */
virtcons_apply_config(vdev);
}
Am Donnerstag 01 Oktober 2009 10:47:37 schrieb Amit Shah:
> > > It would be great if you could test this.
> >
> > The basic stuff seems to work, but when I did a console resize I get:
>
> ...
>
> > One of the config->get() related changes is probably wrong.
>
> I see it. The following should fix it.
Yes, it does.
Am Mittwoch 30 September 2009 19:13:21 schrieb Amit Shah:
> - uses port->id instead of a static hvc_vtermno to pass on a value to
> hvc_alloc(). Motivation explained within comments in the code.
[...]
> + * The first argument of hvc_alloc() is the virtual console
> + * number. hvc_alloc() checks if a console with the same value
> + * was used in hvc_instantiate(). We may not end up passing
> + * the same value here (we use the value 0 in
> + * hvc_instantiate() but the console port may not be at id
> + * 0). This isn't a problem, though. Nothing in hvc really
> + * depends on these numbers and since this number is passed on
> + * to us when cons_get/put_chars() is called, it's preferable
> + * to pass on the port->id so that we can get the port struct
> + * via get_port_from_id().
[...]
> + port->hvc = hvc_alloc(port->id, 0, &virtio_cons, PAGE_SIZE);
I am not sure if this is going to be ok.
When I change the old code (without your patch) to use vtermno=0 in
hvc_instantiate and vtermno=1 in hvc_alloc I get
"Warning: unable to open an initial console."
It seems to me that we have to pass the same values.
Christian
On (Thu) Oct 01 2009 [11:00:48], Christian Borntraeger wrote:
> Am Mittwoch 30 September 2009 19:13:21 schrieb Amit Shah:
> > - uses port->id instead of a static hvc_vtermno to pass on a value to
> > hvc_alloc(). Motivation explained within comments in the code.
>
> [...]
> > + * The first argument of hvc_alloc() is the virtual console
> > + * number. hvc_alloc() checks if a console with the same value
> > + * was used in hvc_instantiate(). We may not end up passing
> > + * the same value here (we use the value 0 in
> > + * hvc_instantiate() but the console port may not be at id
> > + * 0). This isn't a problem, though. Nothing in hvc really
> > + * depends on these numbers and since this number is passed on
> > + * to us when cons_get/put_chars() is called, it's preferable
> > + * to pass on the port->id so that we can get the port struct
> > + * via get_port_from_id().
> [...]
> > + port->hvc = hvc_alloc(port->id, 0, &virtio_cons, PAGE_SIZE);
>
> I am not sure if this is going to be ok.
>
> When I change the old code (without your patch) to use vtermno=0 in
> hvc_instantiate and vtermno=1 in hvc_alloc I get
> "Warning: unable to open an initial console."
>
> It seems to me that we have to pass the same values.
OK; that's bad enough. The following diff reverts that patch and adds a
new get_port_from_vtermno() that works from hvc callbacks.
What do you think? (Applies on top of the latest patch I sent.)
Amit
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index b2de2df..a8ba5aa 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -105,12 +105,42 @@ struct virtio_console_port {
/* The 'id' to identify the port with the Host */
u32 id;
+ /*
+ * If this port is a console port, this number identifies the
+ * number that we used to register with hvc in
+ * hvc_instantiate() and hvc_alloc().
+ */
+ u32 vtermno;
+
/* Is the host device open */
bool host_connected;
};
static struct virtio_console_struct virtconsole;
+/*
+ * This is used to keep track of the number of hvc consoles spawned.
+ * This number is given as first argument to hvc_alloc(). We could as
+ * well pass on the minor number of the char device but to correctly
+ * map an initial console spawned via hvc_instantiate to the console
+ * being hooked up via hvc_alloc, we need to pass the same vtermno.
+ *
+ * With this int, we just assume the first console being initialised
+ * was the first one that got used as the initial console.
+ */
+static unsigned int hvc_vtermno;
+
+static struct virtio_console_port *get_port_from_vtermno(u32 vtermno)
+{
+ struct virtio_console_port *port;
+
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (port->vtermno == vtermno)
+ return port;
+ }
+ return NULL;
+}
+
static struct virtio_console_port *get_port_from_devt(dev_t devt)
{
struct virtio_console_port *port;
@@ -457,7 +487,7 @@ static int cons_put_chars(u32 vtermno, const char *buf, int count)
{
struct virtio_console_port *port;
- port = get_port_from_id(vtermno);
+ port = get_port_from_vtermno(vtermno);
if (!port)
return 0;
@@ -478,7 +508,7 @@ static int cons_get_chars(u32 vtermno, char *buf, int count)
/* If we don't have an input queue yet, we can't get input. */
BUG_ON(!virtconsole.in_vq);
- port = get_port_from_id(vtermno);
+ port = get_port_from_vtermno(vtermno);
if (!port)
return 0;
@@ -566,32 +596,23 @@ int init_port_console(struct virtio_console_port *port)
* hvc_alloc().
*
* The first argument of hvc_alloc() is the virtual console
- * number. hvc_alloc() checks if a console with the same value
- * was used in hvc_instantiate(). We may not end up passing
- * the same value here (we use the value 0 in
- * hvc_instantiate() but the console port may not be at id
- * 0). This isn't a problem, though. Nothing in hvc really
- * depends on these numbers and since this number is passed on
- * to us when cons_get/put_chars() is called, it's preferable
- * to pass on the port->id so that we can get the port struct
- * via get_port_from_id().
- *
- * The second argument is the parameter for the notification
- * mechanism (like irq number). We currently leave this as
- * zero, virtqueues have implicit notifications.
+ * number. The second argument is the parameter for the
+ * notification mechanism (like irq number). We currently
+ * leave this as zero, virtqueues have implicit notifications.
*
* The third argument is a "struct hv_ops" containing the
* put_chars() get_chars(), notifier_add() and notifier_del()
* pointers. The final argument is the output buffer size: we
* can do any size, so we put PAGE_SIZE here.
*/
- port->hvc = hvc_alloc(port->id, 0, &virtio_cons, PAGE_SIZE);
+ port->hvc = hvc_alloc(hvc_vtermno, 0, &virtio_cons, PAGE_SIZE);
if (IS_ERR(port->hvc)) {
ret = PTR_ERR(port->hvc);
pr_err("%s: Could not alloc hvc for virtio console port, ret = %d\n",
__func__, ret);
port->hvc = NULL;
}
+ port->vtermno = hvc_vtermno++;
return ret;
}
Am Mittwoch 30 September 2009 19:13:21 schrieben Sie:
> On (Tue) Sep 29 2009 [15:31:23], Christian Borntraeger wrote:
> > Am Dienstag 29 September 2009 15:09:50 schrieb Amit Shah:
> > > Great, thanks. However I was thinking of moving this init to the
> > > probe() routine instead of in the init_conosle routine just because
> > > multiple consoles can be added and we don't want to init this each
> > > time.. just once in probe is fine.
> >
> > If you have new patch CC me and I can give it a spin.
>
> Hey Christian,
>
> I have a new patch that changes a few things:
> - moves the put_char fix to probe instead of doing it in
> init_port_console(), which gets called on each console port found.
> - uses port->id instead of a static hvc_vtermno to pass on a value to
> hvc_alloc(). Motivation explained within comments in the code.
> - A few other changes that introduce and make use of port->vcon instead
> of accessing the static virtconsole directly -- aimed at easing future
> fix to have multiple virtio-console devices.
>
> It would be great if you could test this.
With the latest git kernel + your patch I sometmes get a completely frozen
console. In the dump there is
<3>virtio_console virtio0: output:id 68 is not a head!
Seems that I can reproduce it with large amounts of output (find / for example)
Without your patch everything is fine.
Christian
On (Thu) Oct 01 2009 [12:28:30], Christian Borntraeger wrote:
> Am Mittwoch 30 September 2009 19:13:21 schrieben Sie:
> > On (Tue) Sep 29 2009 [15:31:23], Christian Borntraeger wrote:
> > > Am Dienstag 29 September 2009 15:09:50 schrieb Amit Shah:
> > > > Great, thanks. However I was thinking of moving this init to the
> > > > probe() routine instead of in the init_conosle routine just because
> > > > multiple consoles can be added and we don't want to init this each
> > > > time.. just once in probe is fine.
> > >
> > > If you have new patch CC me and I can give it a spin.
> >
> > Hey Christian,
> >
> > I have a new patch that changes a few things:
> > - moves the put_char fix to probe instead of doing it in
> > init_port_console(), which gets called on each console port found.
> > - uses port->id instead of a static hvc_vtermno to pass on a value to
> > hvc_alloc(). Motivation explained within comments in the code.
> > - A few other changes that introduce and make use of port->vcon instead
> > of accessing the static virtconsole directly -- aimed at easing future
> > fix to have multiple virtio-console devices.
> >
> > It would be great if you could test this.
>
> With the latest git kernel + your patch I sometmes get a completely frozen
> console. In the dump there is
>
> <3>virtio_console virtio0: output:id 68 is not a head!
>
> Seems that I can reproduce it with large amounts of output (find / for example)
> Without your patch everything is fine.
Do you mean the entire patch? Or just the last snippet that I sent?
I'm testing things using a few days old linux-next; I guess you're
seeing this with Linus' git tree?
I'll try that.
Amit
On (Thu) Oct 01 2009 [12:28:30], Christian Borntraeger wrote:
> Am Mittwoch 30 September 2009 19:13:21 schrieben Sie:
> > On (Tue) Sep 29 2009 [15:31:23], Christian Borntraeger wrote:
> > > Am Dienstag 29 September 2009 15:09:50 schrieb Amit Shah:
> > > > Great, thanks. However I was thinking of moving this init to the
> > > > probe() routine instead of in the init_conosle routine just because
> > > > multiple consoles can be added and we don't want to init this each
> > > > time.. just once in probe is fine.
> > >
> > > If you have new patch CC me and I can give it a spin.
> >
> > Hey Christian,
> >
> > I have a new patch that changes a few things:
> > - moves the put_char fix to probe instead of doing it in
> > init_port_console(), which gets called on each console port found.
> > - uses port->id instead of a static hvc_vtermno to pass on a value to
> > hvc_alloc(). Motivation explained within comments in the code.
> > - A few other changes that introduce and make use of port->vcon instead
> > of accessing the static virtconsole directly -- aimed at easing future
> > fix to have multiple virtio-console devices.
> >
> > It would be great if you could test this.
>
> With the latest git kernel + your patch I sometmes get a completely frozen
> console. In the dump there is
>
> <3>virtio_console virtio0: output:id 68 is not a head!
>
> Seems that I can reproduce it with large amounts of output (find / for example)
> Without your patch everything is fine.
I spawned two ports; one doing 'find /' and the other transferring a
800M file from the host to the guest, both running at the same time.
This is on top of the latest Linus tree + my patches. It works fine.
Can you give me any additional info that can help me nail this down?
Amit
Am Donnerstag 01 Oktober 2009 13:58:01 schrieben Sie:
> I spawned two ports; one doing 'find /' and the other transferring a
> 800M file from the host to the guest, both running at the same time.
> This is on top of the latest Linus tree + my patches. It works fine.
>
> Can you give me any additional info that can help me nail this down?
This is just a wild guess: My guest has 8 cpus and virtio requires that users do
a proper locking. But as far as I see you use schedule_work which should be
single threaded.
Or maybe this is an existing bug that is just exposed by your patch - dont know.
On (Thu) Oct 01 2009 [14:04:37], Christian Borntraeger wrote:
> Am Donnerstag 01 Oktober 2009 13:58:01 schrieben Sie:
> > I spawned two ports; one doing 'find /' and the other transferring a
> > 800M file from the host to the guest, both running at the same time.
> > This is on top of the latest Linus tree + my patches. It works fine.
> >
> > Can you give me any additional info that can help me nail this down?
>
> This is just a wild guess: My guest has 8 cpus and virtio requires that users do
> a proper locking. But as far as I see you use schedule_work which should be
> single threaded.
> Or maybe this is an existing bug that is just exposed by your patch - dont know.
I think there might be two bugs. One is the readbuf_head list needs
some locks around its operations. The readbuf_head list is a per-port
list where buffers received from the host for a port are stored.
They're taken off that list when userspace issues read() requests.
The other bug is the virtio one that you saw. I'll try to see how that
one gets triggered.
Thanks,
Amit
On (Thu) Oct 01 2009 [12:28:30], Christian Borntraeger wrote:
>
> With the latest git kernel + your patch I sometmes get a completely frozen
> console. In the dump there is
>
> <3>virtio_console virtio0: output:id 68 is not a head!
>
> Seems that I can reproduce it with large amounts of output (find / for example)
> Without your patch everything is fine.
Hey Christian,
Can you try this patch?
There's something funny happening with hvc still, though. It sometimes
doesn't respond to input which could mean a race somewhere. When it
fails, it doesn't show up a Password: prompt on the terminal (but
accepts and echoes the username fine). When it does work, however,
everything seems fine.
It would be great if you could try this a few times on your setup.
Amit
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 08a6f50..fc8a04e 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -679,6 +679,12 @@ config VIRTIO_CONSOLE
help
Virtio console for use with lguest and other hypervisors.
+ Also serves as a general-purpose serial device for data
+ transfer between the guest and host. Character devices at
+ /dev/vconNN will be created when corresponding ports are
+ found. If specified by the host, a sysfs attribute called
+ 'name' will be populated with a name for the port which can
+ be used by udev scripts to create a symlink to /dev/vconNN.
config HVCS
tristate "IBM Hypervisor Virtual Console Server support"
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 0d328b5..14a68b4 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -9,10 +9,8 @@
* functions.
:*/
-/*M:002 The console can be flooded: while the Guest is processing input the
- * Host can send more. Buffering in the Host could alleviate this, but it is a
- * difficult problem in general. :*/
/* Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
+ * Copyright (C) 2009, Amit Shah, Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,116 +26,520 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+
+#include <linux/cdev.h>
+#include <linux/device.h>
#include <linux/err.h>
+#include <linux/fs.h>
#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
#include <linux/virtio.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_console.h>
+#include <linux/workqueue.h>
#include "hvc_console.h"
-/*D:340 These represent our input and output console queues, and the virtio
- * operations for them. */
-static struct virtqueue *in_vq, *out_vq;
-static struct virtio_device *vdev;
+/* This struct stores data that's common to all the ports */
+struct virtio_console_struct {
+ /*
+ * Workqueue handlers where we process deferred work after an
+ * interrupt
+ */
+ struct work_struct rx_work;
+ struct work_struct tx_work;
+ struct work_struct config_work;
-/* This is our input buffer, and how much data is left in it. */
-static unsigned int in_len;
-static char *in, *inbuf;
+ struct list_head port_head;
+ struct list_head unused_read_head;
+ struct list_head unused_write_head;
-/* The operations for our console. */
-static struct hv_ops virtio_cons;
+ /* To protect the list of unused write buffers and the out_vq */
+ spinlock_t write_list_lock;
+
+ struct virtio_device *vdev;
+ struct class *class;
+ /* The input and the output queues */
+ struct virtqueue *in_vq, *out_vq;
+
+ /* The current config space is stored here */
+ struct virtio_console_config config;
+};
+
+/* This struct holds individual buffers received for each port */
+struct virtio_console_port_buffer {
+ struct list_head next;
+
+ char *buf;
+
+ /* length of the buffer */
+ size_t len;
+ /* offset in the buf from which to consume data */
+ size_t offset;
+};
+
+/* This struct holds the per-port data */
+struct virtio_console_port {
+ /* Next port in the list, head is in the virtio_console_struct */
+ struct list_head next;
+
+ /* Pointer to the virtio_console device */
+ struct virtio_console_struct *vcon;
+
+ /* Buffer management */
+ struct list_head readbuf_head;
+
+ /*
+ * To protect the readbuf_head list. Has to be a spinlock
+ * because it can be called from interrupt context
+ * (cons_get_char())
+ */
+ spinlock_t readbuf_list_lock;
+
+ /* A waitqueue for poll() or blocking read operations */
+ wait_queue_head_t waitqueue;
+
+ /* Each port associates with a separate char device */
+ struct cdev cdev;
+ struct device *dev;
+
+ /* The hvc device, if this port is associated with a console */
+ struct hvc_struct *hvc;
+
+ /* The 'name' of the port that we expose via sysfs properties */
+ char *name;
+
+ /* The 'id' to identify the port with the Host */
+ u32 id;
-/* The hvc device */
-static struct hvc_struct *hvc;
+ /*
+ * If this port is a console port, this number identifies the
+ * number that we used to register with hvc in
+ * hvc_instantiate() and hvc_alloc().
+ */
+ u32 vtermno;
-/*D:310 The put_chars() callback is pretty straightforward.
+ /* Is the host device open */
+ bool host_connected;
+};
+
+static struct virtio_console_struct virtconsole;
+
+/*
+ * This is used to keep track of the number of hvc consoles spawned.
+ * This number is given as first argument to hvc_alloc(). We could as
+ * well pass on the minor number of the char device but to correctly
+ * map an initial console spawned via hvc_instantiate to the console
+ * being hooked up via hvc_alloc, we need to pass the same vtermno.
*
- * We turn the characters into a scatter-gather list, add it to the output
- * queue and then kick the Host. Then we sit here waiting for it to finish:
- * inefficient in theory, but in practice implementations will do it
- * immediately (lguest's Launcher does). */
-static int put_chars(u32 vtermno, const char *buf, int count)
+ * With this int, we just assume the first console being initialised
+ * was the first one that got used as the initial console.
+ */
+static unsigned int hvc_vtermno;
+
+static struct virtio_console_port *get_port_from_vtermno(u32 vtermno)
{
- struct scatterlist sg[1];
- unsigned int len;
-
- /* This is a convenient routine to initialize a single-elem sg list */
- sg_init_one(sg, buf, count);
-
- /* add_buf wants a token to identify this buffer: we hand it any
- * non-NULL pointer, since there's only ever one buffer. */
- if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) >= 0) {
- /* Tell Host to go! */
- out_vq->vq_ops->kick(out_vq);
- /* Chill out until it's done with the buffer. */
- while (!out_vq->vq_ops->get_buf(out_vq, &len))
- cpu_relax();
+ struct virtio_console_port *port;
+
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (port->hvc && port->vtermno == vtermno)
+ return port;
}
+ return NULL;
+}
+
+static struct virtio_console_port *get_port_from_devt(dev_t devt)
+{
+ struct virtio_console_port *port;
- /* We're expected to return the amount of data we wrote: all of it. */
- return count;
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (port->dev->devt == devt)
+ return port;
+ }
+ return NULL;
}
-/* Create a scatter-gather list representing our input buffer and put it in the
- * queue. */
-static void add_inbuf(void)
+static struct virtio_console_port *get_port_from_id(u32 id)
{
- struct scatterlist sg[1];
- sg_init_one(sg, inbuf, PAGE_SIZE);
+ struct virtio_console_port *port;
- /* We should always be able to add one buffer to an empty queue. */
- if (in_vq->vq_ops->add_buf(in_vq, sg, 0, 1, inbuf) < 0)
- BUG();
- in_vq->vq_ops->kick(in_vq);
+ list_for_each_entry(port, &virtconsole.port_head, next) {
+ if (port->id == id)
+ return port;
+ }
+ return NULL;
}
-/*D:350 get_chars() is the callback from the hvc_console infrastructure when
- * an interrupt is received.
- *
- * Most of the code deals with the fact that the hvc_console() infrastructure
- * only asks us for 16 bytes at a time. We keep in_offset and in_used fields
- * for partially-filled buffers. */
-static int get_chars(u32 vtermno, char *buf, int count)
+static int get_id_from_port(struct virtio_console_port *port)
{
- /* If we don't have an input queue yet, we can't get input. */
- BUG_ON(!in_vq);
+ return port->id;
+}
- /* No buffer? Try to get one. */
- if (!in_len) {
- in = in_vq->vq_ops->get_buf(in_vq, &in_len);
- if (!in)
+static bool is_console_port(struct virtio_console_port *port)
+{
+ if (port->hvc)
+ return true;
+ return false;
+}
+
+static inline bool use_multiport(struct virtio_console_struct *vcon)
+{
+ /*
+ * This condition can be true when put_chars is called from
+ * early_init
+ */
+ if (!vcon->vdev)
+ return 0;
+ return vcon->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+}
+
+static inline bool is_internal(u32 flags)
+{
+ return flags & VIRTIO_CONSOLE_ID_INTERNAL;
+}
+
+/*
+ * Give out the data that's requested from the buffers that we have
+ * queued up per port
+ */
+static ssize_t fill_readbuf(struct virtio_console_port *port,
+ char *out_buf, size_t out_count, bool to_user)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+ ssize_t out_offset, ret;
+
+ out_offset = 0;
+ /*
+ * Not taking the port->readbuf_list_lock here relying on the
+ * fact that buffers are taken out from the list only in this
+ * function so buf2 should be available all the time.
+ *
+ * Also, copy_to_user() might sleep.
+ */
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ size_t copy_size;
+
+ copy_size = out_count;
+ if (copy_size > buf->len - buf->offset)
+ copy_size = buf->len - buf->offset;
+
+ if (to_user) {
+ ret = copy_to_user(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ /* FIXME: Deal with ret != 0 */
+ } else {
+ memcpy(out_buf + out_offset,
+ buf->buf + buf->offset,
+ copy_size);
+ ret = 0; /* Emulate copy_to_user behaviour */
+ }
+
+ /* Return the number of bytes actually copied */
+ ret = copy_size - ret;
+ buf->offset += ret;
+ out_offset += ret;
+ out_count -= ret;
+
+ if (buf->len - buf->offset == 0) {
+ spin_lock(&port->readbuf_list_lock);
+ list_del(&buf->next);
+ spin_unlock(&port->readbuf_list_lock);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ if (!out_count)
+ break;
+ }
+ return out_offset;
+}
+
+/* The condition that must be true for polling to end */
+static bool wait_is_over(struct virtio_console_port *port)
+{
+ return !list_empty(&port->readbuf_head) || !port->host_connected;
+}
+
+static ssize_t virtconsole_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+ ssize_t ret;
+
+ port = filp->private_data;
+
+ if (list_empty(&port->readbuf_head)) {
+ /*
+ * If nothing's connected on the host just return 0 in
+ * case of list_empty; this tells the userspace app
+ * that there's no connection
+ */
+ if (!port->host_connected)
return 0;
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(port->waitqueue,
+ wait_is_over(port));
+ if (ret < 0)
+ return ret;
+ }
+ /*
+ * We could've received a disconnection message while we were
+ * waiting for more data.
+ *
+ * This check is not clubbed in the if() statement above as we
+ * might receive some data as well as the host could get
+ * disconnected after we got woken up from our wait. So we
+ * really want to give off whatever data we have and only then
+ * check for host_connected
+ */
+ if (list_empty(&port->readbuf_head) && !port->host_connected)
+ return 0;
+
+ return fill_readbuf(port, ubuf, count, true);
+}
+
+static ssize_t send_buf(struct virtio_console_port *port,
+ const char *in_buf, size_t in_count,
+ u32 flags, bool from_user)
+{
+ struct virtqueue *out_vq;
+ struct virtio_console_port_buffer *buf, *buf2;
+ struct scatterlist sg[1];
+ struct virtio_console_header header;
+ size_t in_offset, copy_size;
+ ssize_t ret;
+ unsigned int header_len;
+
+ if (!in_count)
+ return 0;
+
+ out_vq = port->vcon->out_vq;
+ /*
+ * We should not send internal messages to a host that won't
+ * understand them
+ */
+ if (!use_multiport(port->vcon) && is_internal(flags))
+ return 0;
+ header_len = 0;
+ if (use_multiport(port->vcon)) {
+ header.id = get_id_from_port(port);
+ header.flags = flags;
+ header.size = in_count;
+ header_len = sizeof(header);
}
+ in_offset = 0; /* offset in the user buffer */
+ while (in_count - in_offset) {
+ copy_size = min(in_count - in_offset + header_len, PAGE_SIZE);
- /* You want more than we have to give? Well, try wanting less! */
- if (in_len < count)
- count = in_len;
+ spin_lock(&port->vcon->write_list_lock);
+ list_for_each_entry_safe(buf, buf2,
+ &port->vcon->unused_write_head,
+ next) {
+ list_del(&buf->next);
+ break;
+ }
+ spin_unlock(&port->vcon->write_list_lock);
+ if (!buf)
+ break;
+ if (header_len) {
+ memcpy(buf->buf, &header, header_len);
+ copy_size -= header_len;
+ }
+ if (from_user)
+ ret = copy_from_user(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ else {
+ /*
+ * Since we're not sure when the host will actually
+ * consume the data and tell us about it, we have
+ * to copy the data here in case the caller
+ * frees the in_buf
+ */
+ memcpy(buf->buf + header_len,
+ in_buf + in_offset, copy_size);
+ ret = 0; /* Emulate copy_from_user behaviour */
+ }
+ buf->len = header_len + copy_size - ret;
+ sg_init_one(sg, buf->buf, buf->len);
- /* Copy across to their buffer and increment offset. */
- memcpy(buf, in, count);
- in += count;
- in_len -= count;
+ spin_lock(&port->vcon->write_list_lock);
+ ret = out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, buf);
+ spin_unlock(&port->vcon->write_list_lock);
+ if (ret < 0) {
+ memset(buf->buf, 0, buf->len);
+ spin_lock(&virtconsole.write_list_lock);
+ list_add_tail(&buf->next,
+ &port->vcon->unused_write_head);
+ spin_unlock(&port->vcon->write_list_lock);
+ break;
+ }
+ in_offset += buf->len - header_len;
+ /*
+ * Only send size with the first buffer. This way
+ * userspace can find out a continuous stream of data
+ * belonging to one write request and consume it
+ * appropriately
+ */
+ header.size = 0;
- /* Finished? Re-register buffer so Host will use it again. */
- if (in_len == 0)
- add_inbuf();
+ /* No space left in the vq anyway */
+ if (!ret)
+ break;
+ }
+ /* Tell Host to go! */
+ spin_lock(&port->vcon->write_list_lock);
+ out_vq->vq_ops->kick(out_vq);
+ spin_unlock(&port->vcon->write_list_lock);
- return count;
+ /* We're expected to return the amount of data we wrote */
+ return in_offset;
}
-/*:*/
-/*D:320 Console drivers are initialized very early so boot messages can go out,
- * so we do things slightly differently from the generic virtio initialization
- * of the net and block drivers.
+static ssize_t virtconsole_write(struct file *filp, const char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct virtio_console_port *port;
+
+ port = filp->private_data;
+
+ return send_buf(port, ubuf, count, 0, true);
+}
+
+static unsigned int virtconsole_poll(struct file *filp, poll_table *wait)
+{
+ struct virtio_console_port *port;
+ unsigned int ret;
+
+ port = filp->private_data;
+ poll_wait(filp, &port->waitqueue, wait);
+
+ ret = 0;
+ if (!list_empty(&port->readbuf_head))
+ ret |= POLLIN | POLLRDNORM;
+ if (!port->host_connected)
+ ret |= POLLHUP;
+
+ return ret;
+}
+
+static int virtconsole_release(struct inode *inode, struct file *filp)
+{
+ struct virtio_console_control cpkt;
+
+ /* Notify host of port being closed */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 0;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+ return 0;
+}
+
+static int virtconsole_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+
+ port = container_of(cdev, struct virtio_console_port, cdev);
+ filp->private_data = port;
+
+ /* Notify host of port being opened */
+ cpkt.event = VIRTIO_CONSOLE_PORT_OPEN;
+ cpkt.value = 1;
+ send_buf(filp->private_data, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ return 0;
+}
+
+/*
+ * The file operations that we support: programs in the guest can open
+ * a console device, read from it, write to it, poll for data and
+ * close it. The devices are at /dev/vconNN
+ */
+static const struct file_operations virtconsole_fops = {
+ .owner = THIS_MODULE,
+ .open = virtconsole_open,
+ .read = virtconsole_read,
+ .write = virtconsole_write,
+ .poll = virtconsole_poll,
+ .release = virtconsole_release,
+};
+
+
+static ssize_t show_port_name(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ struct virtio_console_port *port;
+
+ port = get_port_from_devt(dev->devt);
+ if (!port || !port->name)
+ return 0;
+
+ return sprintf(buffer, "%s\n", port->name);
+}
+
+static DEVICE_ATTR(name, S_IRUGO, show_port_name, NULL);
+
+static struct attribute *virtcon_sysfs_entries[] = {
+ &dev_attr_name.attr,
+ NULL
+};
+
+static struct attribute_group virtcon_attribute_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = virtcon_sysfs_entries,
+};
+
+
+/*D:310
+ * The cons_put_chars() callback is pretty straightforward.
*
- * At this stage, the console is output-only. It's too early to set up a
- * virtqueue, so we let the drivers do some boutique early-output thing. */
-int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+ * We turn the characters into a scatter-gather list, add it to the output
+ * queue and then kick the Host.
+ *
+ * If the data to be outpu spans more than a page, it's split into
+ * page-sized buffers and then individual buffers are pushed to Host.
+ */
+static int cons_put_chars(u32 vtermno, const char *buf, int count)
{
- virtio_cons.put_chars = put_chars;
- return hvc_instantiate(0, 0, &virtio_cons);
+ struct virtio_console_port *port;
+
+ port = get_port_from_vtermno(vtermno);
+ if (!port)
+ return 0;
+
+ return send_buf(port, buf, count, 0, false);
}
+/*D:350
+ * cons_get_chars() is the callback from the hvc_console
+ * infrastructure when an interrupt is received.
+ *
+ * We call out to fill_readbuf that gets us the required data from the
+ * buffers that are queued up.
+ */
+static int cons_get_chars(u32 vtermno, char *buf, int count)
+{
+ struct virtio_console_port *port;
+
+ /* If we don't have an input queue yet, we can't get input. */
+ BUG_ON(!virtconsole.in_vq);
+
+ port = get_port_from_vtermno(vtermno);
+ if (!port)
+ return 0;
+
+ if (list_empty(&port->readbuf_head))
+ return 0;
+
+ return fill_readbuf(port, buf, count, false);
+}
+/*:*/
+
/*
* virtio console configuration. This supports:
* - console resize
@@ -153,98 +555,572 @@ static void virtcons_apply_config(struct virtio_device *dev)
dev->config->get(dev,
offsetof(struct virtio_console_config, rows),
&ws.ws_row, sizeof(u16));
- hvc_resize(hvc, ws);
+ /*
+ * We'll use this way of resizing only for legacy
+ * support. For newer userspace (VIRTIO_CONSOLE_F_MULTPORT+),
+ * use internal messages to indicate console size
+ * changes so that it can be done per-port
+ */
+ if (!use_multiport(&virtconsole))
+ hvc_resize(get_port_from_id(0)->hvc, ws);
}
}
/*
- * we support only one console, the hvc struct is a global var
* We set the configuration at this point, since we now have a tty
*/
-static int notifier_add_vio(struct hvc_struct *hp, int data)
+static int cons_notifier_add_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 1;
- virtcons_apply_config(vdev);
+ virtcons_apply_config(virtconsole.vdev);
return 0;
}
-static void notifier_del_vio(struct hvc_struct *hp, int data)
+static void cons_notifier_del_vio(struct hvc_struct *hp, int data)
{
hp->irq_requested = 0;
}
-static void hvc_handle_input(struct virtqueue *vq)
+/* The operations for our console. */
+static struct hv_ops virtio_cons = {
+ .get_chars = cons_get_chars,
+ .put_chars = cons_put_chars,
+ .notifier_add = cons_notifier_add_vio,
+ .notifier_del = cons_notifier_del_vio,
+ .notifier_hangup = cons_notifier_del_vio,
+};
+
+/*D:320
+ * Console drivers are initialized very early so boot messages can go out,
+ * so we do things slightly differently from the generic virtio initialization
+ * of the net and block drivers.
+ *
+ * At this stage, the console is output-only. It's too early to set up a
+ * virtqueue, so we let the drivers do some boutique early-output thing.
+ */
+int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
+{
+ virtio_cons.put_chars = put_chars;
+ return hvc_instantiate(0, 0, &virtio_cons);
+}
+
+int init_port_console(struct virtio_console_port *port)
{
- if (hvc_poll(hvc))
- hvc_kick();
+ int ret = 0;
+
+ /*
+ * The Host's telling us this port is a console port. Hook it
+ * up with an hvc console.
+ *
+ * To set up and manage our virtual console, we call
+ * hvc_alloc().
+ *
+ * The first argument of hvc_alloc() is the virtual console
+ * number. The second argument is the parameter for the
+ * notification mechanism (like irq number). We currently
+ * leave this as zero, virtqueues have implicit notifications.
+ *
+ * The third argument is a "struct hv_ops" containing the
+ * put_chars() get_chars(), notifier_add() and notifier_del()
+ * pointers. The final argument is the output buffer size: we
+ * can do any size, so we put PAGE_SIZE here.
+ */
+ port->hvc = hvc_alloc(hvc_vtermno, 0, &virtio_cons, PAGE_SIZE);
+ if (IS_ERR(port->hvc)) {
+ ret = PTR_ERR(port->hvc);
+ pr_err("%s: Could not alloc hvc for virtio console port, ret = %d\n",
+ __func__, ret);
+ port->hvc = NULL;
+ } else
+ port->vtermno = hvc_vtermno++;
+ return ret;
}
-/*D:370 Once we're further in boot, we get probed like any other virtio device.
- * At this stage we set up the output virtqueue.
+/* Any secret messages that the Host and Guest want to share */
+static void handle_control_message(struct virtio_console_port *port,
+ struct virtio_console_port_buffer *buf)
+{
+ struct virtio_console_control *cpkt;
+ size_t name_size;
+
+ cpkt = (struct virtio_console_control *)(buf->buf + buf->offset);
+
+ switch (cpkt->event) {
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->host_connected = cpkt->value;
+ break;
+ case VIRTIO_CONSOLE_PORT_NAME:
+ /*
+ * Skip the size of the header and the cpkt to get the size
+ * of the name that was sent
+ */
+ name_size = buf->len - buf->offset - sizeof(*cpkt) + 1;
+
+ port->name = kmalloc(name_size, GFP_KERNEL);
+ if (!port->name) {
+ pr_err("%s: not enough space to store port name\n",
+ __func__);
+ break;
+ }
+ strncpy(port->name, buf->buf + buf->offset + sizeof(*cpkt),
+ name_size - 1);
+ port->name[name_size - 1] = 0;
+ break;
+ case VIRTIO_CONSOLE_CONSOLE_PORT:
+ if (!cpkt->value)
+ break;
+ init_port_console(port);
+ /*
+ * Could remove the port here in case init fails - but
+ * have to notify the host first
+ */
+ break;
+ }
+}
+
+
+static struct virtio_console_port_buffer *get_buf(size_t buf_size)
+{
+ struct virtio_console_port_buffer *buf;
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ goto out;
+ buf->buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!buf->buf) {
+ kfree(buf);
+ goto out;
+ }
+ buf->len = buf_size;
+out:
+ return buf;
+}
+
+static void fill_queue(struct virtqueue *vq, size_t buf_size,
+ struct list_head *unused_head)
+{
+ struct scatterlist sg[1];
+ struct virtio_console_port_buffer *buf;
+ int ret;
+
+ do {
+ buf = get_buf(buf_size);
+ if (!buf)
+ break;
+ sg_init_one(sg, buf->buf, buf_size);
+
+ ret = vq->vq_ops->add_buf(vq, sg, 0, 1, buf);
+ if (ret < 0) {
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ /*
+ * We have to keep track of the unused buffers so that
+ * they can be freed when the module is being removed
+ */
+ list_add_tail(&buf->next, unused_head);
+ } while (ret > 0);
+ vq->vq_ops->kick(vq);
+}
+
+static void fill_receive_queue(struct virtio_console_struct *vcon)
+{
+ fill_queue(vcon->in_vq, PAGE_SIZE, &vcon->unused_read_head);
+}
+
+/*
+ * This function is only called from the init routine so the spinlock
+ * for the unused_write_head list isn't taken
+ */
+static void alloc_write_bufs(struct virtio_console_struct *vcon)
+{
+ struct virtio_console_port_buffer *buf;
+ int i;
+
+ for (i = 0; i < 1024; i++) {
+ buf = get_buf(PAGE_SIZE);
+ if (!buf)
+ break;
+ list_add_tail(&buf->next, &vcon->unused_write_head);
+ }
+}
+
+/*
+ * The workhandler for any buffers that appear on our input queue.
+ * Pick the buffer; if it's some internal communication meant for the
+ * us, just process it. Otherwise queue it up for the read() or
+ * get_chars() routines to pick the data up later.
+ */
+static void virtio_console_rx_work_handler(struct work_struct *work)
+{
+ struct virtio_console_struct *vcon;
+ struct virtio_console_port *port;
+ struct virtio_console_port_buffer *buf;
+ struct virtio_console_header header;
+ struct virtqueue *vq;
+ unsigned int tmplen, header_len;
+
+ vcon = container_of(work, struct virtio_console_struct, rx_work);
+ header_len = use_multiport(vcon) ? sizeof(header) : 0;
+
+ port = NULL;
+ vq = vcon->in_vq;
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* The buffer is no longer unused */
+ list_del(&buf->next);
+
+ if (use_multiport(vcon)) {
+ memcpy(&header, buf->buf, header_len);
+ port = get_port_from_id(header.id);
+ } else
+ port = get_port_from_id(0);
+ if (!port) {
+ /* No valid header at start of buffer. Drop it. */
+ pr_debug("%s: invalid index in buffer, %c %d\n",
+ __func__, buf->buf[0], buf->buf[0]);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free / alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ break;
+ }
+ buf->len = tmplen;
+ buf->offset = header_len;
+ if (use_multiport(vcon) && is_internal(header.flags)) {
+ handle_control_message(port, buf);
+ /*
+ * OPT: This buffer can be added to the unused
+ * list to avoid free/alloc
+ */
+ kfree(buf->buf);
+ kfree(buf);
+ } else {
+ spin_lock(&port->readbuf_list_lock);
+ list_add_tail(&buf->next, &port->readbuf_head);
+ spin_unlock(&port->readbuf_list_lock);
+ /*
+ * We might have missed a connection
+ * notification, e.g. before the queues were
+ * initialised.
+ */
+ port->host_connected = true;
+ }
+ wake_up_interruptible(&port->waitqueue);
+
+ if (is_console_port(port) && !list_empty(&port->readbuf_head))
+ if (hvc_poll(port->hvc))
+ hvc_kick();
+ }
+ /* Allocate buffers for all the ones that got used up */
+ fill_receive_queue(&virtconsole);
+}
+
+/*
+ * This is the workhandler for buffers that get received on the output
+ * virtqueue, which is an indication that Host consumed the data we
+ * sent it. Since all our buffers going out are of a fixed size we can
+ * just reuse them instead of freeing them and allocating new ones.
+ *
+ * Zero out the buffer so that we don't leak any information from
+ * other processes. There's a small optimisation here as well: the
+ * buffers are PAGE_SIZE-sized; but instead of zeroing the entire
+ * page, we just zero the length that was most recently used and we
+ * can be sure the rest of the page is already set to 0s.
+ *
+ * So once we zero them out we add them back to the unused buffers
+ * list
+ */
+static void virtio_console_tx_work_handler(struct work_struct *work)
+{
+ struct virtio_console_struct *vcon;
+ struct virtqueue *vq;
+ struct virtio_console_port_buffer *buf;
+ unsigned int tmplen;
+
+ vcon = container_of(work, struct virtio_console_struct, tx_work);
+
+ vq = vcon->out_vq;
+ spin_lock(&vcon->write_list_lock);
+ while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) {
+ /* 0 the buffer to not leak data from other processes */
+ memset(buf->buf, 0, buf->len);
+ list_add_tail(&buf->next, &vcon->unused_write_head);
+ }
+ spin_unlock(&vcon->write_list_lock);
+}
+
+static void rx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.rx_work);
+}
+
+static void tx_intr(struct virtqueue *vq)
+{
+ schedule_work(&virtconsole.tx_work);
+}
+
+static void config_intr(struct virtio_device *vdev)
+{
+ if (use_multiport(&virtconsole)) {
+ /* Handle port hot-add */
+ schedule_work(&virtconsole.config_work);
+ }
+ /* Handle console size changes */
+ virtcons_apply_config(vdev);
+}
+
+static int virtconsole_add_port(u32 port_nr)
+{
+ struct virtio_console_port *port;
+ struct virtio_console_control cpkt;
+ dev_t devt;
+ int ret;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->vcon = &virtconsole;
+ port->id = port_nr;
+
+ cdev_init(&port->cdev, &virtconsole_fops);
+
+ ret = alloc_chrdev_region(&devt, 0, 1, "virtio-console");
+ if (ret < 0) {
+ pr_err("%s: error allocing chrdev region, ret = %d\n",
+ __func__, ret);
+ goto free_port;
+ }
+ ret = cdev_add(&port->cdev, devt, 1);
+ if (ret < 0) {
+ pr_err("%s: error adding cdev, ret = %d\n", __func__, ret);
+ goto free_chrdev;
+ }
+ port->dev = device_create(port->vcon->class, NULL, devt, NULL,
+ "vcon%u", port_nr);
+ if (IS_ERR(port->dev)) {
+ ret = PTR_ERR(port->dev);
+ pr_err("%s: error creating device, ret = %d\n", __func__, ret);
+ goto free_cdev;
+ }
+ ret = sysfs_create_group(&port->dev->kobj, &virtcon_attribute_group);
+ if (ret) {
+ pr_err("%s: error creating sysfs device attributes, ret = %d\n",
+ __func__, ret);
+ goto free_cdev;
+ }
+
+ spin_lock_init(&port->readbuf_list_lock);
+ INIT_LIST_HEAD(&port->readbuf_head);
+ init_waitqueue_head(&port->waitqueue);
+
+ list_add_tail(&port->next, &port->vcon->port_head);
+
+ /*
+ * Ask for the port's name from Host. The string that we
+ * receive in 'name' can be of arbitrary length; so pass the
+ * maximum available buffer size: PAGE_SIZE.
+ */
+ cpkt.event = VIRTIO_CONSOLE_PORT_NAME;
+ send_buf(port, (char *)&cpkt, sizeof(cpkt),
+ VIRTIO_CONSOLE_ID_INTERNAL, false);
+
+ /*
+ * If we're not using multiport support, this has to be a console port
+ */
+ if (!use_multiport(&virtconsole)) {
+ ret = init_port_console(port);
+ if (ret)
+ goto free_cdev;
+ }
+ return 0;
+free_cdev:
+ cdev_del(&port->cdev);
+free_chrdev:
+ unregister_chrdev_region(devt, 1);
+free_port:
+ kfree(port);
+ return ret;
+}
+
+
+/* The workhandler for config-space updates
*
- * To set up and manage our virtual console, we call hvc_alloc(). Since we
- * never remove the console device we never need this pointer again.
+ * This is used when new ports are added
+ */
+static void virtio_console_config_work_handler(struct work_struct *work)
+{
+ struct virtio_console_struct *vcon;
+ struct virtio_console_config virtconconf;
+ struct virtio_device *vdev;
+ u32 i;
+ int ret;
+
+ vcon = container_of(work, struct virtio_console_struct, config_work);
+
+ vdev = vcon->vdev;
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config, nr_active_ports),
+ &virtconconf.nr_active_ports,
+ sizeof(virtconconf.nr_active_ports));
+
+ /* Hot-add ports */
+ for (i = virtconsole.config.nr_active_ports;
+ i < virtconconf.nr_active_ports; i++) {
+ ret = virtconsole_add_port(virtconsole.config.nr_active_ports + i);
+ if (!ret)
+ virtconsole.config.nr_active_ports++;
+ }
+}
+
+/*D:370
+ * Once we're further in boot, we get probed like any other virtio device.
+ * At this stage we set up the output virtqueue.
*
- * Finally we put our input buffer in the input queue, ready to receive. */
-static int __devinit virtcons_probe(struct virtio_device *dev)
+ * Finally we put our input buffer in the input queue, ready to receive.
+ */
+static int __devinit virtcons_probe(struct virtio_device *vdev)
{
- vq_callback_t *callbacks[] = { hvc_handle_input, NULL};
+ vq_callback_t *callbacks[] = { rx_intr, tx_intr };
const char *names[] = { "input", "output" };
struct virtqueue *vqs[2];
- int err;
-
- vdev = dev;
+ u32 i;
+ int ret;
+ bool multiport;
- /* This is the scratch page we use to receive console input */
- inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!inbuf) {
- err = -ENOMEM;
- goto fail;
+ if (virtconsole.vdev) {
+ pr_err("Multiple virtio-console devices not supported yet\n");
+ return -EEXIST;
}
+ virtconsole.vdev = vdev;
+
+ multiport = false;
+ if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT)) {
+ multiport = true;
+ vdev->features[0] |= 1 << VIRTIO_CONSOLE_F_MULTIPORT;
+ vdev->config->finalize_features(vdev);
+ vdev->config->get(vdev, offsetof(struct virtio_console_config,
+ nr_active_ports),
+ &virtconsole.config.nr_active_ports,
+ sizeof(virtconsole.config.nr_active_ports));
+ }
/* Find the queues. */
/* FIXME: This is why we want to wean off hvc: we do nothing
* when input comes in. */
- err = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
- if (err)
- goto free;
+ ret = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
+ if (ret)
+ goto fail;
- in_vq = vqs[0];
- out_vq = vqs[1];
+ virtconsole.in_vq = vqs[0];
+ virtconsole.out_vq = vqs[1];
- /* Start using the new console output. */
- virtio_cons.get_chars = get_chars;
- virtio_cons.put_chars = put_chars;
- virtio_cons.notifier_add = notifier_add_vio;
- virtio_cons.notifier_del = notifier_del_vio;
- virtio_cons.notifier_hangup = notifier_del_vio;
-
- /* The first argument of hvc_alloc() is the virtual console number, so
- * we use zero. The second argument is the parameter for the
- * notification mechanism (like irq number). We currently leave this
- * as zero, virtqueues have implicit notifications.
- *
- * The third argument is a "struct hv_ops" containing the put_chars()
- * get_chars(), notifier_add() and notifier_del() pointers.
- * The final argument is the output buffer size: we can do any size,
- * so we put PAGE_SIZE here. */
- hvc = hvc_alloc(0, 0, &virtio_cons, PAGE_SIZE);
- if (IS_ERR(hvc)) {
- err = PTR_ERR(hvc);
- goto free_vqs;
+ /*
+ * We had set the virtio_cons put_chars implementation to
+ * put_chars for early_init. Now that we're done with the
+ * early init phase, replace it with our cons_put_chars
+ * implementation.
+ */
+ virtio_cons.put_chars = cons_put_chars;
+
+ INIT_LIST_HEAD(&virtconsole.port_head);
+ INIT_LIST_HEAD(&virtconsole.unused_read_head);
+ INIT_LIST_HEAD(&virtconsole.unused_write_head);
+
+ INIT_WORK(&virtconsole.rx_work, &virtio_console_rx_work_handler);
+ INIT_WORK(&virtconsole.tx_work, &virtio_console_tx_work_handler);
+ INIT_WORK(&virtconsole.config_work, &virtio_console_config_work_handler);
+ spin_lock_init(&virtconsole.write_list_lock);
+
+ fill_receive_queue(&virtconsole);
+ alloc_write_bufs(&virtconsole);
+
+ virtconsole_add_port(0);
+ if (multiport)
+ for (i = 1; i < virtconsole.config.nr_active_ports; i++)
+ virtconsole_add_port(i);
+
+ return 0;
+
+fail:
+ return ret;
+}
+
+/*
+ * Remove port-specific data.
+ * In case the port can't be removed, return non-zero. This could
+ * then be used in the port hot-unplug case.
+ */
+static int virtcons_remove_port_data(struct virtio_console_port *port)
+{
+ struct virtio_console_port_buffer *buf, *buf2;
+
+ if (is_console_port(port)) {
+ /* hvc_console is compiled in, at least on Fedora. */
+ /* hvc_remove(hvc); */
+ return 1;
}
- /* Register the input buffer the first time. */
- add_inbuf();
+ sysfs_remove_group(&port->dev->kobj, &virtcon_attribute_group);
+ device_destroy(virtconsole.class, port->dev->devt);
+ unregister_chrdev_region(port->dev->devt, 1);
+ cdev_del(&port->cdev);
+
+ kfree(port->name);
+
+ /* Remove the buffers in which we have unconsumed data */
+ spin_lock(&port->readbuf_list_lock);
+ list_for_each_entry_safe(buf, buf2, &port->readbuf_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ spin_unlock(&port->readbuf_list_lock);
return 0;
+}
+
+static void virtcons_remove(struct virtio_device *vdev)
+{
+ struct virtio_console_port *port, *port2;
+ struct virtio_console_port_buffer *buf, *buf2;
+ char *tmpbuf;
+ int len;
+
+ class_destroy(virtconsole.class);
+
+ cancel_work_sync(&virtconsole.rx_work);
+ /*
+ * Free up the buffers that we queued up for the Host to pass
+ * us data
+ */
+ while ((tmpbuf = virtconsole.in_vq->vq_ops->get_buf(virtconsole.in_vq,
+ &len)))
+ kfree(tmpbuf);
-free_vqs:
vdev->config->del_vqs(vdev);
-free:
- kfree(inbuf);
-fail:
- return err;
+ /*
+ * Free up the buffers that were sent to us by Host but were
+ * left unused
+ */
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_read_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(buf, buf2, &virtconsole.unused_write_head, next) {
+ list_del(&buf->next);
+ kfree(buf->buf);
+ kfree(buf);
+ }
+ list_for_each_entry_safe(port, port2, &virtconsole.port_head, next) {
+ list_del(&port->next);
+ virtcons_remove_port_data(port);
+ kfree(port);
+ }
}
static struct virtio_device_id id_table[] = {
@@ -254,6 +1130,7 @@ static struct virtio_device_id id_table[] = {
static unsigned int features[] = {
VIRTIO_CONSOLE_F_SIZE,
+ VIRTIO_CONSOLE_F_MULTIPORT,
};
static struct virtio_driver virtio_console = {
@@ -263,14 +1140,34 @@ static struct virtio_driver virtio_console = {
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtcons_probe,
- .config_changed = virtcons_apply_config,
+ .remove = virtcons_remove,
+ .config_changed = config_intr,
};
static int __init init(void)
{
- return register_virtio_driver(&virtio_console);
+ int ret;
+
+ virtconsole.class = class_create(THIS_MODULE, "virtio-console");
+ if (IS_ERR(virtconsole.class)) {
+ pr_err("Error creating virtio-console class\n");
+ ret = PTR_ERR(virtconsole.class);
+ return ret;
+ }
+ ret = register_virtio_driver(&virtio_console);
+ if (ret) {
+ class_destroy(virtconsole.class);
+ return ret;
+ }
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ unregister_virtio_driver(&virtio_console);
}
module_init(init);
+module_exit(fini);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio console driver");
diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h
index b5f5198..96bb6f0 100644
--- a/include/linux/virtio_console.h
+++ b/include/linux/virtio_console.h
@@ -2,19 +2,63 @@
#define _LINUX_VIRTIO_CONSOLE_H
#include <linux/types.h>
#include <linux/virtio_config.h>
-/* This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
- * anyone can use the definitions to implement compatible drivers/servers. */
+/*
+ * This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so
+ * anyone can use the definitions to implement compatible drivers/servers.
+ *
+ * Copyright (C) Red Hat, Inc., 2009
+ */
/* Feature bits */
#define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */
+#define VIRTIO_CONSOLE_F_MULTIPORT 1 /* Does host provide multiple ports? */
+
+#define VIRTIO_CONSOLE_BAD_ID (~(u32)0) /* Invalid port number */
struct virtio_console_config {
/* colums of the screens */
__u16 cols;
/* rows of the screens */
__u16 rows;
+ /* number of ports in use */
+ __u32 nr_active_ports;
+} __attribute__((packed));
+
+
+/*
+ * An internal-only message that's passed between the Host and the
+ * Guest for a particular port.
+ */
+struct virtio_console_control {
+ __u16 event;
+ __u16 value;
+};
+
+/* Some events for internal messages (control packets) */
+#define VIRTIO_CONSOLE_PORT_OPEN 0
+#define VIRTIO_CONSOLE_PORT_NAME 1
+#define VIRTIO_CONSOLE_CONSOLE_PORT 2
+
+
+/*
+ * This struct is put in each buffer that gets passed to userspace and
+ * vice-versa
+ */
+struct virtio_console_header {
+ /* Port number */
+ u32 id;
+ /* Some message between host and guest */
+ u32 flags;
+ /*
+ * Complete size of the write request - only sent with the
+ * first buffer for each write request
+ */
+ u32 size;
} __attribute__((packed));
+/* Messages between host and guest ('flags' field in the header above) */
+#define VIRTIO_CONSOLE_ID_INTERNAL (1 << 0)
+
#ifdef __KERNEL__
int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int));
Am Montag 05 Oktober 2009 16:05:35 schrieb Amit Shah:
> On (Thu) Oct 01 2009 [12:28:30], Christian Borntraeger wrote:
> > With the latest git kernel + your patch I sometmes get a completely
> > frozen console. In the dump there is
> >
> > <3>virtio_console virtio0: output:id 68 is not a head!
> >
> > Seems that I can reproduce it with large amounts of output (find / for
> > example) Without your patch everything is fine.
>
> Hey Christian,
>
> Can you try this patch?
This version seems to work on s390. Thanks
On (Tue) Oct 06 2009 [08:49:22], Christian Borntraeger wrote:
> Am Montag 05 Oktober 2009 16:05:35 schrieb Amit Shah:
> > On (Thu) Oct 01 2009 [12:28:30], Christian Borntraeger wrote:
> > > With the latest git kernel + your patch I sometmes get a completely
> > > frozen console. In the dump there is
> > >
> > > <3>virtio_console virtio0: output:id 68 is not a head!
> > >
> > > Seems that I can reproduce it with large amounts of output (find / for
> > > example) Without your patch everything is fine.
> >
> > Hey Christian,
> >
> > Can you try this patch?
>
> This version seems to work on s390. Thanks
Great; thanks for the confirmation.
The race I'm seeing could be due to some misconfiguration in qemu then.
Amit