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

We use the virtio queue ring to maintain our buffer. This buffer length
is set by the host while initializing the queue.

Signed-off-by: Amit Shah <[email protected]>
---
 drivers/char/virtio_console.c |  172 +++++++++++++++++++++++++++++++----------
 1 files changed, 131 insertions(+), 41 deletions(-)

diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index e4b6a37..2ab15a5 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -100,6 +100,13 @@ struct port {
        /* The current buffer from which data has to be fed to readers */
        struct port_buffer *inbuf;
 
+       /*
+        * To protect the operations on the in_vq associated with this
+        * port.  Has to be a spinlock because it can be called from
+        * interrupt context (get_char()).
+        */
+       spinlock_t inbuf_lock;
+
        /* The IO vqs for this port */
        struct virtqueue *in_vq, *out_vq;
 
@@ -132,6 +139,25 @@ out:
        return port;
 }
 
+static struct port *find_port_by_vq(struct ports_device *portdev,
+                                   struct virtqueue *vq)
+{
+       struct port *port;
+       struct console *cons;
+       unsigned long flags;
+
+       spin_lock_irqsave(&pdrvdata_lock, flags);
+       list_for_each_entry(cons, &pdrvdata.consoles, list) {
+               port = container_of(cons, struct port, cons);
+               if (port->in_vq == vq || port->out_vq == vq)
+                       goto out;
+       }
+       port = NULL;
+out:
+       spin_unlock_irqrestore(&pdrvdata_lock, flags);
+       return port;
+}
+
 static void free_buf(struct port_buffer *buf)
 {
        kfree(buf->buf);
@@ -181,15 +207,75 @@ static void *get_inbuf(struct port *port)
  *
  * Callers should take appropriate locks.
  */
-static void add_inbuf(struct virtqueue *vq, struct port_buffer *buf)
+static int add_inbuf(struct virtqueue *vq, struct port_buffer *buf)
 {
        struct scatterlist sg[1];
+       int ret;
 
        sg_init_one(sg, buf->buf, buf->size);
 
-       if (vq->vq_ops->add_buf(vq, sg, 0, 1, buf) < 0)
-               BUG();
+       ret = vq->vq_ops->add_buf(vq, sg, 0, 1, buf);
        vq->vq_ops->kick(vq);
+       return ret;
+}
+
+static bool port_has_data(struct port *port)
+{
+       unsigned long flags;
+       bool ret;
+
+       spin_lock_irqsave(&port->inbuf_lock, flags);
+       if (port->inbuf) {
+               ret = true;
+               goto out;
+       }
+       port->inbuf = get_inbuf(port);
+       if (port->inbuf) {
+               ret = true;
+               goto out;
+       }
+       ret = false;
+out:
+       spin_unlock_irqrestore(&port->inbuf_lock, flags);
+       return ret;
+}
+
+/*
+ * Give out the data that's requested from the buffers that we have
+ * queued up.
+ */
+static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count)
+{
+       struct port_buffer *buf;
+       ssize_t out_offset, ret;
+       unsigned long flags;
+
+       out_offset = 0;
+       while (out_count && port_has_data(port)) {
+               size_t copy_size;
+
+               buf = port->inbuf;
+               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);
+
+               ret = copy_size;
+               buf->offset += ret;
+               out_offset += ret;
+               out_count -= ret;
+
+               if (buf->offset == buf->len) {
+                       spin_lock_irqsave(&port->inbuf_lock, flags);
+                       port->inbuf = NULL;
+
+                       if (add_inbuf(port->in_vq, buf) < 0)
+                               free_buf(buf);
+                       spin_unlock_irqrestore(&port->inbuf_lock, flags);
+               }
+       }
+       return out_offset;
 }
 
 /*
@@ -234,9 +320,8 @@ static int put_chars(u32 vtermno, const char *buf, int 
count)
  * 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)
 {
@@ -249,25 +334,7 @@ static int get_chars(u32 vtermno, char *buf, int count)
        /* 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->inbuf->offset == port->inbuf->len) {
-               if (!get_inbuf(port))
-                       return 0;
-       }
-
-       /* You want more than we have to give?  Well, try wanting less! */
-       if (port->inbuf->offset + count > port->inbuf->len)
-               count = port->inbuf->len - port->inbuf->offset;
-
-       /* Copy across to their buffer and increment offset. */
-       memcpy(buf, port->inbuf->buf + port->inbuf->offset, count);
-       port->inbuf->offset += count;
-
-       /* Finished?  Re-register buffer so Host will use it again. */
-       if (port->inbuf->offset == port->inbuf->len)
-               add_inbuf(port->in_vq, port->inbuf);
-
-       return count;
+       return fill_readbuf(port, buf, count);
 }
 
 static void resize_console(struct port *port)
@@ -314,13 +381,19 @@ static void notifier_del_vio(struct hvc_struct *hp, int 
data)
 
 static void hvc_handle_input(struct virtqueue *vq)
 {
-       struct console *cons;
-       bool activity = false;
+       struct port *port;
+       unsigned long flags;
 
-       list_for_each_entry(cons, &pdrvdata.consoles, list)
-               activity |= hvc_poll(cons->hvc);
+       port = find_port_by_vq(vq->vdev->priv, vq);
+       if (!port)
+               return;
+
+       spin_lock_irqsave(&port->inbuf_lock, flags);
+       if (!port->inbuf)
+               port->inbuf = get_inbuf(port);
+       spin_unlock_irqrestore(&port->inbuf_lock, flags);
 
-       if (activity)
+       if (hvc_poll(port->cons.hvc))
                hvc_kick();
 }
 
@@ -385,6 +458,27 @@ int __devinit init_port_console(struct port *port)
        return 0;
 }
 
+static void fill_queue(struct virtqueue *vq, spinlock_t *lock)
+{
+       struct port_buffer *buf;
+       int ret;
+
+       do {
+               buf = alloc_buf(PAGE_SIZE);
+               if (!buf)
+                       break;
+
+               spin_lock_irq(lock);
+               ret = add_inbuf(vq, buf);
+               if (ret < 0) {
+                       spin_unlock_irq(lock);
+                       free_buf(buf);
+                       break;
+               }
+               spin_unlock_irq(lock);
+       } while (ret > 0);
+}
+
 static int __devinit add_port(struct ports_device *portdev)
 {
        struct port *port;
@@ -397,26 +491,22 @@ static int __devinit add_port(struct ports_device 
*portdev)
        }
 
        port->portdev = portdev;
+
+       port->inbuf = NULL;
+
        port->in_vq = portdev->in_vqs[0];
        port->out_vq = portdev->out_vqs[0];
 
-       port->inbuf = alloc_buf(PAGE_SIZE);
-       if (!port->inbuf) {
-               err = -ENOMEM;
-               goto free_port;
-       }
+       spin_lock_init(&port->inbuf_lock);
+
+       fill_queue(port->in_vq, &port->inbuf_lock);
 
        err = init_port_console(port);
        if (err)
-               goto free_inbuf;
-
-       /* Register the input buffer the first time. */
-       add_inbuf(port->in_vq, port->inbuf);
+               goto free_port;
 
        return 0;
 
-free_inbuf:
-       free_buf(port->inbuf);
 free_port:
        kfree(port);
 fail:
-- 
1.6.2.5

_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linux-foundation.org/mailman/listinfo/virtualization

Reply via email to