diff options
author | Ahmad Fatoum <ahmad@a3f.at> | 2021-02-22 08:06:01 +0100 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2021-02-22 10:24:33 +0100 |
commit | bdc87d8661b6650909de2fa1c4d23721f6a1ce1a (patch) | |
tree | ea5888a6333b78cae26bc62c2b0d73eec2b8bedd /drivers | |
parent | 2b772387518630af946e222e97af4dd925bce7b9 (diff) | |
download | barebox-bdc87d8661b6650909de2fa1c4d23721f6a1ce1a.tar.gz barebox-bdc87d8661b6650909de2fa1c4d23721f6a1ce1a.tar.xz |
serial: add basic VirtIO console driver
With this driver enabled, -device virtio-serial-device can now be passed
to Qemu for barebox to detect a VirtIO console device. If barebox is
passed as argument to the Qemu -kernel option, no device tree changes are
necessary.
Example:
$ qemu-system-arm -m 256M -M virt -nographic \
-kernel build/images/barebox-dt-2nd.img \
-device virtio-serial-device \
-chardev socket,path=/tmp/foo,server,nowait,id=foo \
-device virtconsole,chardev=foo,name=console.foo
Signed-off-by: Ahmad Fatoum <ahmad@a3f.at>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/serial/Kconfig | 8 | ||||
-rw-r--r-- | drivers/serial/Makefile | 1 | ||||
-rw-r--r-- | drivers/serial/virtio_console.c | 166 |
3 files changed, 175 insertions, 0 deletions
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 5c6f0e88e3..09434c1ba8 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -156,4 +156,12 @@ config DRIVER_SERIAL_LPUART default y bool "LPUART serial driver" +config VIRTIO_CONSOLE + tristate "Virtio console" + select VIRTIO + help + Virtio console for use with hypervisors. + + Also serves as a general-purpose serial device for data + transfer between the guest and host. endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 8a2abbbe45..7ff41cd5c7 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -22,3 +22,4 @@ obj-$(CONFIG_DRIVER_SERIAL_CADENCE) += serial_cadence.o obj-$(CONFIG_DRIVER_SERIAL_EFI_STDIO) += efi-stdio.o obj-$(CONFIG_DRIVER_SERIAL_DIGIC) += serial_digic.o obj-$(CONFIG_DRIVER_SERIAL_LPUART) += serial_lpuart.o +obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o diff --git a/drivers/serial/virtio_console.c b/drivers/serial/virtio_console.c new file mode 100644 index 0000000000..a1331035d9 --- /dev/null +++ b/drivers/serial/virtio_console.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2006, 2007, 2009 Rusty Russell, IBM Corporation + * Copyright (C) 2009, 2010, 2011 Red Hat, Inc. + * Copyright (C) 2009, 2010, 2011 Amit Shah <amit.shah@redhat.com> + * Copyright (C) 2021 Ahmad Fatoum + * + * This ridiculously simple implementation does a DMA transfer for + * every single character. On the plus side, we neither need to + * buffer RX or to wade through TX to turn LFs to CRLFs. + */ +#include <common.h> +#include <driver.h> +#include <init.h> +#include <linux/list.h> +#include <malloc.h> +#include <console.h> +#include <xfuncs.h> +#include <linux/spinlock.h> +#include <linux/virtio.h> +#include <linux/virtio_ring.h> +#include <linux/virtio_console.h> + +struct virtio_console { + struct console_device cdev; + struct virtqueue *in_vq, *out_vq; + char inbuf[1]; +}; + +static bool have_one; + +/* + * The put_chars() callback is pretty straightforward. + * + * We turn the characters into a scatter-gather list, add it to the + * output queue and then kick the Host. Then we sit here waiting for + * it to finish: inefficient in theory, but in practice + * implementations will do it immediately (lguest's Launcher does). + */ +static void put_chars(struct virtio_console *virtcons, const char *buf, int count) +{ + struct virtqueue *out_vq = virtcons->out_vq; + unsigned int len; + struct virtio_sg *sgs[1] = { + &(struct virtio_sg) { .addr = (void *)buf, .length = count } + }; + + /* + * add_buf wants a token to identify this buffer: we hand it + * any non-NULL pointer, since there's only ever one buffer. + */ + if (virtqueue_add(out_vq, sgs, 1, 0) >= 0) { + /* Tell Host to go! */ + virtqueue_kick(out_vq); + /* Chill out until it's done with the buffer. */ + while (!virtqueue_get_buf(out_vq, &len)) + cpu_relax(); + } +} + +static void virtcons_putc(struct console_device *cdev, char c) +{ + struct virtio_console *virtcons = container_of(cdev, struct virtio_console, cdev); + + return put_chars(virtcons, &c, 1); +} + +/* + * Create a scatter-gather list representing our input buffer and put + * it in the queue. + */ +static void add_inbuf(struct virtio_console *virtcons) +{ + struct virtio_sg *sgs[1] = { &(struct virtio_sg) { + .addr = virtcons->inbuf, .length = sizeof(virtcons->inbuf) } + }; + + /* We should always be able to add one buffer to an empty queue. */ + if (virtqueue_add(virtcons->in_vq, sgs, 0, 1) < 0) + BUG(); + virtqueue_kick(virtcons->in_vq); +} + +static int virtcons_tstc(struct console_device *cdev) +{ + struct virtio_console *virtcons = container_of(cdev, struct virtio_console, cdev); + + return virtqueue_poll(virtcons->in_vq, virtcons->in_vq->last_used_idx); +} + +static int virtcons_getc(struct console_device *cdev) +{ + struct virtio_console *virtcons = container_of(cdev, struct virtio_console, cdev); + char *in; + int ch; + + in = virtqueue_get_buf(virtcons->in_vq, NULL); + if (!in) + BUG(); + + ch = *in; + + add_inbuf(virtcons); + + return ch; +} + +static int virtcons_probe(struct virtio_device *vdev) +{ + struct virtqueue *vqs[2]; + struct virtio_console *virtcons; + int err; + + if (have_one) { + /* Neither multiport consoles (one virtio_device for multiple consoles) + * nor multiple consoles (one virtio_device per each console + * is supported. I would've expected: + * -chardev socket,path=/tmp/bar,server,nowait,id=bar \ + * -device virtconsole,chardev=bar,name=console.bar \ + * -device virtio-serial-device \ + * -chardev socket,path=/tmp/baz,server,nowait,id=baz \ + * -device virtconsole,chardev=baz,name=console.baz \ + * to just work, but it doesn't + */ + dev_warn(&vdev->dev, + "Multiple virtio-console devices not supported yet\n"); + return -EEXIST; + } + + /* Find the queues. */ + err = virtio_find_vqs(vdev, 2, vqs); + if (err) + return err; + + virtcons = xzalloc(sizeof(*virtcons)); + + virtcons->in_vq = vqs[0]; + virtcons->out_vq = vqs[1]; + + /* Register the input buffer the first time. */ + add_inbuf(virtcons); + + virtcons->cdev.dev = &vdev->dev; + virtcons->cdev.tstc = virtcons_tstc; + virtcons->cdev.getc = virtcons_getc; + virtcons->cdev.putc = virtcons_putc; + + have_one = true; + + return console_register(&virtcons->cdev); +} + +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_CONSOLE, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_console = { + .driver.name = "virtio_console", + .id_table = id_table, + .probe = virtcons_probe, +}; +device_virtio_driver(virtio_console); + +MODULE_DESCRIPTION("Virtio console driver"); +MODULE_LICENSE("GPL"); |