The console could be flooded with data from the host; handle this situation by buffering the data. Signed-off-by: Amit Shah <amit.shah@xxxxxxxxxx> --- drivers/char/virtio_console.c | 210 +++++++++++++++++++++++++++++------------ 1 files changed, 149 insertions(+), 61 deletions(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 19c9729..585ad3c 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -9,9 +9,6 @@ * 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 * * This program is free software; you can redistribute it and/or modify @@ -30,6 +27,8 @@ */ #include <linux/err.h> #include <linux/init.h> +#include <linux/list.h> +#include <linux/spinlock.h> #include <linux/virtio.h> #include <linux/virtio_console.h> #include "hvc_console.h" @@ -41,6 +40,17 @@ struct virtio_console_struct { */ struct work_struct rx_work; + /* Buffer management */ + struct list_head unused_read_head; + 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; + /*D:340 * These represent our input and output console queues, * and the virtio operations for them. @@ -48,14 +58,22 @@ struct virtio_console_struct { struct virtqueue *in_vq, *out_vq; struct virtio_device *vdev; - /* This is our input buffer, and how much data is left in it. */ - unsigned int in_len; - char *in, *inbuf; - /* The hvc device */ struct hvc_struct *hvc; }; +/* This struct holds individual buffers received from the host */ +struct virtio_console_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; +}; + struct virtio_console_struct virtconsole; /*D:310 The put_chars() callback is pretty straightforward. @@ -88,58 +106,65 @@ static int put_chars(u32 vtermno, const char *buf, int count) return count; } -/* Create a scatter-gather list representing our input buffer and put it in the - * queue. */ -static void add_inbuf(void) +/* + * Give out the data that's requested from the buffers that we have + * queued up. + */ +static ssize_t fill_readbuf(char *out_buf, size_t out_count) { - struct virtqueue *in_vq; - struct scatterlist sg[1]; - - sg_init_one(sg, virtconsole.inbuf, PAGE_SIZE); + struct virtio_console_port_buffer *buf, *buf2; + ssize_t out_offset, ret; - in_vq = virtconsole.in_vq; - /* 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, virtconsole.inbuf) < 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, &virtconsole.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(&virtconsole.readbuf_list_lock); + list_del(&buf->list); + spin_unlock(&virtconsole.readbuf_list_lock); + kfree(buf->buf); + kfree(buf); + } + if (!out_count) + break; + } + return out_offset; } -/*D:350 get_chars() is the callback from the hvc_console infrastructure when +/*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. */ + * 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 virtqueue *in_vq; - - in_vq = virtconsole.in_vq; /* If we don't have an input queue yet, we can't get input. */ - BUG_ON(!in_vq); - - /* No buffer? Try to get one. */ - if (!virtconsole.in_len) { - virtconsole.in = in_vq->vq_ops->get_buf(in_vq, - &virtconsole.in_len); - if (!virtconsole.in) - return 0; - } - - /* You want more than we have to give? Well, try wanting less! */ - if (virtconsole.in_len < count) - count = virtconsole.in_len; + BUG_ON(!virtconsole.in_vq); - /* Copy across to their buffer and increment offset. */ - memcpy(buf, virtconsole.in, count); - virtconsole.in += count; - virtconsole.in_len -= count; + if (list_empty(&virtconsole.readbuf_head)) + return 0; - /* Finished? Re-register buffer so Host will use it again. */ - if (virtconsole.in_len == 0) - add_inbuf(); - - return count; + return fill_readbuf(buf, count); } /*:*/ @@ -203,14 +228,82 @@ int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int)) return hvc_instantiate(0, 0, &virtio_cons); } +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->list, 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); +} + static void virtio_console_rx_work_handler(struct work_struct *work) { struct virtio_console_struct *vcon; + struct virtio_console_port_buffer *buf; + struct virtqueue *vq; + unsigned int tmplen; vcon = container_of(work, struct virtio_console_struct, rx_work); - if (hvc_poll(vcon->hvc)) + vq = vcon->in_vq; + while ((buf = vq->vq_ops->get_buf(vq, &tmplen))) { + /* The buffer is no longer unused */ + list_del(&buf->list); + + buf->len = tmplen; + buf->offset = 0; + + spin_lock(&virtconsole.readbuf_list_lock); + list_add_tail(&buf->list, &virtconsole.readbuf_head); + spin_unlock(&virtconsole.readbuf_list_lock); + } + if (hvc_poll(virtconsole.hvc)) hvc_kick(); + + /* Allocate buffers for all the ones that got used up */ + fill_receive_queue(&virtconsole); } static void rx_intr(struct virtqueue *vq) @@ -238,19 +331,12 @@ static int __devinit virtcons_probe(struct virtio_device *vdev) } virtconsole.vdev = vdev; - /* This is the scratch page we use to receive console input */ - virtconsole.inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); - if (!virtconsole.inbuf) { - err = -ENOMEM; - goto fail; - } - /* 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; + goto fail; virtconsole.in_vq = vqs[0]; virtconsole.out_vq = vqs[1]; @@ -263,8 +349,14 @@ static int __devinit virtcons_probe(struct virtio_device *vdev) */ virtio_cons.put_chars = put_chars; + spin_lock_init(&virtconsole.readbuf_list_lock); + INIT_LIST_HEAD(&virtconsole.readbuf_head); + INIT_LIST_HEAD(&virtconsole.unused_read_head); + INIT_WORK(&virtconsole.rx_work, &virtio_console_rx_work_handler); + fill_receive_queue(&virtconsole); + /* 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 @@ -280,14 +372,10 @@ static int __devinit virtcons_probe(struct virtio_device *vdev) goto free_vqs; } - /* Register the input buffer the first time. */ - add_inbuf(); return 0; free_vqs: vdev->config->del_vqs(vdev); -free: - kfree(virtconsole.inbuf); fail: return err; } -- 1.6.2.5 _______________________________________________ Virtualization mailing list Virtualization@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linux-foundation.org/mailman/listinfo/virtualization