virtio: console: Add support for nonblocking write()s
From: Amit Shah <amit.shah@redhat.com>

If the host port is not open, a write() should either just return if the
file is opened in non-blocking mode, or block till the host port is
opened.

Also, don't spin till host consumes data for nonblocking ports. For
non-blocking ports, we can do away with the spinning and reclaim the
buffers consumed by the host on the next write call or on the condition
that'll make poll return.

Signed-off-by: Amit Shah <amit.shah@redhat.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
---
 drivers/char/virtio_console.c |  119 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 111 insertions(+), 8 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
@@ -159,6 +159,9 @@ struct port {
 	 */
 	spinlock_t inbuf_lock;
 
+	/* Protect the operations on the out_vq. */
+	spinlock_t outvq_lock;
+
 	/* The IO vqs for this port */
 	struct virtqueue *in_vq, *out_vq;
 
@@ -184,6 +187,8 @@ struct port {
 	/* The 'id' to identify the port with the Host */
 	u32 id;
 
+	bool outvq_full;
+
 	/* Is the host device open */
 	bool host_connected;
 
@@ -405,15 +410,33 @@ static ssize_t send_control_msg(struct p
 	return __send_control_msg(port->portdev, port->id, event, value);
 }
 
-static ssize_t send_buf(struct port *port, void *in_buf, size_t in_count)
+/* Callers must take the port->outvq_lock */
+static void reclaim_consumed_buffers(struct port *port)
+{
+	void *buf;
+	unsigned int len;
+
+	while ((buf = virtqueue_get_buf(port->out_vq, &len))) {
+		kfree(buf);
+		port->outvq_full = false;
+	}
+}
+
+static ssize_t send_buf(struct port *port, void *in_buf, size_t in_count,
+			bool nonblock)
 {
 	struct scatterlist sg[1];
 	struct virtqueue *out_vq;
 	ssize_t ret;
+	unsigned long flags;
 	unsigned int len;
 
 	out_vq = port->out_vq;
 
+	spin_lock_irqsave(&port->outvq_lock, flags);
+
+	reclaim_consumed_buffers(port);
+
 	sg_init_one(sg, in_buf, in_count);
 	ret = virtqueue_add_buf(out_vq, sg, 1, 0, in_buf);
 
@@ -422,14 +445,29 @@ static ssize_t send_buf(struct port *por
 
 	if (ret < 0) {
 		in_count = 0;
-		goto fail;
+		goto done;
 	}
 
-	/* Wait till the host acknowledges it pushed out the data we sent. */
+	if (ret == 0)
+		port->outvq_full = true;
+
+	if (nonblock)
+		goto done;
+
+	/*
+	 * Wait till the host acknowledges it pushed out the data we
+	 * sent.  This is done for ports in blocking mode or for data
+	 * from the hvc_console; the tty operations are performed with
+	 * spinlocks held so we can't sleep here.
+	 */
 	while (!virtqueue_get_buf(out_vq, &len))
 		cpu_relax();
-fail:
-	/* We're expected to return the amount of data we wrote */
+done:
+	spin_unlock_irqrestore(&port->outvq_lock, flags);
+	/*
+	 * We're expected to return the amount of data we wrote -- all
+	 * of it
+	 */
 	return in_count;
 }
 
@@ -484,6 +522,25 @@ static bool will_read_block(struct port 
 	return !port_has_data(port) && port->host_connected;
 }
 
+static bool will_write_block(struct port *port)
+{
+	bool ret;
+
+	if (!port->host_connected)
+		return true;
+
+	spin_lock_irq(&port->outvq_lock);
+	/*
+	 * Check if the Host has consumed any buffers since we last
+	 * sent data (this is only applicable for nonblocking ports).
+	 */
+	reclaim_consumed_buffers(port);
+	ret = port->outvq_full;
+	spin_unlock_irq(&port->outvq_lock);
+
+	return ret;
+}
+
 static ssize_t port_fops_read(struct file *filp, char __user *ubuf,
 			      size_t count, loff_t *offp)
 {
@@ -530,9 +587,22 @@ static ssize_t port_fops_write(struct fi
 	struct port *port;
 	char *buf;
 	ssize_t ret;
+	bool nonblock;
 
 	port = filp->private_data;
 
+	nonblock = filp->f_flags & O_NONBLOCK;
+
+	if (will_write_block(port)) {
+		if (nonblock)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible(port->waitqueue,
+					       !will_write_block(port));
+		if (ret < 0)
+			return ret;
+	}
+
 	count = min((size_t)(32 * 1024), count);
 
 	buf = kmalloc(count, GFP_KERNEL);
@@ -545,9 +615,14 @@ static ssize_t port_fops_write(struct fi
 		goto free_buf;
 	}
 
-	ret = send_buf(port, buf, count);
+	ret = send_buf(port, buf, count, nonblock);
+
+	if (nonblock && ret > 0)
+		goto out;
+
 free_buf:
 	kfree(buf);
+out:
 	return ret;
 }
 
@@ -562,7 +637,7 @@ static unsigned int port_fops_poll(struc
 	ret = 0;
 	if (port->inbuf)
 		ret |= POLLIN | POLLRDNORM;
-	if (port->host_connected)
+	if (!will_write_block(port))
 		ret |= POLLOUT;
 	if (!port->host_connected)
 		ret |= POLLHUP;
@@ -586,6 +661,10 @@ static int port_fops_release(struct inod
 
 	spin_unlock_irq(&port->inbuf_lock);
 
+	spin_lock_irq(&port->outvq_lock);
+	reclaim_consumed_buffers(port);
+	spin_unlock_irq(&port->outvq_lock);
+
 	return 0;
 }
 
@@ -614,6 +693,15 @@ static int port_fops_open(struct inode *
 	port->guest_connected = true;
 	spin_unlock_irq(&port->inbuf_lock);
 
+	spin_lock_irq(&port->outvq_lock);
+	/*
+	 * There might be a chance that we missed reclaiming a few
+	 * buffers in the window of the port getting previously closed
+	 * and opening now.
+	 */
+	reclaim_consumed_buffers(port);
+	spin_unlock_irq(&port->outvq_lock);
+
 	/* Notify host of port being opened */
 	send_control_msg(filp->private_data, VIRTIO_CONSOLE_PORT_OPEN, 1);
 
@@ -654,7 +742,7 @@ static int put_chars(u32 vtermno, const 
 	if (!port)
 		return -EPIPE;
 
-	return send_buf(port, (void *)buf, count);
+	return send_buf(port, (void *)buf, count, false);
 }
 
 /*
@@ -846,6 +934,8 @@ static ssize_t debugfs_read(struct file 
 	out_offset += snprintf(buf + out_offset, out_count - out_offset,
 			       "host_connected: %d\n", port->host_connected);
 	out_offset += snprintf(buf + out_offset, out_count - out_offset,
+			       "outvq_full: %d\n", port->outvq_full);
+	out_offset += snprintf(buf + out_offset, out_count - out_offset,
 			       "is_console: %s\n",
 			       is_console_port(port) ? "yes" : "no");
 	out_offset += snprintf(buf + out_offset, out_count - out_offset,
@@ -912,6 +1002,8 @@ static int add_port(struct ports_device 
 
 	port->host_connected = port->guest_connected = false;
 
+	port->outvq_full = false;
+
 	port->in_vq = portdev->in_vqs[port->id];
 	port->out_vq = portdev->out_vqs[port->id];
 
@@ -936,6 +1028,7 @@ static int add_port(struct ports_device 
 	}
 
 	spin_lock_init(&port->inbuf_lock);
+	spin_lock_init(&port->outvq_lock);
 	init_waitqueue_head(&port->waitqueue);
 
 	/* Fill the in_vq with buffers so the host can send us data. */
@@ -1031,6 +1124,8 @@ static int remove_port(struct port *port
 	/* Remove unused data this port might have received. */
 	discard_port_data(port);
 
+	reclaim_consumed_buffers(port);
+
 	/* Remove buffers we queued up for the Host to send us data in. */
 	while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
 		free_buf(buf);
@@ -1102,6 +1197,14 @@ static void handle_control_message(struc
 	case VIRTIO_CONSOLE_PORT_OPEN:
 		port->host_connected = cpkt->value;
 		wake_up_interruptible(&port->waitqueue);
+		/*
+		 * If the host port got closed and the host had any
+		 * unconsumed buffers, we'll be able to reclaim them
+		 * now.
+		 */
+		spin_lock_irq(&port->outvq_lock);
+		reclaim_consumed_buffers(port);
+		spin_unlock_irq(&port->outvq_lock);
 		break;
 	case VIRTIO_CONSOLE_PORT_NAME:
 		/*