diff options
author | Amit Shah <amit.shah@redhat.com> | 2010-01-18 14:45:12 +0100 |
---|---|---|
committer | Rusty Russell <rusty@rustcorp.com.au> | 2010-02-24 04:52:50 +0100 |
commit | 203baab8ba3195dd929473ba95b91c2b838833e6 (patch) | |
tree | e6720bb993a46f53bd4607d55701606c754f2c00 /drivers/char | |
parent | virtio: console: Separate out find_vqs operation into a different function (diff) | |
download | linux-203baab8ba3195dd929473ba95b91c2b838833e6.tar.xz linux-203baab8ba3195dd929473ba95b91c2b838833e6.zip |
virtio: console: Introduce function to hand off data from host to readers
In preparation for serving data to userspace (generic ports) as well as
in-kernel users (hvc consoles), separate out the functionality common to
both in a 'fill_readbuf()' function.
Signed-off-by: Amit Shah <amit.shah@redhat.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/virtio_console.c | 142 |
1 files changed, 106 insertions, 36 deletions
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 75c5a3512ecd..5096d92f5b89 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,67 @@ 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; + + ret = false; + spin_lock_irqsave(&port->inbuf_lock, flags); + if (port->inbuf) + ret = true; + spin_unlock_irqrestore(&port->inbuf_lock, flags); + + return ret; +} + +/* + * Give out the data that's requested from the buffer that we have + * queued up. + */ +static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count) +{ + struct port_buffer *buf; + unsigned long flags; + + if (!out_count || !port_has_data(port)) + return 0; + + buf = port->inbuf; + if (out_count > buf->len - buf->offset) + out_count = buf->len - buf->offset; + + memcpy(out_buf, buf->buf + buf->offset, out_count); + + /* Return the number of bytes actually copied */ + buf->offset += out_count; + + if (buf->offset == buf->len) { + /* + * We're done using all the data in this buffer. + * Re-queue so that the Host can send us more data. + */ + spin_lock_irqsave(&port->inbuf_lock, flags); + port->inbuf = NULL; + + if (add_inbuf(port->in_vq, buf) < 0) + dev_warn(&port->portdev->vdev->dev, "failed add_buf\n"); + + spin_unlock_irqrestore(&port->inbuf_lock, flags); + } + return out_count; } /* @@ -234,9 +312,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 +326,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 +373,18 @@ 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; + + port = find_port_by_vq(vq->vdev->priv, vq); + if (!port) + return; - list_for_each_entry(cons, &pdrvdata.consoles, list) - activity |= hvc_poll(cons->hvc); + spin_lock_irqsave(&port->inbuf_lock, flags); + port->inbuf = get_inbuf(port); + spin_unlock_irqrestore(&port->inbuf_lock, flags); - if (activity) + if (hvc_poll(port->cons.hvc)) hvc_kick(); } @@ -388,6 +452,7 @@ int __devinit init_port_console(struct port *port) static int __devinit add_port(struct ports_device *portdev) { struct port *port; + struct port_buffer *inbuf; int err; port = kmalloc(sizeof(*port), GFP_KERNEL); @@ -397,26 +462,31 @@ 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) { + spin_lock_init(&port->inbuf_lock); + + inbuf = alloc_buf(PAGE_SIZE); + if (!inbuf) { err = -ENOMEM; goto free_port; } + /* Register the input buffer the first time. */ + add_inbuf(port->in_vq, inbuf); + err = init_port_console(port); if (err) goto free_inbuf; - /* Register the input buffer the first time. */ - add_inbuf(port->in_vq, port->inbuf); - return 0; free_inbuf: - free_buf(port->inbuf); + free_buf(inbuf); free_port: kfree(port); fail: |