[PATCH 12/28] virtio: console: Buffer data that comes in from the host

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

 



The console could be flooded with data from the host; handle
this situation by buffering the data.

We move from the per-port inbuf to a per-device queue of buffers,
shared by all the ports for that device, ready to receive host data.

Signed-off-by: Amit Shah <amit.shah@xxxxxxxxxx>
---
 drivers/char/virtio_console.c |  219 +++++++++++++++++++++++++++++++----------
 1 files changed, 168 insertions(+), 51 deletions(-)

diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index a80d15c..e8dabae 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -65,6 +65,23 @@ struct ports_device {
 	 * interrupt
 	 */
 	struct work_struct rx_work;
+
+	struct list_head unused_read_head;
+
+	/* To protect the list of unused read buffers and the in_vq */
+	spinlock_t read_list_lock;
+};
+
+/* This struct holds individual buffers received for each port */
+struct port_buffer {
+	struct list_head list;
+
+	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 */
@@ -72,9 +89,15 @@ struct port {
 	/* Pointer to the parent virtio_console device */
 	struct ports_device *portdev;
 
-	/* This is our input buffer, and how much data is left in it. */
-	char *inbuf;
-	unsigned int used_len, offset;
+	/* 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
+	 * (get_char())
+	 */
+	spinlock_t readbuf_list_lock;
 
 	/* For console ports, hvc != NULL and these are valid. */
 	/* The hvc device */
@@ -145,67 +168,71 @@ static int put_chars(u32 vtermno, const char *buf, int count)
 }
 
 /*
- * Create a scatter-gather list representing our input buffer and put
- * it in the queue.
+ * Give out the data that's requested from the buffers that we have
+ * queued up.
  */
-static void add_inbuf(struct port *port)
+static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count)
 {
-	struct virtqueue *in_vq;
-	struct scatterlist sg[1];
-
-	sg_init_one(sg, port->inbuf, PAGE_SIZE);
+	struct port_buffer *buf, *buf2;
+	ssize_t out_offset, ret;
+	unsigned long flags;
 
-	in_vq = port->portdev->in_vq;
-	/* Should always be able to add one buffer to an empty queue. */
-	if (in_vq->vq_ops->add_buf(in_vq, sg, 0, 1, port) < 0)
-		BUG();
-	in_vq->vq_ops->kick(in_vq);
+	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.
+	 */
+	list_for_each_entry_safe(buf, buf2, &port->readbuf_head, list) {
+		size_t copy_size;
+
+		copy_size = out_count;
+		if (copy_size > buf->len - buf->offset)
+			copy_size = buf->len - buf->offset;
+
+		memcpy(out_buf + out_offset, buf->buf + buf->offset, copy_size);
+
+		/* Return the number of bytes actually copied */
+		ret = copy_size;
+		buf->offset += ret;
+		out_offset += ret;
+		out_count -= ret;
+
+		if (buf->len - buf->offset == 0) {
+			spin_lock_irqsave(&port->readbuf_list_lock, flags);
+			list_del(&buf->list);
+			spin_unlock_irqrestore(&port->readbuf_list_lock, flags);
+			kfree(buf->buf);
+			kfree(buf);
+		}
+		if (!out_count)
+			break;
+	}
+	return out_offset;
 }
 
 /*
  * 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.
+ * We call out to fill_readbuf that gets us the required data from the
+ * buffers that are queued up.
  */
 static int get_chars(u32 vtermno, char *buf, int count)
 {
 	struct port *port;
-	struct virtqueue *in_vq;
 
 	port = find_port_by_vtermno(vtermno);
 	if (!port)
 		return 0;
 
-	in_vq = port->portdev->in_vq;
 	/* If we don't have an input queue yet, we can't get input. */
-	BUG_ON(!in_vq);
-
-	/* No more in buffer?  See if they've (re)used it. */
-	if (port->offset == port->used_len) {
-		unsigned int len;
-
-		if (!in_vq->vq_ops->get_buf(in_vq, &len))
-			return 0;
-		port->used_len = len;
-		port->offset = 0;
-	}
-
-	/* You want more than we have to give?  Well, try wanting less! */
-	if (port->offset + count > port->used_len)
-		count = port->used_len - port->offset;
+	BUG_ON(!port->portdev->in_vq);
 
-	/* Copy across to their buffer and increment offset. */
-	memcpy(buf, port->inbuf + port->offset, count);
-	port->offset += count;
-
-	/* Finished?  Re-register buffer so Host will use it again. */
-	if (port->offset == port->used_len)
-		add_inbuf(port);
+	if (list_empty(&port->readbuf_head))
+		return 0;
 
-	return count;
+	return fill_readbuf(port, buf, count);
 }
 
 static void resize_console(struct port *port)
@@ -274,16 +301,103 @@ int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
 	return hvc_instantiate(0, 0, &hv_ops);
 }
 
+static struct port_buffer *get_buf(size_t buf_size)
+{
+	struct 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_receive_queue(struct ports_device *portdev)
+{
+	struct scatterlist sg[1];
+	struct port_buffer *buf;
+	struct virtqueue *vq;
+	int buf_size, ret;
+
+	vq = portdev->in_vq;
+	buf_size = PAGE_SIZE;
+	do {
+		buf = get_buf(buf_size);
+		if (!buf)
+			break;
+		sg_init_one(sg, buf->buf, buf_size);
+
+		spin_lock(&portdev->read_list_lock);
+		ret = vq->vq_ops->add_buf(vq, sg, 0, 1, buf);
+		if (ret < 0) {
+			spin_unlock(&portdev->read_list_lock);
+			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->list, &portdev->unused_read_head);
+		spin_unlock(&portdev->read_list_lock);
+	} while (ret > 0);
+
+	spin_lock(&portdev->read_list_lock);
+	vq->vq_ops->kick(vq);
+	spin_unlock(&portdev->read_list_lock);
+}
+
+static void *get_incoming_buf(struct ports_device *portdev,
+			      unsigned int *len)
+{
+	struct port_buffer *buf;
+	struct virtqueue *vq;
+
+	vq = portdev->in_vq;
+
+	spin_lock(&portdev->read_list_lock);
+	buf = vq->vq_ops->get_buf(vq, len);
+	if (buf) {
+		/* The buffer is no longer unused */
+		list_del(&buf->list);
+	}
+	spin_unlock(&portdev->read_list_lock);
+	return buf;
+}
+
 static void rx_work_handler(struct work_struct *work)
 {
+	struct ports_device *portdev;
 	struct port *port;
+	struct port_buffer *buf;
+	unsigned int tmplen;
 	bool activity = false;
 
-	list_for_each_entry(port, &pdrvdata.consoles, list)
-		activity |= hvc_poll(port->hvc);
+	portdev = container_of(work, struct ports_device, rx_work);
+
+	/* We currently have only one port */
+	port = find_port_by_vtermno(0);
+	while ((buf = get_incoming_buf(portdev, &tmplen))) {
+		buf->len = tmplen;
+		buf->offset = 0;
+
+		spin_lock_irq(&port->readbuf_list_lock);
+		list_add_tail(&buf->list, &port->readbuf_head);
+		spin_unlock_irq(&port->readbuf_list_lock);
 
+		activity |= hvc_poll(port->hvc);
+	}
 	if (activity)
 		hvc_kick();
+
+	fill_receive_queue(portdev);
 }
 
 static void rx_intr(struct virtqueue *vq)
@@ -305,9 +419,6 @@ static int __devinit add_port(struct ports_device *portdev)
 		goto fail;
 
 	port->portdev = portdev;
-	port->inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
-	if (!port->inbuf)
-		goto free;
 
 	/*
 	 * The first argument of hvc_alloc() is the virtual console
@@ -327,15 +438,15 @@ static int __devinit add_port(struct ports_device *portdev)
 		goto free;
 	}
 
+	spin_lock_init(&port->readbuf_list_lock);
+	INIT_LIST_HEAD(&port->readbuf_head);
+
 	/* Add to vtermno list. */
 	spin_lock_irq(&pdrvdata_lock);
 	pdrvdata.next_vtermno++;
 	list_add(&port->list, &pdrvdata.consoles);
 	spin_unlock_irq(&pdrvdata_lock);
 
-	/* Register the input buffer the first time. */
-	add_inbuf(port);
-
 	return 0;
 free:
 	kfree(port);
@@ -372,8 +483,14 @@ static int __devinit virtcons_probe(struct virtio_device *vdev)
 	portdev->in_vq = vqs[0];
 	portdev->out_vq = vqs[1];
 
+	spin_lock_init(&portdev->read_list_lock);
+
+	INIT_LIST_HEAD(&portdev->unused_read_head);
+
 	INIT_WORK(&portdev->rx_work, &rx_work_handler);
 
+	fill_receive_queue(portdev);
+
 	/* We only have one port. */
 	err = add_port(portdev);
 	if (err)
-- 
1.6.2.5

_______________________________________________
Virtualization mailing list
Virtualization@xxxxxxxxxxxxxxxxxxxxxxxxxx
https://lists.linux-foundation.org/mailman/listinfo/virtualization

[Index of Archives]     [KVM Development]     [Libvirt Development]     [Libvirt Users]     [CentOS Virtualization]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux