drivers tty: add okl4_vtty driver

Adds a driver which provides console and TTY using the OKL4
hypervisor pipe IPC facility.

Change-Id: Iccbb9eda626fd90d3cc6494c490e13d51edfe42a
Signed-off-by: Carl van Schaik <carl@cog.systems>
Signed-off-by: Neeraj Upadhyay <neeraju@codeaurora.org>
Signed-off-by: Ashok Kundurthi <askund@codeaurora.org>
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 741e966..53e3186 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -458,4 +458,21 @@
 	help
 	  FDC channel number to use for KGDB.
 
+config OKL4_VTTY
+	bool "Virtual TTY on the OKL4 Microvisor"
+	depends on OKL4_GUEST
+	select TTY_FLUSH_LOCAL_ECHO
+	default y
+	---help---
+	  This device provides character-level read-write access
+	  to the virtual console, usually connected to a serial-server which
+	  multiplexes output on a physical UART.
+
+config OKL4_VTTY_CONSOLE
+	bool "Console on OKL4 VTTY"
+	depends on OKL4_VTTY
+	default y
+	help
+	  Console support for OKL4 Microvisor virtual ttys.
+
 endif # TTY
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 5817e23..6ed793d 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -32,3 +32,4 @@
 obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o
 
 obj-y += ipwireless/
+obj-$(CONFIG_OKL4_VTTY)		+= okl4_vtty.o
diff --git a/drivers/tty/okl4_vtty.c b/drivers/tty/okl4_vtty.c
new file mode 100644
index 0000000..75e84b3
--- /dev/null
+++ b/drivers/tty/okl4_vtty.c
@@ -0,0 +1,882 @@
+/*
+ * drivers/char/okl4_vtty.c
+ *
+ * Copyright (c) 2012-2014 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ * Copyright (c) 2014-2017 Cog Systems Pty Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * OKL4 Microvisor Virtual TTY driver.
+ *
+ * Clients using this driver must have vclient names of the form
+ * "vtty%d", where %d is the tty number, which must be
+ * unique and less than MAX_VTTYS.
+ */
+
+/* #define DEBUG 1 */
+/* #define VERBOSE_DEBUG 1 */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+#include <linux/of.h>
+#include <linux/uaccess.h>
+#include <linux/seq_file.h>
+#include <clocksource/arm_arch_timer.h>
+#include <asm-generic/okl4_virq.h>
+
+#include <microvisor/microvisor.h>
+#if 0
+#include <asm/okl4-microvisor/okl4tags.h>
+#include <asm/okl4-microvisor/microvisor_bus.h>
+#include <asm/okl4-microvisor/virq.h>
+#endif
+
+#define DRIVER_NAME "okl4-vtty"
+#define DEVICE_NAME "vtty"
+#define DEVICE_PREFIX "ttyV"
+
+/* FIXME: Jira ticket SDK-138 - philipd. */
+#define MAX_VTTYS 8
+
+struct vtty_port {
+	bool exists;
+	int vtty_id;
+
+	bool read_throttled, write_full, irq_registered;
+	struct work_struct read_work;
+	spinlock_t write_lock;
+
+	/*
+	 * Buffer length is max_msg_size plus one u32, which encodes the
+	 * message length.
+	 */
+	char *read_buf;
+	int read_buf_pos, read_buf_len;
+	char *write_buf;
+	int write_buffered;
+	size_t max_msg_size;
+
+	okl4_kcap_t pipe_tx_kcap;
+	okl4_kcap_t pipe_rx_kcap;
+	int tx_irq;
+	int rx_irq;
+
+#ifdef CONFIG_OKL4_VTTY_CONSOLE
+	struct console console;
+#endif
+
+	struct device *dev;
+	struct tty_port port;
+};
+
+static struct workqueue_struct *read_workqueue;
+
+static struct vtty_port ports[MAX_VTTYS];
+
+static void
+vtty_read_irq(struct vtty_port *port)
+{
+	queue_work(read_workqueue, &port->read_work);
+}
+
+static int
+do_pipe_write(struct vtty_port *port, int count)
+{
+	okl4_error_t ret;
+	int send;
+
+	if (port->write_full)
+		return 0;
+
+	BUG_ON(count > port->max_msg_size);
+
+	*(u32 *)port->write_buf = count;
+	send = roundup(count + sizeof(u32), sizeof(u32));
+
+	ret = _okl4_sys_pipe_send(port->pipe_tx_kcap, send,
+			(void *)port->write_buf);
+
+	if (ret == OKL4_ERROR_PIPE_NOT_READY) {
+		okl4_pipe_control_t x = 0;
+
+		okl4_pipe_control_setdoop(&x, true);
+		okl4_pipe_control_setoperation(&x,
+			OKL4_PIPE_CONTROL_OP_SET_TX_READY);
+		_okl4_sys_pipe_control(port->pipe_tx_kcap, x);
+
+		ret = _okl4_sys_pipe_send(port->pipe_tx_kcap, send,
+				(void *)port->write_buf);
+	}
+
+	if (ret == OKL4_ERROR_PIPE_FULL ||
+			ret == OKL4_ERROR_PIPE_NOT_READY) {
+		port->write_full = true;
+		return 0;
+	}
+
+	if (ret != OKL4_OK)
+		return -EIO;
+
+	return count;
+}
+
+static void
+vtty_write_irq(struct vtty_port *port)
+{
+	struct tty_struct *tty = tty_port_tty_get(&port->port);
+
+	spin_lock(&port->write_lock);
+
+	port->write_full = false;
+
+	if (port->write_buffered &&
+			do_pipe_write(port, port->write_buffered) > 0)
+		port->write_buffered = 0;
+
+	if (tty)
+		tty_wakeup(tty);
+
+	spin_unlock(&port->write_lock);
+
+	tty_kref_put(tty);
+}
+
+static irqreturn_t
+vtty_tx_irq(int irq, void *dev)
+{
+	struct vtty_port *port = dev;
+	okl4_pipe_state_t payload = okl4_get_virq_payload(irq);
+
+	if (okl4_pipe_state_gettxavailable(&payload))
+		vtty_write_irq(port);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t
+vtty_rx_irq(int irq, void *dev)
+{
+	struct vtty_port *port = dev;
+	okl4_pipe_state_t payload = okl4_get_virq_payload(irq);
+
+	if (okl4_pipe_state_getrxavailable(&payload))
+		vtty_read_irq(port);
+
+	return IRQ_HANDLED;
+}
+
+static int
+vtty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+	int port_num = tty->index;
+	struct vtty_port *port;
+	int status;
+
+	if (port_num < 0 || port_num >= MAX_VTTYS)
+		return -ENXIO;
+
+	port = &ports[port_num];
+	if (!port->exists)
+		return -ENODEV;
+
+	tty->driver_data = port;
+
+	port->write_full = false;
+	port->read_throttled = false;
+	port->write_buffered = 0;
+
+	/*
+	 * low_latency forces all tty read handling to be done by the
+	 * read task.
+	 */
+	port->port.low_latency = 1;
+
+	if (!port->irq_registered) {
+		status = devm_request_irq(port->dev, port->tx_irq,
+				vtty_tx_irq, 0, dev_name(port->dev), port);
+		if (status)
+			return status;
+
+		status = devm_request_irq(port->dev, port->rx_irq,
+				vtty_rx_irq, 0, dev_name(port->dev), port);
+		if (status) {
+			devm_free_irq(port->dev, port->tx_irq, port);
+			return status;
+		}
+
+		port->irq_registered = true;
+	}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)
+	tty_port_install(&port->port, driver, tty);
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0)
+	tty->port = &port->port;
+	tty_standard_install(driver, tty);
+#else
+	tty->port = &port->port;
+	if (tty_init_termios(tty) != 0)
+		return -ENOMEM;
+
+	tty_driver_kref_get(driver);
+	tty->count++;
+	driver->ttys[tty->index] = tty;
+#endif
+
+	return 0;
+}
+
+static int
+vtty_open(struct tty_struct *tty, struct file *file)
+{
+	struct vtty_port *port = tty->driver_data;
+	okl4_pipe_control_t x = 0;
+
+	okl4_pipe_control_setdoop(&x, true);
+	okl4_pipe_control_setoperation(&x,
+		OKL4_PIPE_CONTROL_OP_SET_TX_READY);
+	_okl4_sys_pipe_control(port->pipe_tx_kcap, x);
+	okl4_pipe_control_setoperation(&x,
+		OKL4_PIPE_CONTROL_OP_SET_RX_READY);
+	_okl4_sys_pipe_control(port->pipe_rx_kcap, x);
+
+	return tty_port_open(&port->port, tty, file);
+}
+
+static void
+vtty_close(struct tty_struct *tty, struct file *file)
+{
+	struct vtty_port *port = tty->driver_data;
+	if (port)
+		tty_port_close(&port->port, tty, file);
+}
+
+static int
+vtty_activate(struct tty_port *port, struct tty_struct *tty)
+{
+	struct vtty_port *vtty_port = tty->driver_data;
+
+	/* Run the read task immediately to drain the channel */
+	queue_work(read_workqueue, &vtty_port->read_work);
+
+	return 0;
+}
+
+static void
+vtty_shutdown(struct tty_port *port)
+{
+	struct vtty_port *vtty_port =
+			container_of(port, struct vtty_port, port);
+
+	cancel_work_sync(&vtty_port->read_work);
+}
+
+static int
+do_vtty_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	struct vtty_port *port = tty->driver_data;
+	int retval = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&port->write_lock, flags);
+
+	/* If we have a whole message, try to send it */
+	if (port->write_buffered == 0 && count >= port->max_msg_size) {
+		if (count > port->max_msg_size)
+			count = port->max_msg_size;
+
+		memcpy(&port->write_buf[sizeof(u32)], buf, count);
+		retval = do_pipe_write(port, count);
+		count -= retval;
+	}
+
+	/* If nothing was sent yet, buffer the data */
+	if (!retval) {
+		/* Determine how much data will fit in the buffer */
+		if (count > port->max_msg_size - port->write_buffered)
+			count = port->max_msg_size - port->write_buffered;
+
+		/* Copy into the buffer if possible */
+		if (count) {
+			memcpy(&port->write_buf[sizeof(u32) +
+					port->write_buffered], buf, count);
+			port->write_buffered += count;
+			retval = count;
+		}
+
+		/* Flush the buffer if it is full */
+		if (port->write_buffered == port->max_msg_size) {
+			if (do_pipe_write(port, port->write_buffered) > 0)
+				port->write_buffered = 0;
+		}
+	}
+
+	spin_unlock_irqrestore(&port->write_lock, flags);
+
+	return retval;
+}
+
+static void
+vtty_flush_chars(struct tty_struct *tty)
+{
+	struct vtty_port *port = tty->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&port->write_lock, flags);
+
+	if (port->write_buffered && do_pipe_write(port,
+			port->write_buffered) > 0) {
+		port->write_buffered = 0;
+		tty_wakeup(tty);
+	}
+
+	spin_unlock_irqrestore(&port->write_lock, flags);
+}
+
+static int
+vtty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	return do_vtty_write(tty, &ch, 1);
+}
+
+static int
+vtty_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	int retval;
+
+	retval = do_vtty_write(tty, buf, count);
+	vtty_flush_chars(tty);
+
+	return retval;
+}
+
+static int
+vtty_write_room(struct tty_struct *tty)
+{
+	struct vtty_port *port = tty->driver_data;
+
+	/*
+	 * If the channel is full, we have to buffer writes locally. While
+	 * vtty_write() can handle that, we may as well tell the ldisc to wait
+	 * for the channel to drain, so we return 0 here.
+	 */
+	return port->write_full ? 0 : port->max_msg_size - port->write_buffered;
+}
+
+static int
+vtty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct vtty_port *port = tty->driver_data;
+
+	return port->max_msg_size - vtty_write_room(tty);
+}
+
+static void
+vtty_throttle(struct tty_struct *tty)
+{
+	struct vtty_port *port = tty->driver_data;
+
+	port->read_throttled = true;
+}
+
+static void
+vtty_unthrottle(struct tty_struct *tty)
+{
+	struct vtty_port *port = tty->driver_data;
+
+	port->read_throttled = false;
+	queue_work(read_workqueue, &port->read_work);
+}
+
+static const struct tty_port_operations vtty_port_ops = {
+	.activate = vtty_activate,
+	.shutdown = vtty_shutdown,
+};
+
+static int vtty_proc_show(struct seq_file *m, void *v)
+{
+	int i;
+
+	seq_puts(m, "okl4vttyinfo:1.0 driver:1.0\n");
+	for (i = 0; i < sizeof(ports)/sizeof(ports[0]); i++) {
+		struct vtty_port *port = &ports[i];
+
+		if (!port->exists)
+			continue;
+		seq_printf(m, "%d: tx_kcap: %d tx_irq: %d rx_kcap: %d rx_irq: %d\n",
+				i, port->pipe_tx_kcap, port->tx_irq, port->pipe_rx_kcap, port->rx_irq);
+	}
+
+	return 0;
+}
+
+static int vtty_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, vtty_proc_show, NULL);
+}
+
+static const struct file_operations vtty_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= vtty_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static const struct tty_operations vtty_ops = {
+	.install = vtty_install,
+	.open = vtty_open,
+	.close = vtty_close,
+	.write = vtty_write,
+	.put_char = vtty_put_char,
+	.flush_chars = vtty_flush_chars,
+	.write_room = vtty_write_room,
+	.chars_in_buffer = vtty_chars_in_buffer,
+	.throttle = vtty_throttle,
+	.unthrottle = vtty_unthrottle,
+	.proc_fops = &vtty_proc_fops,
+};
+
+static void
+vtty_read_task(struct work_struct *work)
+{
+	struct vtty_port *port = container_of(work, struct vtty_port,
+			read_work);
+	struct tty_struct *tty = tty_port_tty_get(&port->port);
+	bool pushed = false;
+
+	if (!tty)
+		return;
+
+	while (true) {
+		struct _okl4_sys_pipe_recv_return ret_recv;
+		int space, len;
+
+		/* Stop reading if we are throttled. */
+		if (port->read_throttled)
+			break;
+
+		/* Find out how much space we have in the tty buffer. */
+		space = tty_buffer_request_room(&port->port,
+				port->max_msg_size);
+
+		if (space == 0) {
+			BUG_ON(pushed);
+			tty_flip_buffer_push(&port->port);
+			pushed = true;
+			continue;
+		} else {
+			pushed = false;
+		}
+
+		if (port->read_buf_pos == port->read_buf_len) {
+			/*
+			 * We have run out of chars in our message buffer.
+			 * Check whether there are any more messages in the
+			 * queue.
+			 */
+
+			ret_recv = _okl4_sys_pipe_recv(port->pipe_rx_kcap,
+					port->max_msg_size + sizeof(u32),
+					(void *)port->read_buf);
+			if (ret_recv.error == OKL4_ERROR_PIPE_NOT_READY) {
+				okl4_pipe_control_t x = 0;
+
+				okl4_pipe_control_setdoop(&x, true);
+				okl4_pipe_control_setoperation(&x,
+					OKL4_PIPE_CONTROL_OP_SET_RX_READY);
+				_okl4_sys_pipe_control(port->pipe_rx_kcap, x);
+
+				ret_recv = _okl4_sys_pipe_recv(port->pipe_rx_kcap,
+						port->max_msg_size + sizeof(u32),
+						(void *)port->read_buf);
+			}
+			if (ret_recv.error == OKL4_ERROR_PIPE_EMPTY ||
+					ret_recv.error == OKL4_ERROR_PIPE_NOT_READY) {
+				port->read_buf_pos = 0;
+				port->read_buf_len = 0;
+				break;
+			}
+
+			if (ret_recv.error != OKL4_OK) {
+				dev_err(port->dev,
+					"pipe receive returned error %d in vtty driver !\n",
+					(int)ret_recv.error);
+				port->read_buf_pos = 0;
+				port->read_buf_len = 0;
+				break;
+			}
+
+			port->read_buf_pos = sizeof(uint32_t);
+			port->read_buf_len = sizeof(uint32_t) +
+					*(uint32_t *)port->read_buf;
+		}
+
+		/* Send chars to tty layer. */
+		len = port->read_buf_len - port->read_buf_pos;
+		if (len > space)
+			len = space;
+
+		tty_insert_flip_string(&port->port, port->read_buf +
+				port->read_buf_pos, len);
+		port->read_buf_pos += len;
+	}
+
+	tty_flip_buffer_push(&port->port);
+
+	tty_kref_put(tty);
+}
+
+static struct tty_driver *vtty_driver;
+
+#ifdef CONFIG_OKL4_VTTY_CONSOLE
+static int vconsole_setup(struct console *co, char *options);
+static void vconsole_write(struct console *co, const char *p, unsigned count);
+static struct tty_driver *vconsole_device(struct console *co, int *index);
+#endif
+
+static int
+vtty_probe(struct platform_device *pdev)
+{
+	struct vtty_port *vtty_port;
+	struct device *tty_dev;
+	u32 reg[2];
+	int vtty_id, irq, err;
+
+	vtty_id = of_alias_get_id(pdev->dev.of_node, "vserial");
+	if (vtty_id < 0)
+		vtty_id = of_alias_get_id(pdev->dev.of_node, "serial");
+
+	if (vtty_id < 0 || vtty_id >= MAX_VTTYS) {
+		err = -ENXIO;
+		goto fail_vtty_id;
+	}
+
+	vtty_port = &ports[vtty_id];
+	if (vtty_port->exists) {
+		dev_err(&pdev->dev, "vtty port already exists\n");
+		err = -ENODEV;
+		goto fail_vtty_id;
+	}
+
+	if (of_property_read_u32_array(pdev->dev.of_node, "reg", reg, 2)) {
+		dev_err(&pdev->dev, "need 2 reg resources\n");
+		err = -ENODEV;
+		goto fail_vtty_id;
+	}
+
+	dev_set_drvdata(&pdev->dev, vtty_port);
+
+	/* Set up and register the tty port */
+	vtty_port->dev = &pdev->dev;
+	vtty_port->vtty_id = vtty_id;
+	tty_port_init(&vtty_port->port);
+	vtty_port->port.ops = &vtty_port_ops;
+
+	vtty_port->pipe_tx_kcap = reg[0];
+	vtty_port->pipe_rx_kcap = reg[1];
+	vtty_port->max_msg_size = 32;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no tx irq resource?\n");
+		err = -ENODEV;
+		goto fail_of;
+	}
+	vtty_port->tx_irq = irq;
+
+	irq = platform_get_irq(pdev, 1);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no rx irq resource?\n");
+		err = -ENODEV;
+		goto fail_of;
+	}
+	vtty_port->rx_irq = irq;
+
+	vtty_port->exists = true;
+
+	spin_lock_init(&vtty_port->write_lock);
+	INIT_WORK(&vtty_port->read_work, vtty_read_task);
+
+	vtty_port->read_buf = kmalloc(vtty_port->max_msg_size + sizeof(u32),
+		GFP_KERNEL);
+	if (!vtty_port->read_buf) {
+		dev_err(&pdev->dev, "%s: bad kmalloc\n", __func__);
+		err = -ENOMEM;
+		goto fail_malloc_read;
+	}
+	vtty_port->read_buf_pos = 0;
+	vtty_port->read_buf_len = 0;
+
+	vtty_port->write_buf = kmalloc(vtty_port->max_msg_size + sizeof(u32),
+		GFP_KERNEL);
+	if (!vtty_port->write_buf) {
+		dev_err(&pdev->dev, "%s: bad kmalloc\n", __func__);
+		err = -ENOMEM;
+		goto fail_malloc_write;
+	}
+
+	tty_dev = tty_register_device(vtty_driver, vtty_id, &pdev->dev);
+	if (IS_ERR(tty_dev)) {
+		dev_err(&pdev->dev, "%s: can't register "DEVICE_NAME"%d: %ld",
+			__func__, vtty_id, PTR_ERR(tty_dev));
+		err = PTR_ERR(tty_dev);
+		goto fail_tty_register;
+	}
+
+#ifdef CONFIG_OKL4_VTTY_CONSOLE
+	/* Set up and register the port's console device */
+	strlcpy(vtty_port->console.name, DEVICE_PREFIX,
+		sizeof(vtty_port->console.name));
+	vtty_port->console.write = vconsole_write;
+	vtty_port->console.flags = CON_PRINTBUFFER;
+	vtty_port->console.device = vconsole_device;
+	vtty_port->console.setup = vconsole_setup;
+	vtty_port->console.index = vtty_id;
+
+	register_console(&vtty_port->console);
+#endif
+
+	return 0;
+
+fail_tty_register:
+	kfree(vtty_port->write_buf);
+fail_malloc_write:
+	kfree(vtty_port->read_buf);
+	vtty_port->exists = false;
+fail_of:
+fail_vtty_id:
+fail_malloc_read:
+	dev_set_drvdata(&pdev->dev, NULL);
+	return err;
+}
+
+static int
+vtty_remove(struct platform_device *pdev)
+{
+	struct vtty_port *vtty_port = dev_get_drvdata(&pdev->dev);
+
+	if (!vtty_port->exists)
+		return -ENOENT;
+
+#ifdef CONFIG_OKL4_VTTY_CONSOLE
+	unregister_console(&vtty_port->console);
+#endif
+	tty_unregister_device(vtty_driver, vtty_port->vtty_id);
+	vtty_port->exists = false;
+	kfree(vtty_port->write_buf);
+	kfree(vtty_port->read_buf);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+	devm_kfree(&pdev->dev, vtty_port);
+
+	return 0;
+}
+
+static const struct of_device_id vtty_match[] = {
+	{
+		.compatible = "okl,pipe-tty",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, vtty_match);
+
+static struct platform_driver driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = vtty_match,
+	},
+	.probe		= vtty_probe,
+	.remove		= vtty_remove,
+};
+
+
+static int __init vtty_init(void)
+{
+	int err;
+
+	/* Allocate workqueue */
+	read_workqueue = create_workqueue("okl4vtty");
+	if (read_workqueue == NULL) {
+		err = -ENOMEM;
+		goto fail_create_workqueue;
+	}
+
+	/* Set up the tty driver. */
+	vtty_driver = alloc_tty_driver(MAX_VTTYS);
+	if (vtty_driver == NULL) {
+		err = -ENOMEM;
+		goto fail_alloc_tty_driver;
+	}
+
+	vtty_driver->owner = THIS_MODULE;
+	vtty_driver->driver_name = DRIVER_NAME;
+	vtty_driver->name = DEVICE_PREFIX;
+	vtty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	vtty_driver->subtype = SERIAL_TYPE_NORMAL;
+	vtty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	vtty_driver->init_termios = tty_std_termios;
+
+	/* These flags don't really matter; just use sensible defaults. */
+	vtty_driver->init_termios.c_cflag =
+			B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	vtty_driver->init_termios.c_ispeed = 9600;
+	vtty_driver->init_termios.c_ospeed = 9600;
+
+	tty_set_operations(vtty_driver, &vtty_ops);
+
+	err = tty_register_driver(vtty_driver);
+	if (err)
+		goto fail_tty_driver_register;
+
+	err = platform_driver_register(&driver);
+	if (err)
+		goto fail_mv_driver_register;
+
+	return 0;
+
+fail_mv_driver_register:
+	tty_unregister_driver(vtty_driver);
+fail_tty_driver_register:
+	put_tty_driver(vtty_driver);
+	vtty_driver = NULL;
+fail_alloc_tty_driver:
+	destroy_workqueue(read_workqueue);
+	read_workqueue = NULL;
+fail_create_workqueue:
+	return err;
+}
+
+static void __exit vtty_exit(void)
+{
+	platform_driver_unregister(&driver);
+
+	tty_unregister_driver(vtty_driver);
+	put_tty_driver(vtty_driver);
+	vtty_driver = NULL;
+	destroy_workqueue(read_workqueue);
+	read_workqueue = NULL;
+}
+
+module_init(vtty_init);
+module_exit(vtty_exit);
+
+#ifdef CONFIG_OKL4_VTTY_CONSOLE
+
+static u32 cycle_limit = 0;
+
+static int
+vconsole_setup(struct console *co, char *options)
+{
+	struct vtty_port *port;
+
+	if (co->index < 0 || co->index >= MAX_VTTYS)
+		co->index = 0;
+
+	port = &ports[co->index];
+	if (!port->exists)
+		return -ENODEV;
+
+	cycle_limit = arch_timer_get_rate() * 20 / MSEC_PER_SEC;
+	if (cycle_limit == 0) {
+		cycle_limit = -1;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_OKL4_INTERLEAVED_PRIORITIES
+extern int vcpu_prio_normal;
+#endif
+
+static void
+vconsole_write(struct console *co, const char *p, unsigned count)
+{
+	struct vtty_port *port = &ports[co->index];
+	size_t bytes_remaining = count;
+	char buf[port->max_msg_size + sizeof(u32)];
+	cycles_t last_sent_start = get_cycles();
+	static int pipe_full = 0;
+
+	memset(buf, 0, sizeof(buf));
+
+	while (bytes_remaining > 0) {
+		unsigned to_send = min(port->max_msg_size, bytes_remaining);
+		unsigned send = roundup(to_send + sizeof(u32), sizeof(u32));
+		okl4_error_t ret;
+
+		*(u32 *)buf = to_send;
+		memcpy(&buf[sizeof(u32)], p, to_send);
+
+		ret = _okl4_sys_pipe_send(port->pipe_tx_kcap, send,
+				(void *)buf);
+
+		if (ret == OKL4_ERROR_PIPE_NOT_READY) {
+			okl4_pipe_control_t x = 0;
+
+			okl4_pipe_control_setdoop(&x, true);
+			okl4_pipe_control_setoperation(&x,
+					OKL4_PIPE_CONTROL_OP_SET_TX_READY);
+			_okl4_sys_pipe_control(port->pipe_tx_kcap, x);
+			continue;
+		}
+
+		if (ret == OKL4_ERROR_PIPE_FULL) {
+			cycles_t last_sent_cycles = get_cycles() -
+					last_sent_start;
+			if (last_sent_cycles > cycle_limit || pipe_full) {
+				pipe_full = 1;
+				return;
+			}
+#ifdef CONFIG_OKL4_INTERLEAVED_PRIORITIES
+			_okl4_sys_priority_waive(vcpu_prio_normal);
+#else
+			_okl4_sys_priority_waive(0);
+#endif
+			continue;
+		}
+
+		if (ret != OKL4_OK) {
+			/*
+			 * We cannot call printk here since that will end up
+			 * calling back here and make things worse. We just
+			 * have to return and hope that the problem corrects
+			 * itself.
+			 */
+			return;
+		}
+
+		p += to_send;
+		bytes_remaining -= to_send;
+		last_sent_start = get_cycles();
+		pipe_full = 0;
+	}
+}
+
+struct tty_driver *
+vconsole_device(struct console *co, int *index)
+{
+	*index = co->index;
+	return vtty_driver;
+}
+
+#endif /* CONFIG_OKL4_VTTY_CONSOLE */
+
+MODULE_DESCRIPTION("OKL4 virtual TTY driver");
+MODULE_AUTHOR("Philip Derrin <philipd@ok-labs.com>");