[PATCH 3/3] minitty: minimal TTY support replacement for serial ports
Nicolas Pitre
nicolas.pitre at linaro.org
Thu Mar 23 14:03:04 PDT 2017
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 <nico at 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
More information about the linux-arm-kernel
mailing list