---
 drivers/char/virtio_console.c |  182 +++++++++++++++++++++++++++---------------
 1 file changed, 117 insertions(+), 65 deletions(-)

diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -21,15 +21,28 @@
 #include <linux/virtio_console.h>
 #include "hvc_console.h"
 
+struct buffer {
+	struct list_head list;
+
+	const void *data;
+	unsigned int len;
+
+	/* This starts 0, set to length for outbufs, used_len for inbufs */
+	unsigned int used_len;
+
+	struct port *owner;
+	void (*complete)(struct buffer *buffer, unsigned int len);
+};
+
 struct port {
 	struct virtqueue *in_vq, *out_vq;
 	struct virtio_device *vdev;
 
-	/* This is our input buffer, and how much data is left in it. */
-	char *inbuf;
-	unsigned int len, offset;
+	/* Input and output buffers. */
+	struct list_head inbufs;
+	struct list_head outbufs;
 
-	/* For console ports, hvc != NULL and these are valid. */
+	/* For console ports, hvc != NULL and these are valid: */
 	/* The hvc device */
 	struct hvc_struct *hvc;
 
@@ -38,6 +51,11 @@ struct port {
 
 	/* Our vterm number. */
 	u32 vtermno;
+
+	/* Our single input buffer, which we recycle. */
+	struct buffer console_inbuf;
+	unsigned int inbuf_offset;
+	bool inbuf_empty;
 };
 
 /* This is vdev->priv. */
@@ -53,17 +71,6 @@ static LIST_HEAD(ports_by_vtermno);
 /* The lock on the port list. */
 static DEFINE_SPINLOCK(ports_by_vtermno_lock);
 
-/* Does an add_buf to the other side: you need to kick after this. */
-static int add_outbuf(struct port *port, const char *buf, int count)
-{
-	struct scatterlist sg[1];
-
-	/* This is a convenient routine to initialize a single-elem sg list */
-	sg_init_one(sg, buf, count);
-
-	return port->out_vq->vq_ops->add_buf(port->out_vq, sg, 1, 0, port);
-}
-
 static struct port *find_port_by_vtermno(u32 vtermno)
 {
 	unsigned long flags;
@@ -80,39 +87,72 @@ out:
 	return port;
 }
 
-/* The put_chars() callback is pretty straightforward.
+static int add_buffer(struct buffer *buf,
+		      struct list_head *list,
+		      struct port *port,
+		      void (*cb)(struct buffer *buffer, unsigned int len),
+		      const void *data, unsigned int len)
+{
+	struct scatterlist sg[1];
+	int ret;
+
+	buf->owner = port;
+	buf->complete = cb;
+	buf->used_len = 0;
+	buf->data = data;
+	buf->len = len;
+
+	/* This is a convenient routine to initialize a single-elem sg list */
+	sg_init_one(sg, buf->data, buf->len);
+
+	ret = port->out_vq->vq_ops->add_buf(port->out_vq, sg, 1, 0, buf);
+	if (unlikely(ret < 0))
+		return ret;
+
+	list_add(list, &buf->list);
+	return 0;
+}
+
+static void complete_mark_used(struct buffer *buf, unsigned int len)
+{
+	buf->used_len = len;
+}
+
+/*
+ * 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). */
+ * immediately (lguest's Launcher does).
+ *
+ * Interrupts are disabled, so we can't sleep while waiting.
+ */
 static int put_chars(u32 vtermno, const char *buf, int count)
 {
-	unsigned int len;
 	struct port *port = find_port_by_vtermno(vtermno);
+	struct buffer outbuf;
 
 	/* We should never fail.  If we do, just lose console output. */
-	if (unlikely(add_outbuf(port, buf, count) < 0))
+	if (add_buffer(&outbuf, &port->outbufs, port, complete_mark_used,
+		       buf, count) < 0)
 		return count;
 
 	port->out_vq->vq_ops->kick(port->out_vq);
 
 	/* Chill out until it's done with the buffer. */
-	while (!port->out_vq->vq_ops->get_buf(port->out_vq, &len))
+	while (!outbuf.used_len)
 		cpu_relax();
 
 	/* We're expected to return the amount of data we wrote: all of it. */
 	return count;
 }
 
-/* Create a scatter-gather list representing our input buffer and put it in the
- * queue. */
-static int add_inbuf(struct port *port)
+static void complete_poll_hvc(struct buffer *buf, unsigned int len)
 {
-	struct scatterlist sg[1];
-	sg_init_one(sg, port->inbuf, port->len);
-
-	return port->in_vq->vq_ops->add_buf(port->in_vq, sg, 0, 1, port);
+	buf->used_len = len;
+	if (hvc_poll(buf->owner->hvc))
+		hvc_kick();
 }
 
 /* get_chars() is the callback from the hvc_console infrastructure when
@@ -124,31 +164,33 @@ static int add_inbuf(struct port *port)
 static int get_chars(u32 vtermno, char *buf, int count)
 {
 	struct port *port = find_port_by_vtermno(vtermno);
+	struct buffer *inbuf = &port->console_inbuf;
 
 	/* If we don't have an input queue yet, we can't get input. */
 	BUG_ON(!port->in_vq);
 
 	/* No more in buffer?  See if they've (re)used it. */
-	if (port->offset == port->len) {
-		unsigned int len;
-		if (!port->in_vq->vq_ops->get_buf(port->in_vq, &len))
+	if (port->inbuf_empty) {
+		if (!inbuf->used_len)
 			return 0;
-		BUG_ON(len != port->len);
-		port->offset = 0;
+		port->inbuf_offset = 0;
+		port->inbuf_empty = false;
 	}
 
 	/* You want more than we have to give?  Well, try wanting less! */
-	if (port->offset + count > port->len)
-		count = port->len - port->offset;
+	if (port->inbuf_offset + count > inbuf->used_len)
+		count = inbuf->used_len - port->inbuf_offset;
 
 	/* Copy across to their buffer and increment offset. */
-	memcpy(buf, port->inbuf + port->offset, count);
-	port->offset += count;
+	memcpy(buf, inbuf + port->inbuf_offset, count);
+	port->inbuf_offset += count;
 
 	/* Finished?  Re-register buffer so Host will use it again. */
-	if (port->offset == port->len) {
-		/* Should always be able to add one buffer to an empty queue. */
-		if (add_inbuf(port) < 0)
+	if (port->inbuf_offset == inbuf->used_len) {
+		port->inbuf_empty = true;
+		/* Should always be able to add one buffer to empty queue. */
+		if (add_buffer(inbuf, &port->inbufs, port, complete_poll_hvc,
+			       inbuf->data, inbuf->len) < 0)
 			BUG();
 		port->in_vq->vq_ops->kick(port->in_vq);
 	}
@@ -208,17 +250,29 @@ static void notifier_del_vio(struct hvc_
 	hp->irq_requested = 0;
 }
 
+static void hvc_output_done(struct virtqueue *vq)
+{
+	struct buffer *buf;
+	unsigned int len;
+
+	while ((buf = vq->vq_ops->get_buf(vq, &len)) != NULL) {
+		BUG_ON(len);
+		/* Remove from list first, in case ->complete frees it */
+		list_del(&buf->list);
+		buf->complete(buf, buf->len);
+	}
+}
+
 static void hvc_handle_input(struct virtqueue *vq)
 {
-	struct ports *ports = vq->vdev->priv;
-	unsigned int i;
-	bool activity = false;
+	struct buffer *buf;
+	unsigned int len;
 
-	for (i = 0; i < ports->num_ports; i++)
-		activity |= hvc_poll(ports->port[i].hvc);
-
-	if (activity)
-		hvc_kick();
+	while ((buf = vq->vq_ops->get_buf(vq, &len)) != NULL) {
+		/* Remove from list first, in case ->complete frees it */
+		list_del(&buf->list);
+		buf->complete(buf, len);
+	}
 }
 
 /* The operations for the console. */
@@ -233,32 +287,17 @@ static struct hv_ops hv_ops = {
 static struct ports *__devinit alloc_ports(unsigned int num)
 {
 	struct ports *ports;
-	int i;
 
 	ports = kmalloc(sizeof *ports + sizeof(ports->port) * num, GFP_KERNEL);
 	if (!ports)
 		return NULL;
 
 	ports->num_ports = num;
-	for (i = 0; i < ports->num_ports; i++) {
-		ports->port[i].len = PAGE_SIZE;
-		ports->port[i].inbuf = kmalloc(ports->port[i].len, GFP_KERNEL);
-		if (unlikely(!ports->port[i].inbuf)) {
-			while (--i >= 0)
-				kfree(ports->port[i].inbuf);
-			kfree(ports);
-			return NULL;
-		}
-	}
 	return ports;
 }
 
 static void free_ports(struct ports *ports)
 {
-	unsigned int i;
-
-	for (i = 0; i < ports->num_ports; i++)
-		kfree(ports->port[i].inbuf);
 	kfree(ports);
 }
 
@@ -271,12 +310,13 @@ static void free_ports(struct ports *por
  * 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[] = { hvc_handle_input, hvc_output_done };
 	const char *names[] = { "input", "output" };
 	struct virtqueue *vqs[2];
 	int err;
 	struct ports *ports;
 	struct port *port;
+	char *buffer;
 
 	ports = alloc_ports(1);
 	if (!ports) {
@@ -287,10 +327,16 @@ static int __devinit virtcons_probe(stru
 	/* Convenience variable since we only have one port. */
 	port = &ports->port[0];
 
+	buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buffer) {
+		err = -ENOMEM;
+		goto free;
+	}
+
 	/* Find the queues. */
 	err = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names);
 	if (err)
-		goto free;
+		goto free_buffer;
 
 	port->in_vq = vqs[0];
 	port->out_vq = vqs[1];
@@ -320,11 +366,17 @@ static int __devinit virtcons_probe(stru
 	spin_unlock_irq(&ports_by_vtermno_lock);
 
 	/* Register the input buffer the first time. */
-	add_inbuf(port);
+	port->inbuf_offset = 0;
+	port->inbuf_empty = true;
+	if (add_buffer(&port->console_inbuf, &port->inbufs, port,
+		       complete_poll_hvc, buffer, PAGE_SIZE) < 0)
+		BUG();
 	return 0;
 
 free_vqs:
 	vdev->config->del_vqs(vdev);
+free_buffer:
+	kfree(buffer);
 free:
 	free_ports(ports);
 fail:
