This is a minimal TTY layer replacement for embedded systems with limited
capabilities. This supports only serial ports, supports only a subset of
the default line discipline, and dispense with anything that is of no use
for a small embedded system.

Signed-off-by: Nicolas Pitre <n...@linaro.org>
---
 drivers/tty/Kconfig                                |   10 +-
 drivers/tty/Makefile                               |    1 +
 drivers/tty/serial/Kconfig                         |   12 +-
 drivers/tty/serial/Makefile                        |    3 +
 .../tty/serial/{serial_core.c => fulltty_serial.c} |    0
 drivers/tty/serial/minitty_serial.c                | 2091 ++++++++++++++++++++
 include/linux/tty_flip.h                           |    4 +
 7 files changed, 2116 insertions(+), 5 deletions(-)
 rename drivers/tty/serial/{serial_core.c => fulltty_serial.c} (100%)
 create mode 100644 drivers/tty/serial/minitty_serial.c

diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 95103054c0..8517c353d8 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -2,10 +2,12 @@ config TTY
        bool "Enable TTY" if EXPERT
        default y
        ---help---
-         Allows you to remove TTY support which can save space, and
-         blocks features that require TTY from inclusion in the kernel.
-         TTY is required for any text terminals or serial port
-         communication. Most users should leave this enabled.
+         Allows you to remove the full-featured TTY support which can save
+         space, and blocks features that require it from inclusion in the
+         kernel. TTY support is required for any text terminals or serial
+         port communication. If turned off, a much smaller TTY implementation
+         that only supports serial ports in a limited capacity may be
+         selected instead. Most users should leave this enabled.
 
 if TTY
 
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 1461be6b90..9b7b3418cd 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -1,5 +1,6 @@
 obj-$(CONFIG_TTY)              += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \
                                   tty_buffer.o tty_port.o tty_mutex.o 
tty_ldsem.o tty_baudrate.o
+obj-$(CONFIG_MINITTY_SERIAL)   += tty_baudrate.o
 obj-$(CONFIG_LEGACY_PTYS)      += pty.o
 obj-$(CONFIG_UNIX98_PTYS)      += pty.o
 obj-$(CONFIG_AUDIT)            += tty_audit.o
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 6117ac8da4..a552387a39 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -2,7 +2,17 @@
 # Serial device configuration
 #
 
-if TTY
+config MINITTY_SERIAL
+       bool "Enable mini TTY for serial ports"
+       depends on !TTY
+       default y
+       help
+         This enables a much smaller TTY implementation that only supports
+         serial ports in a limited capacity. This is however sufficient for
+         many embedded use cases that use serial ports mainly as a debug
+         console where the saving in kernel code size is welcome.
+
+if TTY || MINITTY_SERIAL
 
 menu "Serial drivers"
        depends on HAS_IOMEM
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 2d6288bc45..970af02c86 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -2,7 +2,10 @@
 # Makefile for the kernel serial device drivers.
 #
 
+serial_core-$(CONFIG_TTY) := fulltty_serial.o
+serial_core-$(CONFIG_MINITTY_SERIAL) := minitty_serial.o
 obj-$(CONFIG_SERIAL_CORE) += serial_core.o
+
 obj-$(CONFIG_SERIAL_21285) += 21285.o
 
 obj-$(CONFIG_SERIAL_EARLYCON) += earlycon.o
diff --git a/drivers/tty/serial/serial_core.c 
b/drivers/tty/serial/fulltty_serial.c
similarity index 100%
rename from drivers/tty/serial/serial_core.c
rename to drivers/tty/serial/fulltty_serial.c
diff --git a/drivers/tty/serial/minitty_serial.c 
b/drivers/tty/serial/minitty_serial.c
new file mode 100644
index 0000000000..c8e91b2c54
--- /dev/null
+++ b/drivers/tty/serial/minitty_serial.c
@@ -0,0 +1,2091 @@
+/*
+ * Smallest shortcut replacement for tty and serial core layers.
+ *
+ * Based mainly on tty_io.c, n_tty.c and serial_core.c from many smart people.
+ *
+ * Created by:  Nicolas Pitre, January 2017
+ * Copyright:   (C) 2017  Linaro Limited
+ *
+ * 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/ctype.h>
+#include <linux/console.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/sched/signal.h>
+#include <linux/serial_core.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+struct minitty_data {
+       struct uart_state state;
+       struct ktermios termios;
+       struct mutex mutex;
+       unsigned char *rx_buf;
+       int rx_head, rx_vetted, rx_tail;
+       int rx_lines, column, canon_start_pos;
+       bool rx_raw;
+       bool rx_overflow;
+       wait_queue_head_t write_wait;
+       wait_queue_head_t read_wait;
+       struct work_struct rx_work;
+       struct cdev cdev;
+       struct device *dev;
+       int usecount;
+};
+
+#define RX_BUF_SIZE PAGE_SIZE
+#define RX_BUF_WRAP(x) ((x) & (RX_BUF_SIZE - 1))
+
+/*
+ * Functions called back by low level UART drivers when
+ * the TX buffer is getting near empty.
+ */
+void uart_write_wakeup(struct uart_port *port)
+{
+       struct uart_state *state = port->state;
+       struct minitty_data *mtty = container_of(state, typeof(*mtty), state);
+
+       wake_up_interruptible_poll(&mtty->write_wait, POLLOUT);
+}
+EXPORT_SYMBOL(uart_write_wakeup);
+
+static void
+uart_update_mctrl(struct uart_port *port, unsigned int set, unsigned int clear)
+{
+       unsigned long flags;
+       unsigned int old;
+
+       spin_lock_irqsave(&port->lock, flags);
+       old = port->mctrl;
+       port->mctrl = (old & ~clear) | set;
+       if (old != port->mctrl)
+               port->ops->set_mctrl(port, port->mctrl);
+       spin_unlock_irqrestore(&port->lock, flags);
+}
+
+#define uart_set_mctrl(port, set)      uart_update_mctrl(port, set, 0)
+#define uart_clear_mctrl(port, clear)  uart_update_mctrl(port, 0, clear)
+
+static void uart_change_pm(struct uart_state *state,
+                          enum uart_pm_state pm_state)
+{
+       struct uart_port *port =state->uart_port; 
+
+       if (state->pm_state != pm_state) {
+               if (port && port->ops->pm)
+                       port->ops->pm(port, pm_state, state->pm_state);
+               state->pm_state = pm_state;
+       }
+}
+
+int uart_suspend_port(struct uart_driver *drv, struct uart_port *port)
+{
+       return -EPROTONOSUPPORT;
+}
+EXPORT_SYMBOL(uart_suspend_port);
+
+int uart_resume_port(struct uart_driver *drv, struct uart_port *port)
+{
+       return -EPROTONOSUPPORT;
+}
+EXPORT_SYMBOL(uart_resume_port);
+
+/*
+ *     Are the two ports equivalent?
+ */
+int uart_match_port(struct uart_port *port1, struct uart_port *port2)
+{
+       if (port1->iotype != port2->iotype)
+               return 0;
+
+       switch (port1->iotype) {
+       case UPIO_PORT:
+               return (port1->iobase == port2->iobase);
+       case UPIO_HUB6:
+               return (port1->iobase == port2->iobase) &&
+                      (port1->hub6   == port2->hub6);
+       case UPIO_MEM:
+       case UPIO_MEM16:
+       case UPIO_MEM32:
+       case UPIO_MEM32BE:
+       case UPIO_AU:
+       case UPIO_TSI:
+               return (port1->mapbase == port2->mapbase);
+       }
+       return 0;
+}
+EXPORT_SYMBOL(uart_match_port);
+
+/**
+ *     uart_handle_dcd_change - handle a change of carrier detect state
+ *     @port: uart_port structure for the open port
+ *     @status: new carrier detect status, nonzero if active
+ *
+ *     Caller must hold port->lock
+ */
+void uart_handle_dcd_change(struct uart_port *port, unsigned int status)
+{
+       port->icount.dcd++;
+}
+EXPORT_SYMBOL_GPL(uart_handle_dcd_change);
+
+/**
+ *     uart_handle_cts_change - handle a change of clear-to-send state
+ *     @port: uart_port structure for the open port
+ *     @status: new clear to send status, nonzero if active
+ *
+ *     Caller must hold port->lock
+ */
+void uart_handle_cts_change(struct uart_port *port, unsigned int status)
+{
+       port->icount.cts++;
+
+       if (uart_softcts_mode(port)) {
+               if (port->hw_stopped) {
+                       if (status) {
+                               port->hw_stopped = 0;
+                               port->ops->start_tx(port);
+                               uart_write_wakeup(port);
+                       }
+               } else {
+                       if (!status) {
+                               port->hw_stopped = 1;
+                               port->ops->stop_tx(port);
+                       }
+               }
+       }
+}
+EXPORT_SYMBOL_GPL(uart_handle_cts_change);
+
+/**
+ *     uart_update_timeout - update per-port FIFO timeout.
+ *     @port:  uart_port structure describing the port
+ *     @cflag: termios cflag value
+ *     @baud:  speed of the port
+ *
+ *     Set the port FIFO timeout value.  The @cflag value should
+ *     reflect the actual hardware settings.
+ */
+void
+uart_update_timeout(struct uart_port *port, unsigned int cflag,
+                   unsigned int baud)
+{
+       unsigned int bits;
+
+       /* byte size and parity */
+       switch (cflag & CSIZE) {
+       case CS5:
+               bits = 7;
+               break;
+       case CS6:
+               bits = 8;
+               break;
+       case CS7:
+               bits = 9;
+               break;
+       default:
+               bits = 10;
+               break; /* CS8 */
+       }
+
+       if (cflag & CSTOPB)
+               bits++;
+       if (cflag & PARENB)
+               bits++;
+
+       /*
+        * The total number of bits to be transmitted in the fifo.
+        */
+       bits = bits * port->fifosize;
+
+       /*
+        * Figure the timeout to send the above number of bits.
+        * Add .02 seconds of slop
+        */
+       port->timeout = (HZ * bits) / baud + HZ/50;
+}
+EXPORT_SYMBOL(uart_update_timeout);
+
+/**
+ *     uart_get_baud_rate - return baud rate for a particular port
+ *     @port: uart_port structure describing the port in question.
+ *     @termios: desired termios settings.
+ *     @old: old termios (or NULL)
+ *     @min: minimum acceptable baud rate
+ *     @max: maximum acceptable baud rate
+ *
+ *     Decode the termios structure into a numeric baud rate,
+ *     mapping the %B0 rate to 9600 baud.
+ *
+ *     If the new baud rate is invalid, try the old termios setting.
+ *     If it's still invalid, go with the closest acceptable.
+ *
+ *     Update the @termios structure to reflect the baud rate
+ *     we're actually going to be using. Don't do this for the case
+ *     where B0 is requested ("hang up").
+ */
+unsigned int
+uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
+                  struct ktermios *old, unsigned int min, unsigned int max)
+{
+       unsigned int baud;
+       bool hung_up = false;
+
+       baud = termios->c_ospeed;
+
+       /* Special case: B0 rate. */
+       if (baud == 0) {
+               hung_up = true;
+               baud = 9600;
+       }
+
+       if (old && (baud < min || baud > max))
+               baud = old->c_ospeed;
+       if (baud < min)
+               baud = min;
+       else if (baud > max)
+               baud = max;
+
+       if (!hung_up)
+               termios->c_ispeed = termios->c_ospeed = baud;
+       
+       return baud;
+}
+EXPORT_SYMBOL(uart_get_baud_rate);
+
+/**
+ *     uart_get_divisor - return uart clock divisor
+ *     @port: uart_port structure describing the port.
+ *     @baud: desired baud rate
+ *
+ *     Calculate the uart clock divisor for the port.
+ */
+unsigned int
+uart_get_divisor(struct uart_port *port, unsigned int baud)
+{
+       return DIV_ROUND_CLOSEST(port->uartclk, 16 * baud);
+}
+EXPORT_SYMBOL(uart_get_divisor);
+
+static void uart_start_tx(struct minitty_data *mtty)
+{
+       struct uart_port *port = mtty->state.uart_port;
+       spin_lock_irq(&port->lock);
+       if (!port->hw_stopped)
+               port->ops->start_tx(port);
+       spin_unlock_irq(&port->lock);
+}
+
+static int uart_chars_in_buffer(struct minitty_data *mtty)
+{
+       struct uart_state *state = &mtty->state;
+       struct uart_port *port = mtty->state.uart_port;
+       int ret;
+
+       spin_lock_irq(&port->lock);
+       ret = uart_circ_chars_pending(&state->xmit);
+       spin_unlock_irq(&port->lock);
+       return ret;
+}
+
+static void uart_flush_tx_buffer(struct minitty_data *mtty)
+{
+       struct uart_state *state = &mtty->state;
+       struct uart_port *port = mtty->state.uart_port;
+
+       spin_lock_irq(&port->lock);
+       uart_circ_clear(&state->xmit);
+       if (port->ops->flush_buffer)
+               port->ops->flush_buffer(port);
+       spin_unlock_irq(&port->lock);
+       uart_write_wakeup(port);
+}
+
+static int uart_get_lsr_info(struct minitty_data *mtty, unsigned int __user *p)
+{
+       struct uart_state *state = &mtty->state;
+       struct uart_port *port = mtty->state.uart_port;
+       unsigned int result;
+
+       mutex_lock(&mtty->mutex);
+       result = port->ops->tx_empty(port);
+
+       /*
+        * If we're about to load something into the transmit
+        * register, we'll pretend the transmitter isn't empty to
+        * avoid a race condition (depending on when the transmit
+        * interrupt happens).
+        */
+       if (port->x_char ||
+           ((uart_circ_chars_pending(&state->xmit) > 0) &&
+            !uart_tx_stopped(port)))
+               result &= ~TIOCSER_TEMT;
+       mutex_unlock(&mtty->mutex);
+       return put_user(result, p);
+}
+
+static int uart_tiocmget(struct minitty_data *mtty, int __user *p)
+{
+       struct uart_port *port = mtty->state.uart_port;
+       int ret = -EIO;
+
+       mutex_lock(&mtty->mutex);
+       ret = port->mctrl;
+       spin_lock_irq(&port->lock);
+       ret |= port->ops->get_mctrl(port);
+       spin_unlock_irq(&port->lock);
+       mutex_unlock(&mtty->mutex);
+       if (ret >= 0)
+               ret = put_user(ret, p);
+       return ret;
+}
+
+static int
+uart_tiocmset(struct minitty_data *mtty, unsigned int cmd, unsigned __user *p)
+{
+       struct uart_port *port = mtty->state.uart_port;
+       unsigned int set, clear, val;
+       int ret;
+
+       ret = get_user(val, p);
+       if (ret)
+               return ret;
+       set = clear = 0;
+       switch (cmd) {
+       case TIOCMBIS:
+               set = val;
+               break;
+       case TIOCMBIC:
+               clear = val;
+               break;
+       case TIOCMSET:
+               set = val;
+               clear = ~val;
+               break;
+       }
+       set &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP;
+       clear &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP;
+
+       mutex_lock(&mtty->mutex);
+       uart_update_mctrl(port, set, clear);
+       mutex_unlock(&mtty->mutex);
+       return 0;
+}
+
+static int uart_break_ctl(struct minitty_data *mtty, int break_state)
+{
+       struct uart_port *port = mtty->state.uart_port;
+       int ret = -EIO;
+
+       mutex_lock(&mtty->mutex);
+       port->ops->break_ctl(port, break_state);
+       mutex_unlock(&mtty->mutex);
+       return ret;
+}
+
+/*
+ * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+ * - mask passed in arg for lines of interest
+ *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+ * Caller should use TIOCGICOUNT to see which one it was
+ */
+static int uart_wait_modem_status(struct minitty_data *mtty, unsigned long arg)
+{
+       struct uart_port *uport = mtty->state.uart_port;
+       struct tty_port *port = &mtty->state.port;
+       DECLARE_WAITQUEUE(wait, current);
+       struct uart_icount cprev, cnow;
+       int ret;
+
+       /*
+        * note the counters on entry
+        */
+       spin_lock_irq(&uport->lock);
+       memcpy(&cprev, &uport->icount, sizeof(struct uart_icount));
+       if (uport->ops->enable_ms)
+               uport->ops->enable_ms(uport);
+       spin_unlock_irq(&uport->lock);
+
+       add_wait_queue(&port->delta_msr_wait, &wait);
+       for (;;) {
+               spin_lock_irq(&uport->lock);
+               memcpy(&cnow, &uport->icount, sizeof(struct uart_icount));
+               spin_unlock_irq(&uport->lock);
+
+               set_current_state(TASK_INTERRUPTIBLE);
+
+               if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+                   ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+                   ((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd)) ||
+                   ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) {
+                       ret = 0;
+                       break;
+               }
+
+               schedule();
+
+               /* see if a signal did it */
+               if (signal_pending(current)) {
+                       ret = -ERESTARTSYS;
+                       break;
+               }
+
+               cprev = cnow;
+       }
+       __set_current_state(TASK_RUNNING);
+       remove_wait_queue(&port->delta_msr_wait, &wait);
+
+       return ret;
+}
+
+/*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * NB: both 1->0 and 0->1 transitions are counted except for
+ *     RI where only 0->1 is counted.
+ */
+static int uart_tiocgicount(struct minitty_data *mtty, void __user *p)
+{
+       struct uart_port *port = mtty->state.uart_port;
+       struct serial_icounter_struct icount;
+       struct uart_icount cnow;
+
+       spin_lock_irq(&port->lock);
+       memcpy(&cnow, &port->icount, sizeof(struct uart_icount));
+       spin_unlock_irq(&port->lock);
+
+       memset(&icount, 0, sizeof(icount));
+       icount.cts         = cnow.cts;
+       icount.dsr         = cnow.dsr;
+       icount.rng         = cnow.rng;
+       icount.dcd         = cnow.dcd;
+       icount.rx          = cnow.rx;
+       icount.tx          = cnow.tx;
+       icount.frame       = cnow.frame;
+       icount.overrun     = cnow.overrun;
+       icount.parity      = cnow.parity;
+       icount.brk         = cnow.brk;
+       icount.buf_overrun = cnow.buf_overrun;
+       if (copy_to_user(p, &icount, sizeof(icount)))
+               return -EFAULT;
+       return 0;
+}
+
+static void uart_change_speed(struct minitty_data *mtty,
+                             struct ktermios *old_termios)
+{
+       struct uart_port *port = mtty->state.uart_port;
+       struct ktermios *termios = &mtty->termios;
+       int hw_stopped;
+
+       port->ops->set_termios(port, termios, old_termios);
+
+       /*
+        * Set modem status enables based on termios cflag
+        */
+       spin_lock_irq(&port->lock);
+       if (termios->c_cflag & CRTSCTS)
+               port->status |= UPSTAT_CTS_ENABLE;
+       else
+               port->status &= ~UPSTAT_CTS_ENABLE;
+
+       if (termios->c_cflag & CLOCAL)
+               port->status &= ~UPSTAT_DCD_ENABLE;
+       else
+               port->status |= UPSTAT_DCD_ENABLE;
+
+       /* reset sw-assisted CTS flow control based on (possibly) new mode */
+       hw_stopped = port->hw_stopped;
+       port->hw_stopped = uart_softcts_mode(port) &&
+                               !(port->ops->get_mctrl(port) & TIOCM_CTS);
+       if (port->hw_stopped) {
+               if (!hw_stopped)
+                       port->ops->stop_tx(port);
+       } else {
+               if (hw_stopped)
+                       port->ops->start_tx(port);
+       }
+       spin_unlock_irq(&port->lock);
+}
+
+static void uart_set_termios(struct minitty_data *mtty,
+                            struct ktermios *old_termios)
+{
+       struct uart_port *port = mtty->state.uart_port;
+       unsigned int cflag = mtty->termios.c_cflag;
+       unsigned int iflag_mask = IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK;
+       bool sw_changed = false;
+
+       /*
+        * Drivers doing software flow control also need to know
+        * about changes to these input settings.
+        */
+       if (port->flags & UPF_SOFT_FLOW) {
+               iflag_mask |= IXANY|IXON|IXOFF;
+               sw_changed =
+                  mtty->termios.c_cc[VSTART] != old_termios->c_cc[VSTART] ||
+                  mtty->termios.c_cc[VSTOP] != old_termios->c_cc[VSTOP];
+       }
+
+       /*
+        * These are the bits that are used to setup various
+        * flags in the low level driver. We can ignore the Bfoo
+        * bits in c_cflag; c_[io]speed will always be set
+        * appropriately by set_termios(). 
+        */
+       if ((cflag ^ old_termios->c_cflag) == 0 &&
+           mtty->termios.c_ospeed == old_termios->c_ospeed &&
+           mtty->termios.c_ispeed == old_termios->c_ispeed &&
+           ((mtty->termios.c_iflag ^ old_termios->c_iflag) & iflag_mask) == 0 
&&
+           !sw_changed)
+               return;
+
+       uart_change_speed(mtty, old_termios);
+       /* reload cflag from termios; port driver may have overriden flags */
+       cflag = mtty->termios.c_cflag;
+
+       /* Handle transition to B0 status */
+       if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD))
+               uart_clear_mctrl(port, TIOCM_RTS | TIOCM_DTR);
+       /* Handle transition away from B0 status */
+       else if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {
+               unsigned int mask = TIOCM_DTR;
+               if (!(cflag & CRTSCTS))
+                       mask |= TIOCM_RTS;
+               uart_set_mctrl(port, mask);
+       }
+}
+
+static void uart_wait_until_sent(struct minitty_data *mtty)
+{
+       struct uart_port *port = mtty->state.uart_port;
+       unsigned long char_time, expire, timeout;
+
+       /*
+        * Set the check interval to be 1/5 of the estimated time to
+        * send a single character, and make it at least 1.
+        *
+        * Note: we have to use pretty tight timings here to satisfy
+        * the NIST-PCTS.
+        */
+       char_time = (port->timeout - HZ/50) / port->fifosize;
+       char_time = char_time / 5;
+       if (char_time == 0)
+               char_time = 1;
+
+       /*
+        * If the transmitter hasn't cleared in twice the approximate
+        * amount of time to send the entire FIFO, it probably won't
+        * ever clear.  This assumes the UART isn't doing flow
+        * control, which is currently the case.  Hence, if it ever
+        * takes longer than port->timeout, this is probably due to a
+        * UART bug of some kind.  So, we clamp the timeout parameter at
+        * 2*port->timeout.
+        */
+       timeout = 2 * port->timeout;
+
+       expire = jiffies + timeout;
+       while (!port->ops->tx_empty(port)) {
+                       msleep_interruptible(jiffies_to_msecs(char_time));
+                               if (signal_pending(current))
+                                                       break;
+                                       if (time_after(jiffies, expire))
+                                                               break;
+       }
+}
+
+static void mtty_wait_until_sent(struct minitty_data *mtty)
+{
+       long timeout = MAX_SCHEDULE_TIMEOUT;
+
+       timeout = wait_event_interruptible_timeout(mtty->write_wait,
+                                       !uart_chars_in_buffer(mtty), timeout);
+       if (timeout > 0)
+               uart_wait_until_sent(mtty);
+}
+
+static void mtty_set_termios(struct minitty_data *mtty,
+                            struct ktermios *old_termios)
+{
+       bool was_raw = mtty->rx_raw;
+
+       mtty->rx_raw = !I_IGNCR(mtty) && !I_ICRNL(mtty) && !I_INLCR(mtty) &&
+                      !L_ICANON(mtty) && !L_ISIG(mtty) && !L_ECHO(mtty);
+       if (!mtty->rx_raw && was_raw)
+               mtty->rx_lines = mtty->column = mtty->canon_start_pos = 0;
+
+       /* mark things we don't support. */
+       mtty->termios.c_iflag |= IGNBRK | IGNPAR;
+       mtty->termios.c_iflag &= ~(ISTRIP | IUCLC | IXON | IXOFF);
+       mtty->termios.c_lflag &= ~IEXTEN;
+
+       /* The termios change make the tty ready for I/O */
+       wake_up_interruptible(&mtty->write_wait);
+       wake_up_interruptible(&mtty->read_wait);
+}
+
+static int set_termios(struct minitty_data *mtty, unsigned int cmd,
+                      void __user *arg)
+{
+       struct ktermios new_termios, old_termios;
+       int ret;
+
+       mutex_lock(&mtty->mutex);
+       new_termios = mtty->termios;
+       mutex_unlock(&mtty->mutex);
+
+       switch (cmd) {
+       case TCSETAF:
+       case TCSETAW:
+       case TCSETA:
+               ret = user_termio_to_kernel_termios(&new_termios,
+                                                   (struct termio __user 
*)arg);
+               break;
+#ifdef TCGETS2
+       case TCSETSF2:
+       case TCSETSW2:
+       case TCSETS2:
+               ret = user_termios_to_kernel_termios(&new_termios,
+                                                    (struct termios2 __user 
*)arg);
+               break;
+       default:
+               ret = user_termios_to_kernel_termios_1(&new_termios,
+                                                      (struct termios __user 
*)arg);
+               break;
+#else
+       default:
+               ret = user_termios_to_kernel_termios(&new_termios,
+                                                    (struct termios __user 
*)arg);
+               break;
+#endif
+       }
+       if (ret)
+               return -EFAULT;
+
+       switch (cmd) {
+       case TCSETSF:
+#ifdef TCGETS2
+       case TCSETSF2:
+#endif
+       case TCSETAF:
+               uart_flush_tx_buffer(mtty);
+       }
+
+       switch (cmd) {
+       case TCSETSF:
+       case TCSETSW:
+#ifdef TCGETS2
+       case TCSETSF2:
+       case TCSETSW2:
+#endif
+       case TCSETAF:
+       case TCSETAW:
+               mtty_wait_until_sent(mtty);
+               if (signal_pending(current))
+                       return -ERESTARTSYS;
+       }
+
+       /*
+        * If old style Bfoo values are used then load c_ispeed/c_ospeed
+        * with the real speed so its unconditionally usable.
+        */
+       new_termios.c_ispeed = tty_termios_input_baud_rate(&new_termios);
+       new_termios.c_ospeed = tty_termios_baud_rate(&new_termios);
+
+       mutex_lock(&mtty->mutex);
+       old_termios = mtty->termios;
+       mtty->termios = new_termios;
+       mtty_set_termios(mtty, &old_termios);
+       uart_set_termios(mtty, &old_termios);
+       mutex_unlock(&mtty->mutex);
+       return 0;
+}
+
+static int tiocsetd(int __user *p)
+{
+       int ldisc;
+
+       if (get_user(ldisc, p))
+               return -EFAULT;
+       if (ldisc != N_TTY)
+               return -EINVAL;
+       return 0;
+}
+
+static int tiocgetd(int __user *p)
+{
+       return put_user(N_TTY, p);
+}
+
+static void copy_termios(struct minitty_data *mtty, struct ktermios *kterm)
+{
+       mutex_lock(&mtty->mutex);
+       *kterm = mtty->termios;
+       mutex_unlock(&mtty->mutex);
+}
+
+static long minitty_ioctl(struct file *file, unsigned int cmd, unsigned long 
arg)
+{
+       struct minitty_data *mtty = file->private_data;
+       struct uart_port *port = mtty->state.uart_port;
+       void __user *p = (void __user *)arg;
+       struct ktermios kterm;
+       int ret = -ENOIOCTLCMD;
+
+       switch (cmd) {
+       case TIOCSETD:
+               return tiocsetd(p);
+       case TIOCGETD:
+               return tiocgetd(p);
+       case TIOCSBRK:
+               return uart_break_ctl(mtty, -1);
+       case TIOCCBRK:
+               return uart_break_ctl(mtty, 0);
+       case TIOCMGET:
+               return uart_tiocmget(mtty, p);
+       case TIOCMSET:
+       case TIOCMBIC:
+       case TIOCMBIS:
+               return uart_tiocmset(mtty, cmd, p);
+       case TIOCGICOUNT:
+               return uart_tiocgicount(mtty, p);
+       case TIOCMIWAIT:
+               return uart_wait_modem_status(mtty, arg);
+       case TIOCSERGETLSR:
+               return uart_get_lsr_info(mtty, p);
+
+#ifndef TCGETS2
+       case TCGETS:
+               copy_termios(mtty, &kterm);
+               if (kernel_termios_to_user_termios((struct termios __user 
*)arg, &kterm))
+                       return -EFAULT;
+               return 0;
+#else
+       case TCGETS:
+               copy_termios(mtty, &kterm);
+               if (kernel_termios_to_user_termios_1((struct termios __user 
*)arg, &kterm))
+                       return -EFAULT;
+               return 0;
+       case TCGETS2:
+               copy_termios(mtty, &kterm);
+               if (kernel_termios_to_user_termios((struct termios2 __user 
*)arg, &kterm))
+                       return -EFAULT;
+               return 0;
+       case TCSETSF2:
+       case TCSETSW2:
+       case TCSETS2:
+#endif
+       case TCSETSF:
+       case TCSETSW:
+       case TCSETS:
+       case TCSETAF:
+       case TCSETAW:
+       case TCSETA:
+               return set_termios(mtty, cmd, p);
+       case TCGETA:
+               copy_termios(mtty, &kterm);
+               if (kernel_termios_to_user_termio((struct termio __user *)arg, 
&kterm))
+                       return -EFAULT;
+               return 0;
+
+       default:
+               mutex_lock(&mtty->mutex);
+               if (port->ops->ioctl)
+                       ret = port->ops->ioctl(port, cmd, arg);
+               mutex_unlock(&mtty->mutex);
+               break;
+       }
+
+       if (ret == -ENOIOCTLCMD)
+               ret = -EINVAL;
+       return ret;
+}
+
+/*
+ * Functions called back by low level UART drivers to provide
+ * an RX character. We simply ignore characters with errors here.
+ */
+
+int tty_insert_flip_char(struct tty_port *port, unsigned char ch, char flag)
+{
+       struct uart_state *state = container_of(port, struct uart_state, port);
+       struct minitty_data *mtty = container_of(state, typeof(*mtty), state);
+
+       if (flag == TTY_NORMAL) {
+               int tail = smp_load_acquire(&mtty->rx_tail);
+               int head = mtty->rx_head;
+               int next = RX_BUF_WRAP(head + 1);
+               /*
+                * Advance head only if buffer is not full.
+                * Keep on overwriting last char otherwise.
+                */
+               mtty->rx_buf[head] = ch;
+               if (next != tail) {
+                       smp_store_release(&mtty->rx_head, next);
+                       return 1;
+               } else {
+                       smp_store_release(&mtty->rx_overflow, true);
+               }
+       }               
+       return 0;
+}
+EXPORT_SYMBOL(tty_insert_flip_char);
+
+void uart_insert_char(struct uart_port *port, unsigned int status,
+                     unsigned int overrun, unsigned int ch, unsigned int flag)
+{
+       struct uart_state *state = port->state;
+       struct minitty_data *mtty = container_of(state, typeof(*mtty), state);
+
+       if (flag == TTY_NORMAL) {
+               int tail = smp_load_acquire(&mtty->rx_tail);
+               int head = mtty->rx_head;
+               int next = RX_BUF_WRAP(head + 1);
+               /*
+                * Advance head only if buffer is not full.
+                * Keep on overwriting last char otherwise.
+                */
+               mtty->rx_buf[head] = ch;
+               if (next != tail) {
+                       smp_store_release(&mtty->rx_head, next);
+               } else {
+                       smp_store_release(&mtty->rx_overflow, true);
+                       port->icount.buf_overrun++;
+               }
+       }
+}
+EXPORT_SYMBOL_GPL(uart_insert_char);
+
+void tty_schedule_flip(struct tty_port *port)
+{
+       struct uart_state *state = container_of(port, struct uart_state, port);
+       struct minitty_data *mtty = container_of(state, typeof(*mtty), state);
+
+       queue_work(system_unbound_wq, &mtty->rx_work);
+}
+EXPORT_SYMBOL(tty_schedule_flip);
+
+void tty_flip_buffer_push(struct tty_port *port)
+{
+       tty_schedule_flip(port);
+}
+EXPORT_SYMBOL(tty_flip_buffer_push);
+
+/*
+ * Line Discipline Stuff
+ */
+
+static bool is_utf8_continuation(struct minitty_data *mtty, unsigned char c)
+{
+       return (I_IUTF8(mtty) && (c & 0xc0) == 0x80);
+}
+
+static bool is_line_termination(struct minitty_data *mtty, unsigned char c)
+{
+       return (c == '\n' || c == EOF_CHAR(mtty) || c == EOL_CHAR(mtty));
+}
+
+/*
+ * Queue the provided character string in its entirety or nothing.
+ * Return true if queued, false otherwise.
+ */
+static bool queue_tx_chars(struct minitty_data *mtty, unsigned char *s, int 
len)
+{
+       struct circ_buf *circ = &mtty->state.xmit;
+       int head, tail, space;
+
+       tail = smp_load_acquire(&circ->tail);
+       head = circ->head;
+       space = CIRC_SPACE(head, tail, UART_XMIT_SIZE);
+       if (space < len)
+               return false;
+       while (len--) {
+               circ->buf[head] = *s++;
+               head = (head + 1) & (UART_XMIT_SIZE - 1);
+       }
+       smp_store_release(&circ->head, head);
+       return true;
+}
+
+/*
+ * Queue characters in their cooked sequence.
+ * Return true if queued, or false otherwise.
+ */
+static bool tx_cooked_char(struct minitty_data *mtty, unsigned char c)
+{
+       int spaces, next_col = mtty->column;
+
+       switch (c) {
+       case '\n':
+               if (O_ONLRET(mtty))
+                       next_col = 0;
+               if (O_ONLCR(mtty)) {
+                       if (!queue_tx_chars(mtty, "\r\n", 2))
+                               return false;
+                       mtty->column = mtty->canon_start_pos = 0;
+                       return true;
+               }
+               break;
+       case '\r':
+               if (O_ONOCR(mtty) && mtty->column == 0)
+                       return true;
+               if (O_OCRNL(mtty)) {
+                       c = '\n';
+                       if (O_ONLRET(mtty))
+                               next_col = 0;
+               } else
+                       next_col = 0;
+               break;
+       case '\t':
+               spaces = 8 - (mtty->column & 7);
+               if (O_TABDLY(mtty) == XTABS) {
+                       if (!queue_tx_chars(mtty, "        ", spaces))
+                               return false;
+                       mtty->column += spaces;
+                       return true;
+               }
+               next_col += spaces;
+               break;
+       case '\b':
+               if (next_col > 0)
+                       next_col--;
+               break;
+       default:
+               if (iscntrl(c))
+                       break;
+               if (is_utf8_continuation(mtty, c))
+                       break;
+               next_col++;
+               break;
+       }
+       if (!queue_tx_chars(mtty, &c, 1))
+               return false;
+       mtty->column = next_col;
+       if (next_col == 0)
+               mtty->canon_start_pos = 0;
+       return true;
+}
+
+/*
+ * Queue echoed characters, converting CTRL sequences into "^X" if need be.
+ * Return true if queued, or false otherwise.
+ */
+static bool echo_rx_char(struct minitty_data *mtty, unsigned char c)
+{
+       if (L_ECHOCTL(mtty) && iscntrl(c) && c != '\t' && c != '\n') {
+               unsigned char buf[2];
+               buf[0] = '^';
+               buf[1] = c ^ 0100;
+               return queue_tx_chars(mtty, buf, 2);
+       }
+       if (O_OPOST(mtty))
+               return tx_cooked_char(mtty, c);
+       else
+               return queue_tx_chars(mtty, &c, 1);
+}
+
+/*
+ * Remove character from RX buffer at given position by shifting
+ * all preceding characters ahead.
+ */
+static void eat_rx_char(struct minitty_data *mtty, int pos)
+{
+       unsigned char *buf = mtty->rx_buf;
+       int tail = mtty->rx_tail;
+       int bottom = (tail <= pos) ? tail : 0;
+
+       memmove(&buf[bottom+1], &buf[bottom], pos - bottom);
+       if (tail > pos) {
+               buf[0] = buf[RX_BUF_SIZE-1];
+               memmove(&buf[tail+1], &buf[tail], RX_BUF_SIZE - 1 - tail);
+       }
+       smp_store_release(&mtty->rx_tail, RX_BUF_WRAP(tail + 1));
+}
+
+/*
+ * Create needed erase sequence according to the erase character c at
+ * position pos in the RX buffer. The erase sequence is sent for each
+ * erased characters and only if that succeeds then the character is
+ * actually removed from the buffer. The erase character itself is removed
+ * last so if the whole erase sequence cannot be completed then this can
+ * be resumed later.
+ */
+static bool erase_rx_char(struct minitty_data *mtty, unsigned char c, int pos)
+{
+       int prev_pos = RX_BUF_WRAP(pos - 1);
+       bool seen_alnum = false;
+
+       while (pos != mtty->rx_tail) {
+               unsigned char prev_c = mtty->rx_buf[prev_pos];
+
+               if (is_line_termination(mtty, prev_c)) {
+                       /* End of previous line: we don't erase further. */
+                       break;
+               }
+
+               if (is_utf8_continuation(mtty, prev_c)) {
+                       /* UTF8 continuation char: we just drop it */
+                       eat_rx_char(mtty, prev_pos);
+                       continue;
+               }
+
+               if (c == WERASE_CHAR(mtty) && seen_alnum && !isalnum(prev_c)) {
+                       /* Beginning of previous word: we don't erase further */
+                       break;
+               }
+
+               if (prev_c == '\t') {
+                       /* depends on characters before the tab */
+                       int spaces = 0;
+                       int i = prev_pos;
+                       while (i != mtty->rx_tail) {
+                               unsigned char before;
+                               i = RX_BUF_WRAP(i - 1);
+                               before = mtty->rx_buf[i];
+                               if (before == '\t')
+                                       break;
+                               if (is_line_termination(mtty, before))
+                                       break;
+                               if (L_ECHOCTL(mtty) && iscntrl(before))
+                                       spaces += 2;
+                               else if (is_utf8_continuation(mtty, before))
+                                       continue;
+                               else if (!iscntrl(before))
+                                       spaces++;
+                       }
+                       if (i == mtty->rx_tail)
+                               spaces += mtty->canon_start_pos;
+                       spaces = 8 - (spaces & 7);
+                       if (!queue_tx_chars(mtty, "\b\b\b\b\b\b\b\b", spaces))
+                               return false;
+                       mtty->column -= spaces;
+               } else if (L_ECHOCTL(mtty) && iscntrl(prev_c)) {
+                       /* control chars were printed as "^X" */
+                       if (!queue_tx_chars(mtty, "\b\b  \b\b", 6))
+                               return false;
+                       mtty->column -= 2;
+               } else if (!iscntrl(prev_c)) {
+                       if (!queue_tx_chars(mtty, "\b \b", 3))
+                               return false;
+                       mtty->column -= 1;
+               }
+
+               /* erase sequence sent, now remove the char from the buffer */
+               eat_rx_char(mtty, prev_pos);
+
+               if (c == ERASE_CHAR(mtty))
+                       break;
+       }
+
+       /* Finally remove the erase character itself. */
+       eat_rx_char(mtty, pos);
+       return true;
+}
+
+/*
+ * Process RX bytes: canonical mode, echo, signals, etc.
+ * This might not process all RX characters if e.g. there is not enough
+ * room in the TX buffer to contain corresponding echo sequences.
+ */
+static void minitty_process_rx(struct minitty_data *mtty)
+{
+       bool xmit = false;
+       int i, head;
+       
+       head = smp_load_acquire(&mtty->rx_head);
+
+       if (mtty->rx_raw) {
+               smp_store_release(&mtty->rx_vetted, head);
+               return;
+       }
+
+       /*
+        * RX overflow mitigation: evaluate the last received character
+        * stored at the very head of the buffer in case it might be a
+        * signal or newline character that could kick the reader into
+        * action. We potentially overwrite the last vetted character but
+        * we're past any concern for lost characters at this point.
+        */
+       if (unlikely(mtty->rx_overflow)) {
+               WRITE_ONCE(mtty->rx_overflow, false);
+               if (RX_BUF_WRAP(head + 1) == mtty->rx_tail) {
+                       i = RX_BUF_WRAP(head - 1);
+                       mtty->rx_buf[i] = mtty->rx_buf[head];
+                       if (mtty->rx_vetted == head)
+                               mtty->rx_vetted = i;
+               }
+       }
+
+       for (i = mtty->rx_vetted; i != head; i = RX_BUF_WRAP(i + 1)) {
+               unsigned char c = mtty->rx_buf[i];
+
+               if (c == '\r') {
+                       if (I_IGNCR(mtty)) {
+                               eat_rx_char(mtty, i);
+                               continue;
+                       }
+                       if (I_ICRNL(mtty))
+                               mtty->rx_buf[i] = c = '\n';
+               } else if (c == '\n' && I_INLCR(mtty))
+                       mtty->rx_buf[i] = c = '\r';
+
+               if (L_ICANON(mtty)) {
+                       if ((L_ECHOE(mtty) && c == ERASE_CHAR(mtty)) ||
+                           (L_ECHOE(mtty) && c == WERASE_CHAR(mtty)) ||
+                           (L_ECHOK(mtty) && c == KILL_CHAR(mtty))) {
+                               xmit = true;
+                               if (!erase_rx_char(mtty, c, i))
+                                               break;
+                               continue;
+                       }
+                       if (is_line_termination(mtty, c)) {
+                               mtty->rx_lines++;
+                               if (c != '\n')
+                                       continue;
+                       }
+               }
+
+               if (L_ECHO(mtty) || (c == '\n' && L_ECHONL(mtty))) {
+                       xmit = true;
+                       if (!echo_rx_char(mtty, c))
+                               break;
+               }
+       }
+
+       smp_store_release(&mtty->rx_vetted, i);
+
+       if (xmit)
+               uart_start_tx(mtty);
+}
+
+static bool rx_data_available(struct minitty_data *mtty, bool poll)
+{
+       bool data_avail = (mtty->rx_tail != mtty->rx_vetted);
+       if (data_avail && !L_ICANON(mtty)) {
+               int amt = poll && !TIME_CHAR(mtty) && MIN_CHAR(mtty) ?
+                               MIN_CHAR(mtty) : 1;
+               data_avail = RX_BUF_WRAP(mtty->rx_vetted - mtty->rx_tail) >= 
amt;
+       } else if (data_avail && !mtty->rx_lines) {
+               /* wait for a full line */
+               data_avail = false;
+       } else if (!data_avail && mtty->rx_lines) {
+               /*
+                * This may happen if the RX buffer was flushed by a signal
+                * or during RX overflow. Let's just reset it to zero.
+                */
+               mtty->rx_lines = 0;
+       }
+       return data_avail;
+}
+
+static void uart_rx_work(struct work_struct *work)
+{
+       struct minitty_data *mtty = container_of(work, typeof(*mtty), rx_work);
+
+       mutex_lock(&mtty->mutex);
+       minitty_process_rx(mtty);
+       if (rx_data_available(mtty, true))
+               wake_up_interruptible_poll(&mtty->read_wait, POLLIN);
+       mutex_unlock(&mtty->mutex);
+}
+
+static ssize_t minitty_raw_read(struct minitty_data *mtty, char __user *buf,
+                               size_t count)
+{
+       int head, tail, len, ret = 0;
+
+       head = smp_load_acquire(&mtty->rx_vetted); 
+       tail = mtty->rx_tail;
+       do {
+               len = CIRC_CNT(head, tail, RX_BUF_SIZE);
+               if (len > count)
+                       len = count;
+               if (copy_to_user(buf, mtty->rx_buf+tail, len) != 0)
+                       return -EFAULT;
+               tail = RX_BUF_WRAP(tail + len);
+               buf += len;
+               count -= len;
+               ret += len;
+       } while (count && len && tail == 0);
+       smp_store_release(&mtty->rx_tail, tail);
+       return ret;
+}
+
+static ssize_t minitty_cooked_read(struct minitty_data *mtty, char __user *buf,
+                                  size_t count)
+{
+       int head, tail, i, ret;
+       bool eol = false;
+
+       head = smp_load_acquire(&mtty->rx_vetted);
+       tail = mtty->rx_tail;
+
+       /* First, locate the end-of-line marker if any. */
+       for (i = tail; i != head && count; i = RX_BUF_WRAP(i + 1), count--) {
+               unsigned char c = mtty->rx_buf[i];
+               if (is_line_termination(mtty, c)) {
+                       eol = true;
+                       break;
+               }
+       }
+
+       count = CIRC_CNT(i, tail, RX_BUF_SIZE);
+
+       if (eol) {
+               /* Include the line delimiter except for EOF */
+               if (mtty->rx_buf[i] != EOF_CHAR(mtty))
+                       count++;
+               i = RX_BUF_WRAP(i + 1);
+       }
+
+       ret = minitty_raw_read(mtty, buf, count);
+       if (ret >= 0 && eol) {
+               /* we consumed a whole line */
+               mtty->rx_lines--;
+               /* adjust tail in case EOF was skipped */
+               smp_store_release(&mtty->rx_tail, i);
+       }
+       return ret;
+}
+
+static ssize_t minitty_read(struct file *file, char __user *buf,
+                           size_t count, loff_t *ppos)
+{
+       struct minitty_data *mtty = file->private_data;
+       char __user *buf0 = buf;
+       DEFINE_WAIT_FUNC(wait, woken_wake_function);
+       int minimum, time;
+       long timeout;
+       int ret = 0;
+
+       mutex_lock(&mtty->mutex);
+
+       minimum = time = 0;
+       timeout = MAX_SCHEDULE_TIMEOUT;
+       if (!L_ICANON(mtty)) {
+               minimum = MIN_CHAR(mtty);
+               if (minimum) {
+                       time = (HZ / 10) * TIME_CHAR(mtty);
+               } else {
+                       timeout = (HZ / 10) * TIME_CHAR(mtty);
+                       minimum = 1;
+               }
+       }
+
+       add_wait_queue(&mtty->read_wait, &wait);
+
+       while (count) {
+               minitty_process_rx(mtty);
+
+               if (!rx_data_available(mtty, false)) {
+                       if (!timeout)
+                               break;
+                       if (file->f_flags & O_NONBLOCK) {
+                               ret = -EAGAIN;
+                               break;
+                       }
+                       if (signal_pending(current)) {
+                               ret = -ERESTARTSYS;
+                               break;
+                       }
+                       mutex_unlock(&mtty->mutex);
+                       timeout = wait_woken(&wait, TASK_INTERRUPTIBLE,
+                                            timeout);
+                       mutex_lock(&mtty->mutex);
+                       continue;
+               }
+
+               if (L_ICANON(mtty)) {
+                       ret = minitty_cooked_read(mtty, buf, count);
+                       if (ret > 0)
+                               buf += ret;
+                       break;
+               }
+
+               ret = minitty_raw_read(mtty, buf, count);
+               if (ret < 0)
+                       break;
+               buf += ret;
+               count -= ret;
+               if (buf - buf0 >= minimum)
+                       break;
+               if (time)
+                       timeout = time;
+       }
+
+       remove_wait_queue(&mtty->read_wait, &wait);
+       mutex_unlock(&mtty->mutex);
+       if (buf - buf0)
+               ret = buf - buf0;
+       return ret;
+}
+
+static ssize_t minitty_raw_write(struct minitty_data *mtty, const char __user 
*buf,
+                                size_t count)
+{
+       struct circ_buf *circ = &mtty->state.xmit;
+       int head, tail, len, ret = 0;
+
+       tail = smp_load_acquire(&circ->tail);
+       head = circ->head;
+       do {
+               len = CIRC_SPACE_TO_END(head, tail, UART_XMIT_SIZE);
+               if (len > count)
+                       len = count;
+               if (copy_from_user(circ->buf + head, buf, len) != 0)
+                       return -EFAULT;
+               head = (head + len) & (UART_XMIT_SIZE - 1);
+               buf += len;
+               count -= len;
+               ret += len;
+       } while (count && len && head == 0);
+       smp_store_release(&circ->head, head);
+
+       uart_start_tx(mtty);
+       return ret;
+}
+
+static ssize_t minitty_cooked_write(struct minitty_data *mtty, const char 
__user *buf,
+                                   size_t count)
+{
+       const char __user *buf0 = buf;
+
+       while (count--) {
+               unsigned char c;
+               if (get_user(c, buf) != 0)
+                       return -EFAULT;
+               if (!tx_cooked_char(mtty, c))
+                       break;
+               buf++;
+       }
+       mtty->canon_start_pos = mtty->column;
+
+       uart_start_tx(mtty);
+       return buf - buf0;
+}
+
+static ssize_t minitty_write(struct file *file, const char __user *buf,
+                            size_t count, loff_t *ppos)
+{
+       struct minitty_data *mtty = file->private_data;
+       const char __user *buf0 = buf;
+       DEFINE_WAIT_FUNC(wait, woken_wake_function);
+       int ret;
+
+       mutex_lock(&mtty->mutex);
+       add_wait_queue(&mtty->write_wait, &wait);
+
+       while (1) {
+               /* give priority to RX echo and signals */
+               minitty_process_rx(mtty);
+
+               if (signal_pending(current)) {
+                       ret = -ERESTARTSYS;
+                       break;
+               }
+
+               if (O_OPOST(mtty))
+                       ret = minitty_cooked_write(mtty, buf, count);
+               else
+                       ret = minitty_raw_write(mtty, buf, count);
+               if (ret < 0)
+                       break;
+               buf += ret;
+               count -= ret;
+               if (!count)
+                       break;
+               if (file->f_flags & O_NONBLOCK) {
+                       ret = -EAGAIN;
+                       break;
+               }
+               mutex_unlock(&mtty->mutex);
+               wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
+               mutex_lock(&mtty->mutex);
+       }
+
+       remove_wait_queue(&mtty->write_wait, &wait);
+       mutex_unlock(&mtty->mutex);
+       return (buf - buf0) ? buf - buf0 : ret;
+}
+
+static unsigned int minitty_poll(struct file *file, poll_table *wait)
+{
+       struct minitty_data *mtty = file->private_data;
+       struct uart_port *port = mtty->state.uart_port;
+       unsigned int mask = 0;
+
+       mutex_lock(&mtty->mutex);
+
+       poll_wait(file, &mtty->read_wait, wait);
+       poll_wait(file, &mtty->write_wait, wait);
+
+       if (rx_data_available(mtty, true)) {
+               mask |= POLLIN | POLLRDNORM;
+       } else {
+               minitty_process_rx(mtty);
+               if (rx_data_available(mtty, true))
+                       mask |= POLLIN | POLLRDNORM;
+       }
+
+       if (!port->hw_stopped) {
+               struct circ_buf *circ = &mtty->state.xmit;
+               int tail = smp_load_acquire(&circ->tail);
+               int head = circ->head;
+               int count = CIRC_CNT(head, tail, UART_XMIT_SIZE);
+               if (count < WAKEUP_CHARS)
+                       mask |= POLLOUT | POLLWRNORM;
+       }
+
+       mutex_unlock(&mtty->mutex);
+
+       return mask;
+}
+
+static int uart_port_startup(struct minitty_data *mtty)
+{
+       struct uart_state *state = &mtty->state;
+       struct uart_port *port = state->uart_port;
+       unsigned long page;
+       int ret;
+
+       /* Make sure the device is in D0 state. */
+       uart_change_pm(state, UART_PM_STATE_ON);
+
+       /* Initialise and allocate the transmit buffer. */
+       page = get_zeroed_page(GFP_KERNEL);
+       if (!page)
+               return -ENOMEM;
+       state->xmit.buf = (unsigned char *) page;
+       uart_circ_clear(&state->xmit);
+
+       /* Initialise and allocate the receive buffer. */
+       page = get_zeroed_page(GFP_KERNEL);
+       if (!page) {
+               ret = -ENOMEM;
+               goto err_free_tx;
+       }
+       mtty->rx_buf = (unsigned char *) page;
+       mtty->rx_head = mtty->rx_tail = mtty->rx_vetted = mtty->rx_lines = 0;
+       mtty->rx_overflow = false;
+
+       ret = port->ops->startup(port);
+       if (ret)
+               goto err_free_rx;
+
+       if (uart_console(port) && port->cons->cflag) {
+               mtty->termios.c_cflag = port->cons->cflag;
+               port->cons->cflag = 0;
+       }
+
+       /* Initialise the hardware port settings. */
+       uart_change_speed(mtty, NULL);
+
+       /*
+        * Setup the RTS and DTR signals once the
+        * port is open and ready to respond.
+        */
+       uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR);
+
+       return 0;
+
+err_free_rx:
+       free_page((unsigned long)mtty->rx_buf);
+       mtty->rx_buf = NULL;
+err_free_tx:
+       free_page((unsigned long)state->xmit.buf);
+       state->xmit.buf = NULL;
+       return ret;
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts are disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void uart_port_shutdown(struct minitty_data *mtty)
+{
+       struct uart_state *state = &mtty->state;
+       struct uart_port *port = state->uart_port;
+
+       spin_lock_irq(&port->lock);
+       port->ops->stop_rx(port);
+       spin_unlock_irq(&port->lock);
+
+       if (uart_console(port))
+               port->cons->cflag = mtty->termios.c_cflag;
+
+       /* Turn off DTR and RTS early. */
+       if (C_HUPCL(mtty))
+               uart_clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+
+       /* Free the IRQ and disable the port. */
+       port->ops->shutdown(port);
+       synchronize_irq(port->irq);
+
+       /* Free the transmit buffer page. */
+       free_page((unsigned long)state->xmit.buf);
+       state->xmit.buf = NULL;
+
+       /* Free the receive buffer page. */
+       free_page((unsigned long)mtty->rx_buf);
+       mtty->rx_buf = NULL;
+}
+
+static int minitty_open(struct inode *inode, struct file *file)
+{
+       struct minitty_data *mtty = NULL;
+       dev_t devt = inode->i_rdev;
+       int ret = 0;
+
+       if (devt == MKDEV(TTYAUX_MAJOR, 1)) {
+               struct console *co;
+               struct uart_driver *drv;
+               console_lock();
+               for_each_console(co) {
+                       if (co->device  != uart_console_device)
+                               continue;
+                       drv = co->data;
+                       mtty = container_of(drv->state, typeof(*mtty), state);
+                       mtty += co->index;
+                       break;
+               }
+               console_unlock();
+               if (!mtty)
+                       return -ENODEV;
+       } else {
+               mtty = container_of(inode->i_cdev, typeof(*mtty), cdev);
+       }
+
+       nonseekable_open(inode, file);
+
+       file->private_data = mtty;
+
+       mutex_lock(&mtty->mutex);
+       if (!mtty->usecount++) {
+               ret = uart_port_startup(mtty);
+               if (ret)
+                       mtty->usecount--;
+       }
+       mutex_unlock(&mtty->mutex);
+       return ret;
+}
+
+static int minitty_release(struct inode *inode, struct file *file)
+{
+       struct minitty_data *mtty = file->private_data;
+       struct uart_state *state = &mtty->state;
+       struct uart_port *port = state->uart_port;
+
+       mutex_lock(&mtty->mutex);
+       mtty->usecount--;
+       if (!mtty->usecount) {
+               uart_flush_tx_buffer(mtty);
+               uart_port_shutdown(mtty);
+               if (!uart_console(port))
+                       uart_change_pm(state, UART_PM_STATE_OFF);
+       }
+       mutex_unlock(&mtty->mutex);
+       return 0;
+}
+
+static const struct file_operations minitty_fops = {
+       .llseek         = no_llseek,
+       .read           = minitty_read,
+       .write          = minitty_write,
+       .poll           = minitty_poll,
+       .unlocked_ioctl = minitty_ioctl,
+       .open           = minitty_open,
+       .release        = minitty_release,
+};
+
+struct class *minitty_class;
+
+#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL)
+/**
+ *     uart_console_write - write a console message to a serial port
+ *     @port: the port to write the message
+ *     @s: array of characters
+ *     @count: number of characters in string to write
+ *     @putchar: function to write character to port
+ */
+void uart_console_write(struct uart_port *port, const char *s,
+                       unsigned int count,
+                       void (*putchar)(struct uart_port *, int))
+{
+       unsigned int i;
+
+       for (i = 0; i < count; i++, s++) {
+               if (*s == '\n')
+                       putchar(port, '\r');
+               putchar(port, *s);
+       }
+}
+EXPORT_SYMBOL_GPL(uart_console_write);
+
+/*
+ *     Check whether an invalid uart number has been specified, and
+ *     if so, search for the first available port that does have
+ *     console support.
+ */
+struct uart_port * __init
+uart_get_console(struct uart_port *ports, int nr, struct console *co)
+{
+       int idx = co->index;
+
+       if (idx < 0 || idx >= nr || (ports[idx].iobase == 0 &&
+                                    ports[idx].membase == NULL))
+               for (idx = 0; idx < nr; idx++)
+                       if (ports[idx].iobase != 0 ||
+                           ports[idx].membase != NULL)
+                               break;
+
+       co->index = idx;
+
+       return ports + idx;
+}
+
+/**
+ *     uart_parse_earlycon - Parse earlycon options
+ *     @p:       ptr to 2nd field (ie., just beyond '<name>,')
+ *     @iotype:  ptr for decoded iotype (out)
+ *     @addr:    ptr for decoded mapbase/iobase (out)
+ *     @options: ptr for <options> field; NULL if not present (out)
+ *
+ *     Decodes earlycon kernel command line parameters of the form
+ *        
earlycon=<name>,io|mmio|mmio16|mmio32|mmio32be|mmio32native,<addr>,<options>
+ *        
console=<name>,io|mmio|mmio16|mmio32|mmio32be|mmio32native,<addr>,<options>
+ *
+ *     The optional form
+ *        earlycon=<name>,0x<addr>,<options>
+ *        console=<name>,0x<addr>,<options>
+ *     is also accepted; the returned @iotype will be UPIO_MEM.
+ *
+ *     Returns 0 on success or -EINVAL on failure
+ */
+int uart_parse_earlycon(char *p, unsigned char *iotype, resource_size_t *addr,
+                       char **options)
+{
+       if (strncmp(p, "mmio,", 5) == 0) {
+               *iotype = UPIO_MEM;
+               p += 5;
+       } else if (strncmp(p, "mmio16,", 7) == 0) {
+               *iotype = UPIO_MEM16;
+               p += 7;
+       } else if (strncmp(p, "mmio32,", 7) == 0) {
+               *iotype = UPIO_MEM32;
+               p += 7;
+       } else if (strncmp(p, "mmio32be,", 9) == 0) {
+               *iotype = UPIO_MEM32BE;
+               p += 9;
+       } else if (strncmp(p, "mmio32native,", 13) == 0) {
+               *iotype = IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) ?
+                       UPIO_MEM32BE : UPIO_MEM32;
+               p += 13;
+       } else if (strncmp(p, "io,", 3) == 0) {
+               *iotype = UPIO_PORT;
+               p += 3;
+       } else if (strncmp(p, "0x", 2) == 0) {
+               *iotype = UPIO_MEM;
+       } else {
+               return -EINVAL;
+       }
+
+       /*
+        * Before you replace it with kstrtoull(), think about options separator
+        * (',') it will not tolerate
+        */
+       *addr = simple_strtoull(p, NULL, 0);
+       p = strchr(p, ',');
+       if (p)
+               p++;
+
+       *options = p;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(uart_parse_earlycon);
+
+/**
+ *     uart_parse_options - Parse serial port baud/parity/bits/flow control.
+ *     @options: pointer to option string
+ *     @baud: pointer to an 'int' variable for the baud rate.
+ *     @parity: pointer to an 'int' variable for the parity.
+ *     @bits: pointer to an 'int' variable for the number of data bits.
+ *     @flow: pointer to an 'int' variable for the flow control character.
+ *
+ *     uart_parse_options decodes a string containing the serial console
+ *     options.  The format of the string is <baud><parity><bits><flow>,
+ *     eg: 115200n8r
+ */
+void
+uart_parse_options(char *options, int *baud, int *parity, int *bits, int *flow)
+{
+       char *s = options;
+
+       *baud = simple_strtoul(s, NULL, 10);
+       while (*s >= '0' && *s <= '9')
+               s++;
+       if (*s)
+               *parity = *s++;
+       if (*s)
+               *bits = *s++ - '0';
+       if (*s)
+               *flow = *s;
+}
+EXPORT_SYMBOL_GPL(uart_parse_options);
+
+/**
+ *     uart_set_options - setup the serial console parameters
+ *     @port: pointer to the serial ports uart_port structure
+ *     @co: console pointer
+ *     @baud: baud rate
+ *     @parity: parity character - 'n' (none), 'o' (odd), 'e' (even)
+ *     @bits: number of data bits
+ *     @flow: flow control character - 'r' (rts)
+ */
+int
+uart_set_options(struct uart_port *port, struct console *co,
+                int baud, int parity, int bits, int flow)
+{
+       struct ktermios termios;
+       static struct ktermios dummy;
+
+       /*
+        * Ensure that the serial console lock is initialised
+        * early.
+        * If this port is a console, then the spinlock is already
+        * initialised.
+        */
+       if (!(uart_console(port) && (port->cons->flags & CON_ENABLED)))
+               spin_lock_init(&port->lock);
+
+       memset(&termios, 0, sizeof(struct ktermios));
+
+       termios.c_cflag |= CREAD | HUPCL | CLOCAL;
+       tty_termios_encode_baud_rate(&termios, baud, baud);
+
+       if (bits == 7)
+               termios.c_cflag |= CS7;
+       else
+               termios.c_cflag |= CS8;
+
+       switch (parity) {
+       case 'o': case 'O':
+               termios.c_cflag |= PARODD;
+               /*fall through*/
+       case 'e': case 'E':
+               termios.c_cflag |= PARENB;
+               break;
+       }
+
+       if (flow == 'r')
+               termios.c_cflag |= CRTSCTS;
+
+       /*
+        * some uarts on other side don't support no flow control.
+        * So we set * DTR in host uart to make them happy
+        */
+       port->mctrl |= TIOCM_DTR;
+
+       port->ops->set_termios(port, &termios, &dummy);
+       /*
+        * Allow the setting of the UART parameters with a NULL console
+        * too:
+        */
+       if (co)
+               co->cflag = termios.c_cflag;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(uart_set_options);
+#endif /* CONFIG_SERIAL_CORE_CONSOLE */
+
+static int
+uart_configure_port(struct uart_driver *drv, struct uart_state *state,
+                   struct uart_port *port)
+{
+       unsigned int flags;
+
+       /*
+        * If there isn't a port here, don't do anything further.
+        */
+       if (!port->iobase && !port->mapbase && !port->membase)
+               return -ENXIO;
+
+       /*
+        * Now do the auto configuration stuff.  Note that config_port
+        * is expected to claim the resources and map the port for us.
+        */
+       flags = 0;
+       if (port->flags & UPF_BOOT_AUTOCONF) {
+               if (!(port->flags & UPF_FIXED_TYPE)) {
+                       port->type = PORT_UNKNOWN;
+                       flags |= UART_CONFIG_TYPE;
+               }
+               port->ops->config_port(port, flags);
+       }
+
+       if (port->type != PORT_UNKNOWN) {
+               unsigned long flags;
+
+               pr_info("%s%d %s\n", drv->dev_name, port->line,
+                       port->ops->type ? port->ops->type(port) : "");
+
+               /* Power up port for set_mctrl() */
+               uart_change_pm(state, UART_PM_STATE_ON);
+
+               /*
+                * Ensure that the modem control lines are de-activated.
+                * keep the DTR setting that is set in uart_set_options()
+                * We probably don't need a spinlock around this, but
+                */
+               spin_lock_irqsave(&port->lock, flags);
+               port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR);
+               spin_unlock_irqrestore(&port->lock, flags);
+
+               /*
+                * If this driver supports console, and it hasn't been
+                * successfully registered yet, try to re-register it.
+                * It may be that the port was not available.
+                */
+               if (port->cons && !(port->cons->flags & CON_ENABLED))
+                       register_console(port->cons);
+
+               /*
+                * Power down all ports by default, except the
+                * console if we have one.
+                */
+               if (!uart_console(port))
+                       uart_change_pm(state, UART_PM_STATE_OFF);
+
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+/**
+ *     uart_add_one_port - attach a driver-defined port structure
+ *     @drv: pointer to the uart low level driver structure for this port
+ *     @port: uart port structure to use for this port.
+ */
+int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
+{
+       unsigned int index = port->line;
+       dev_t devt = MKDEV(drv->major, drv->minor) + index;
+       struct minitty_data *mtty;
+       struct uart_state *state;
+       int ret;
+
+       mtty = container_of(drv->state, typeof(*mtty), state) + index;
+       state = &mtty->state;
+
+       state->uart_port = port;
+       state->pm_state = UART_PM_STATE_UNDEFINED;
+       port->state = state;
+       port->cons = drv->cons;
+       port->minor = drv->minor + index;
+
+       /* our default termios */
+       mtty->termios.c_iflag = ICRNL;
+       mtty->termios.c_oflag = OPOST | ONLCR;
+       mtty->termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+       mtty->termios.c_lflag = ICANON | ECHO | ECHOE | ECHOK | ECHOKE | 
ECHOCTL;
+       mtty->termios.c_ispeed = 9600;
+       mtty->termios.c_ospeed = 9600;
+       memcpy(mtty->termios.c_cc, INIT_C_CC, sizeof(cc_t)*NCCS);
+
+       mutex_init(&mtty->mutex);
+       init_waitqueue_head(&mtty->write_wait);
+       init_waitqueue_head(&mtty->read_wait);
+       INIT_WORK(&mtty->rx_work, uart_rx_work);
+
+       /*
+        * If this port is a console, then the spinlock is already
+        * initialised.
+        */
+       if (!(uart_console(port) && (port->cons->flags & CON_ENABLED)))
+               spin_lock_init(&port->lock);
+       if (port->cons && port->dev)
+               of_console_check(port->dev->of_node, port->cons->name, index);
+
+       ret = uart_configure_port(drv, state, port);
+       /*
+        * We don't support setserial so no point registering a nonexistent
+        * device . Silently ignore this port if not present.
+        */
+       if (ret) {
+               ret = 0;
+               goto out;
+       }
+
+       state->port.console = uart_console(port);
+
+       cdev_init(&mtty->cdev, &minitty_fops);
+       mtty->cdev.owner = drv->owner;
+       ret = cdev_add(&mtty->cdev, devt, 1);
+       if (ret)
+               goto out;
+       mtty->dev = device_create(minitty_class, port->dev, devt, mtty,
+                                 "%s%d", drv->dev_name, index);
+       if (IS_ERR(mtty->dev)) {
+               ret = PTR_ERR(mtty->dev);
+               goto err_cdev_del;
+       }
+
+       return 0;
+
+err_cdev_del:
+       cdev_del(&mtty->cdev);
+out:
+       return ret;
+}
+EXPORT_SYMBOL(uart_add_one_port);
+
+/**
+ *     uart_remove_one_port - detach a driver defined port structure
+ *     @drv: pointer to the uart low level driver structure for this port
+ *     @port: uart port structure for this port
+ *
+ *     This unhooks the specified port structure from the core driver.
+ *     No further calls will be made to the low-level code for this port.
+ */
+int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
+{
+       unsigned int index = port->line;
+       dev_t devt = MKDEV(drv->major, drv->minor) + index;
+       struct minitty_data *mtty;
+       struct uart_state *state;
+
+       mtty = container_of(drv->state, typeof(*mtty), state) + index;
+       state = &mtty->state;
+       BUG_ON(state != port->state);
+
+       device_destroy(minitty_class, devt);
+       cdev_del(&mtty->cdev);
+
+       if (uart_console(port))
+               unregister_console(port->cons);
+
+       if (port->type != PORT_UNKNOWN && port->ops->release_port)
+               port->ops->release_port(port);
+       port->type = PORT_UNKNOWN;
+       state->uart_port = NULL;
+
+       return 0;
+}
+EXPORT_SYMBOL(uart_remove_one_port);
+
+/**
+ *     uart_register_driver - register a driver with the uart core layer
+ *     @drv: low level driver structure
+ *
+ *     Register a uart driver. The per-port structures should be
+ *     registered using uart_add_one_port after this call has succeeded.
+ */
+int uart_register_driver(struct uart_driver *drv)
+{
+       struct minitty_data *mtty;
+       int ret;
+
+       BUG_ON(drv->state);
+
+       mtty = kzalloc(sizeof(*mtty) * drv->nr, GFP_KERNEL);
+       if (!mtty)
+               return -ENOMEM;
+
+       if (!drv->major) {
+               dev_t devt;
+               ret = alloc_chrdev_region(&devt, drv->minor, drv->nr, 
drv->driver_name);
+               drv->major = MAJOR(devt);
+               drv->minor = MINOR(devt);
+       } else {
+               dev_t devt = MKDEV(drv->major, drv->minor);
+               ret = register_chrdev_region(devt, drv->nr, drv->driver_name);
+       }
+       if (ret < 0)
+               goto err;
+
+       drv->state = &mtty->state;
+       return 0;
+
+err:
+       kfree(mtty);
+       return ret;
+}
+EXPORT_SYMBOL(uart_register_driver);
+
+/**
+ *     uart_unregister_driver - remove a driver from the uart core layer
+ *     @drv: low level driver structure
+ *
+ *     Remove all references to a driver from the core driver.  The low
+ *     level driver must have removed all its ports via the
+ *     uart_remove_one_port() if it registered them with uart_add_one_port().
+ */
+void uart_unregister_driver(struct uart_driver *drv)
+{
+       dev_t devt = MKDEV(drv->major, drv->minor);
+       struct minitty_data *mtty;
+
+       unregister_chrdev_region(devt, drv->nr);
+       mtty = container_of(drv->state, typeof(*mtty), state);
+       drv->state = NULL;
+       kfree(mtty);
+}
+EXPORT_SYMBOL(uart_unregister_driver);
+
+void do_SAK(struct tty_struct *tty)
+{
+}
+EXPORT_SYMBOL(do_SAK);
+
+struct tty_driver *uart_console_device(struct console *co, int *index)
+{
+       return NULL;
+}
+
+static struct cdev console_cdev;
+
+static char *minitty_devnode(struct device *dev, umode_t *mode)
+{
+       if (!mode)
+               return NULL;
+       if (dev->devt == MKDEV(TTYAUX_MAJOR, 0) ||
+                       dev->devt == MKDEV(TTYAUX_MAJOR, 2))
+               *mode = 0666;
+       return NULL;
+}
+
+static int __init minitty_class_init(void)
+{
+       minitty_class = class_create(THIS_MODULE, "tty");
+       if (IS_ERR(minitty_class))
+               return PTR_ERR(minitty_class);
+       minitty_class->devnode = minitty_devnode;
+       return 0;
+}
+postcore_initcall(minitty_class_init);
+
+int __init minitty_init(void)
+{
+       dev_t devt = MKDEV(TTYAUX_MAJOR, 1);
+       cdev_init(&console_cdev, &minitty_fops);
+       if (cdev_add(&console_cdev, devt, 1) ||
+           register_chrdev_region(devt, 1, "/dev/console") < 0)
+               panic("Couldn't register /dev/console driver\n");
+       device_create(minitty_class, NULL, devt, NULL, "console");
+       return 0;
+}
+device_initcall(minitty_init);
diff --git a/include/linux/tty_flip.h b/include/linux/tty_flip.h
index c28dd523f9..5393513298 100644
--- a/include/linux/tty_flip.h
+++ b/include/linux/tty_flip.h
@@ -13,6 +13,7 @@ extern int tty_prepare_flip_string(struct tty_port *port,
 extern void tty_flip_buffer_push(struct tty_port *port);
 void tty_schedule_flip(struct tty_port *port);
 
+#ifndef CONFIG_MINITTY_SERIAL
 static inline int tty_insert_flip_char(struct tty_port *port,
                                        unsigned char ch, char flag)
 {
@@ -28,6 +29,9 @@ static inline int tty_insert_flip_char(struct tty_port *port,
        }
        return tty_insert_flip_string_flags(port, &ch, &flag, 1);
 }
+#else
+int tty_insert_flip_char(struct tty_port *port, unsigned char ch, char flag);
+#endif
 
 static inline int tty_insert_flip_string(struct tty_port *port,
                const unsigned char *chars, size_t size)
-- 
2.9.3

Reply via email to