Many embedded systems don't need the full TTY layer support. Most of the
time, the TTY layer is only a conduit for outputting debugging messages
over a serial port. The TTY layer also implements many features that are
very unlikely to ever be used in such a setup. There is great potential
for both code and dynamic memory size reduction on small systems. This is
what this patch series is achieving.
The existing TTY code is quite large and complex. Trying to shrink it is
rather risky as the potential for breakage is non negligeable. Therefore,
the approach used here consists in the creation of the minimal code that
interface with the existing UART drivers and provide TTY-like character
devices to user space. When the regular TTY layer is disabled, then the
minitty layer replacement is proposed by Kconfig.
Of course, making it "mini" means there are limitations to what it does:
- This supports serial ports only. No VT's, no PTY's.
- The default n_tty line discipline is hardcoded and no other line
discipline are supported.
- The line discipline features are not all implemented. Notably, XON/XOFF
is currently not implemented (although this might not require a lot of
code to do it).
- Hung-up state is not implemented.
- No error handling on RX bytes other than counting them.
- Behavior in the presence of overflows is most likely different from the
full TTY code.
- Job control is currently not supported (this may change in the future and
be configurable).
But again, most small embedded systems simply don't need those things.
Here's some numbers using a minimal ARM config.
When CONFIG_TTY=y, the following files are linked into the kernel:
text data bss dec hex filename
8796 128 0 8924 22dc drivers/tty/n_tty.o
12846 276 44 13166 336e drivers/tty/serial/serial_core.o
4852 489 49 5390 150e drivers/tty/sysrq.o
1376 0 0 1376 560 drivers/tty/tty_buffer.o
13571 172 132 13875 3633 drivers/tty/tty_io.o
3072 0 0 3072 c00 drivers/tty/tty_ioctl.o
2457 2 120 2579 a13 drivers/tty/tty_ldisc.o
1328 0 0 1328 530 drivers/tty/tty_ldsem.o
316 0 0 316 13c drivers/tty/tty_mutex.o
2516 0 0 2516 9d4 drivers/tty/tty_port.o
51130 1067 345 52542 cd3e (TOTALS)
With CONFIG_TTY=n and CONFIG_MINITTY_SERIAL=y, the above is replaced by:
text data bss dec hex filename
8776 8 108 8892 22bc drivers/tty/serial/minitty_serial.o
That's it! And the runtime buffer usage is much less as well.
Overall diffstat:
drivers/tty/Kconfig | 10 +-
drivers/tty/Makefile | 3 +-
drivers/tty/serial/Kconfig | 12 +-
drivers/tty/serial/Makefile | 3 +
.../serial/{serial_core.c => fulltty_serial.c} | 0
drivers/tty/serial/minitty_serial.c | 2091 +++++++++++++++++
drivers/tty/tty_baudrate.c | 232 ++
drivers/tty/tty_io.c | 24 -
drivers/tty/tty_ioctl.c | 222 --
include/linux/console.h | 2 +
include/linux/tty.h | 7 +-
include/linux/tty_flip.h | 4 +
init/main.c | 2 +-
kernel/printk/printk.c | 24 +
14 files changed, 2380 insertions(+), 256 deletions(-)
All the console driver handling code lives in printk.c.
Move console_init() there as well so console support can still be used
when the TTY code is configured out.
Signed-off-by: Nicolas Pitre <[email protected]>
---
drivers/tty/tty_io.c | 24 ------------------------
include/linux/console.h | 2 ++
include/linux/tty.h | 7 ++++---
init/main.c | 2 +-
kernel/printk/printk.c | 24 ++++++++++++++++++++++++
5 files changed, 31 insertions(+), 28 deletions(-)
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index e6d1a65108..2100295861 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -3578,30 +3578,6 @@ void tty_default_fops(struct file_operations *fops)
*fops = tty_fops;
}
-/*
- * Initialize the console device. This is called *early*, so
- * we can't necessarily depend on lots of kernel help here.
- * Just do some early initializations, and do the complex setup
- * later.
- */
-void __init console_init(void)
-{
- initcall_t *call;
-
- /* Setup the default TTY line discipline. */
- n_tty_init();
-
- /*
- * set up the console device so that later boot sequences can
- * inform about problems etc..
- */
- call = __con_initcall_start;
- while (call < __con_initcall_end) {
- (*call)();
- call++;
- }
-}
-
static char *tty_devnode(struct device *dev, umode_t *mode)
{
if (!mode)
diff --git a/include/linux/console.h b/include/linux/console.h
index 5949d18555..b8920a031a 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -212,4 +212,6 @@ extern bool vgacon_text_force(void);
static inline bool vgacon_text_force(void) { return false; }
#endif
+extern void console_init(void);
+
#endif /* _LINUX_CONSOLE_H */
diff --git a/include/linux/tty.h b/include/linux/tty.h
index 1017e904c0..f1106d7c73 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -390,7 +390,6 @@ static inline bool tty_throttled(struct tty_struct *tty)
}
#ifdef CONFIG_TTY
-extern void console_init(void);
extern void tty_kref_put(struct tty_struct *tty);
extern struct pid *tty_get_pgrp(struct tty_struct *tty);
extern void tty_vhangup_self(void);
@@ -402,8 +401,6 @@ extern struct tty_struct *get_current_tty(void);
extern int __init tty_init(void);
extern const char *tty_name(const struct tty_struct *tty);
#else
-static inline void console_init(void)
-{ }
static inline void tty_kref_put(struct tty_struct *tty)
{ }
static inline struct pid *tty_get_pgrp(struct tty_struct *tty)
@@ -669,7 +666,11 @@ extern int tty_ldisc_receive_buf(struct tty_ldisc *ld, const unsigned char *p,
/* n_tty.c */
extern void n_tty_inherit_ops(struct tty_ldisc_ops *ops);
+#ifdef CONFIG_TTY
extern void __init n_tty_init(void);
+#else
+static inline void n_tty_init(void) { }
+#endif
/* tty_audit.c */
#ifdef CONFIG_AUDIT
diff --git a/init/main.c b/init/main.c
index f9c9d99482..b9bd0edf21 100644
--- a/init/main.c
+++ b/init/main.c
@@ -27,7 +27,7 @@
#include <linux/initrd.h>
#include <linux/bootmem.h>
#include <linux/acpi.h>
-#include <linux/tty.h>
+#include <linux/console.h>
#include <linux/nmi.h>
#include <linux/percpu.h>
#include <linux/kmod.h>
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 2984fb0f02..3a09406526 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -2611,6 +2611,30 @@ int unregister_console(struct console *console)
EXPORT_SYMBOL(unregister_console);
/*
+ * Initialize the console device. This is called *early*, so
+ * we can't necessarily depend on lots of kernel help here.
+ * Just do some early initializations, and do the complex setup
+ * later.
+ */
+void __init console_init(void)
+{
+ initcall_t *call;
+
+ /* Setup the default TTY line discipline. */
+ n_tty_init();
+
+ /*
+ * set up the console device so that later boot sequences can
+ * inform about problems etc..
+ */
+ call = __con_initcall_start;
+ while (call < __con_initcall_end) {
+ (*call)();
+ call++;
+ }
+}
+
+/*
* Some boot consoles access data that is in the init section and which will
* be discarded after the initcalls have been run. To make sure that no code
* will access this data, unregister the boot consoles in a late initcall.
--
2.9.3
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 <[email protected]>
---
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
To allow reuse without the rest of the tty_ioctl code.
Signed-off-by: Nicolas Pitre <[email protected]>
---
drivers/tty/Makefile | 2 +-
drivers/tty/tty_baudrate.c | 232 +++++++++++++++++++++++++++++++++++++++++++++
drivers/tty/tty_ioctl.c | 222 -------------------------------------------
3 files changed, 233 insertions(+), 223 deletions(-)
create mode 100644 drivers/tty/tty_baudrate.c
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index b95bed92da..1461be6b90 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -1,5 +1,5 @@
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_buffer.o tty_port.o tty_mutex.o tty_ldsem.o 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/tty_baudrate.c b/drivers/tty/tty_baudrate.c
new file mode 100644
index 0000000000..5c33fd2567
--- /dev/null
+++ b/drivers/tty/tty_baudrate.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/termios.h>
+#include <linux/tty.h>
+#include <linux/export.h>
+
+
+/*
+ * Routine which returns the baud rate of the tty
+ *
+ * Note that the baud_table needs to be kept in sync with the
+ * include/asm/termbits.h file.
+ */
+static const speed_t baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800,
+#ifdef __sparc__
+ 76800, 153600, 307200, 614400, 921600
+#else
+ 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000,
+ 2500000, 3000000, 3500000, 4000000
+#endif
+};
+
+#ifndef __sparc__
+static const tcflag_t baud_bits[] = {
+ B0, B50, B75, B110, B134, B150, B200, B300, B600,
+ B1200, B1800, B2400, B4800, B9600, B19200, B38400,
+ B57600, B115200, B230400, B460800, B500000, B576000,
+ B921600, B1000000, B1152000, B1500000, B2000000, B2500000,
+ B3000000, B3500000, B4000000
+};
+#else
+static const tcflag_t baud_bits[] = {
+ B0, B50, B75, B110, B134, B150, B200, B300, B600,
+ B1200, B1800, B2400, B4800, B9600, B19200, B38400,
+ B57600, B115200, B230400, B460800, B76800, B153600,
+ B307200, B614400, B921600
+};
+#endif
+
+static int n_baud_table = ARRAY_SIZE(baud_table);
+
+/**
+ * tty_termios_baud_rate
+ * @termios: termios structure
+ *
+ * Convert termios baud rate data into a speed. This should be called
+ * with the termios lock held if this termios is a terminal termios
+ * structure. May change the termios data. Device drivers can call this
+ * function but should use ->c_[io]speed directly as they are updated.
+ *
+ * Locking: none
+ */
+
+speed_t tty_termios_baud_rate(struct ktermios *termios)
+{
+ unsigned int cbaud;
+
+ cbaud = termios->c_cflag & CBAUD;
+
+#ifdef BOTHER
+ /* Magic token for arbitrary speed via c_ispeed/c_ospeed */
+ if (cbaud == BOTHER)
+ return termios->c_ospeed;
+#endif
+ if (cbaud & CBAUDEX) {
+ cbaud &= ~CBAUDEX;
+
+ if (cbaud < 1 || cbaud + 15 > n_baud_table)
+ termios->c_cflag &= ~CBAUDEX;
+ else
+ cbaud += 15;
+ }
+ return baud_table[cbaud];
+}
+EXPORT_SYMBOL(tty_termios_baud_rate);
+
+/**
+ * tty_termios_input_baud_rate
+ * @termios: termios structure
+ *
+ * Convert termios baud rate data into a speed. This should be called
+ * with the termios lock held if this termios is a terminal termios
+ * structure. May change the termios data. Device drivers can call this
+ * function but should use ->c_[io]speed directly as they are updated.
+ *
+ * Locking: none
+ */
+
+speed_t tty_termios_input_baud_rate(struct ktermios *termios)
+{
+#ifdef IBSHIFT
+ unsigned int cbaud = (termios->c_cflag >> IBSHIFT) & CBAUD;
+
+ if (cbaud == B0)
+ return tty_termios_baud_rate(termios);
+
+ /* Magic token for arbitrary speed via c_ispeed*/
+ if (cbaud == BOTHER)
+ return termios->c_ispeed;
+
+ if (cbaud & CBAUDEX) {
+ cbaud &= ~CBAUDEX;
+
+ if (cbaud < 1 || cbaud + 15 > n_baud_table)
+ termios->c_cflag &= ~(CBAUDEX << IBSHIFT);
+ else
+ cbaud += 15;
+ }
+ return baud_table[cbaud];
+#else
+ return tty_termios_baud_rate(termios);
+#endif
+}
+EXPORT_SYMBOL(tty_termios_input_baud_rate);
+
+/**
+ * tty_termios_encode_baud_rate
+ * @termios: ktermios structure holding user requested state
+ * @ispeed: input speed
+ * @ospeed: output speed
+ *
+ * Encode the speeds set into the passed termios structure. This is
+ * used as a library helper for drivers so that they can report back
+ * the actual speed selected when it differs from the speed requested
+ *
+ * For maximal back compatibility with legacy SYS5/POSIX *nix behaviour
+ * we need to carefully set the bits when the user does not get the
+ * desired speed. We allow small margins and preserve as much of possible
+ * of the input intent to keep compatibility.
+ *
+ * Locking: Caller should hold termios lock. This is already held
+ * when calling this function from the driver termios handler.
+ *
+ * The ifdefs deal with platforms whose owners have yet to update them
+ * and will all go away once this is done.
+ */
+
+void tty_termios_encode_baud_rate(struct ktermios *termios,
+ speed_t ibaud, speed_t obaud)
+{
+ int i = 0;
+ int ifound = -1, ofound = -1;
+ int iclose = ibaud/50, oclose = obaud/50;
+ int ibinput = 0;
+
+ if (obaud == 0) /* CD dropped */
+ ibaud = 0; /* Clear ibaud to be sure */
+
+ termios->c_ispeed = ibaud;
+ termios->c_ospeed = obaud;
+
+#ifdef BOTHER
+ /* If the user asked for a precise weird speed give a precise weird
+ answer. If they asked for a Bfoo speed they may have problems
+ digesting non-exact replies so fuzz a bit */
+
+ if ((termios->c_cflag & CBAUD) == BOTHER)
+ oclose = 0;
+ if (((termios->c_cflag >> IBSHIFT) & CBAUD) == BOTHER)
+ iclose = 0;
+ if ((termios->c_cflag >> IBSHIFT) & CBAUD)
+ ibinput = 1; /* An input speed was specified */
+#endif
+ termios->c_cflag &= ~CBAUD;
+
+ /*
+ * Our goal is to find a close match to the standard baud rate
+ * returned. Walk the baud rate table and if we get a very close
+ * match then report back the speed as a POSIX Bxxxx value by
+ * preference
+ */
+
+ do {
+ if (obaud - oclose <= baud_table[i] &&
+ obaud + oclose >= baud_table[i]) {
+ termios->c_cflag |= baud_bits[i];
+ ofound = i;
+ }
+ if (ibaud - iclose <= baud_table[i] &&
+ ibaud + iclose >= baud_table[i]) {
+ /* For the case input == output don't set IBAUD bits
+ if the user didn't do so */
+ if (ofound == i && !ibinput)
+ ifound = i;
+#ifdef IBSHIFT
+ else {
+ ifound = i;
+ termios->c_cflag |= (baud_bits[i] << IBSHIFT);
+ }
+#endif
+ }
+ } while (++i < n_baud_table);
+
+ /*
+ * If we found no match then use BOTHER if provided or warn
+ * the user their platform maintainer needs to wake up if not.
+ */
+#ifdef BOTHER
+ if (ofound == -1)
+ termios->c_cflag |= BOTHER;
+ /* Set exact input bits only if the input and output differ or the
+ user already did */
+ if (ifound == -1 && (ibaud != obaud || ibinput))
+ termios->c_cflag |= (BOTHER << IBSHIFT);
+#else
+ if (ifound == -1 || ofound == -1)
+ pr_warn_once("tty: Unable to return correct speed data as your architecture needs updating.\n");
+#endif
+}
+EXPORT_SYMBOL_GPL(tty_termios_encode_baud_rate);
+
+/**
+ * tty_encode_baud_rate - set baud rate of the tty
+ * @ibaud: input baud rate
+ * @obad: output baud rate
+ *
+ * Update the current termios data for the tty with the new speed
+ * settings. The caller must hold the termios_rwsem for the tty in
+ * question.
+ */
+
+void tty_encode_baud_rate(struct tty_struct *tty, speed_t ibaud, speed_t obaud)
+{
+ tty_termios_encode_baud_rate(&tty->termios, ibaud, obaud);
+}
+EXPORT_SYMBOL_GPL(tty_encode_baud_rate);
diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c
index a9a978731c..efa96e6c4c 100644
--- a/drivers/tty/tty_ioctl.c
+++ b/drivers/tty/tty_ioctl.c
@@ -258,228 +258,6 @@ static void unset_locked_termios(struct tty_struct *tty, struct ktermios *old)
/* FIXME: What should we do for i/ospeed */
}
-/*
- * Routine which returns the baud rate of the tty
- *
- * Note that the baud_table needs to be kept in sync with the
- * include/asm/termbits.h file.
- */
-static const speed_t baud_table[] = {
- 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
- 9600, 19200, 38400, 57600, 115200, 230400, 460800,
-#ifdef __sparc__
- 76800, 153600, 307200, 614400, 921600
-#else
- 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000,
- 2500000, 3000000, 3500000, 4000000
-#endif
-};
-
-#ifndef __sparc__
-static const tcflag_t baud_bits[] = {
- B0, B50, B75, B110, B134, B150, B200, B300, B600,
- B1200, B1800, B2400, B4800, B9600, B19200, B38400,
- B57600, B115200, B230400, B460800, B500000, B576000,
- B921600, B1000000, B1152000, B1500000, B2000000, B2500000,
- B3000000, B3500000, B4000000
-};
-#else
-static const tcflag_t baud_bits[] = {
- B0, B50, B75, B110, B134, B150, B200, B300, B600,
- B1200, B1800, B2400, B4800, B9600, B19200, B38400,
- B57600, B115200, B230400, B460800, B76800, B153600,
- B307200, B614400, B921600
-};
-#endif
-
-static int n_baud_table = ARRAY_SIZE(baud_table);
-
-/**
- * tty_termios_baud_rate
- * @termios: termios structure
- *
- * Convert termios baud rate data into a speed. This should be called
- * with the termios lock held if this termios is a terminal termios
- * structure. May change the termios data. Device drivers can call this
- * function but should use ->c_[io]speed directly as they are updated.
- *
- * Locking: none
- */
-
-speed_t tty_termios_baud_rate(struct ktermios *termios)
-{
- unsigned int cbaud;
-
- cbaud = termios->c_cflag & CBAUD;
-
-#ifdef BOTHER
- /* Magic token for arbitrary speed via c_ispeed/c_ospeed */
- if (cbaud == BOTHER)
- return termios->c_ospeed;
-#endif
- if (cbaud & CBAUDEX) {
- cbaud &= ~CBAUDEX;
-
- if (cbaud < 1 || cbaud + 15 > n_baud_table)
- termios->c_cflag &= ~CBAUDEX;
- else
- cbaud += 15;
- }
- return baud_table[cbaud];
-}
-EXPORT_SYMBOL(tty_termios_baud_rate);
-
-/**
- * tty_termios_input_baud_rate
- * @termios: termios structure
- *
- * Convert termios baud rate data into a speed. This should be called
- * with the termios lock held if this termios is a terminal termios
- * structure. May change the termios data. Device drivers can call this
- * function but should use ->c_[io]speed directly as they are updated.
- *
- * Locking: none
- */
-
-speed_t tty_termios_input_baud_rate(struct ktermios *termios)
-{
-#ifdef IBSHIFT
- unsigned int cbaud = (termios->c_cflag >> IBSHIFT) & CBAUD;
-
- if (cbaud == B0)
- return tty_termios_baud_rate(termios);
-
- /* Magic token for arbitrary speed via c_ispeed*/
- if (cbaud == BOTHER)
- return termios->c_ispeed;
-
- if (cbaud & CBAUDEX) {
- cbaud &= ~CBAUDEX;
-
- if (cbaud < 1 || cbaud + 15 > n_baud_table)
- termios->c_cflag &= ~(CBAUDEX << IBSHIFT);
- else
- cbaud += 15;
- }
- return baud_table[cbaud];
-#else
- return tty_termios_baud_rate(termios);
-#endif
-}
-EXPORT_SYMBOL(tty_termios_input_baud_rate);
-
-/**
- * tty_termios_encode_baud_rate
- * @termios: ktermios structure holding user requested state
- * @ispeed: input speed
- * @ospeed: output speed
- *
- * Encode the speeds set into the passed termios structure. This is
- * used as a library helper for drivers so that they can report back
- * the actual speed selected when it differs from the speed requested
- *
- * For maximal back compatibility with legacy SYS5/POSIX *nix behaviour
- * we need to carefully set the bits when the user does not get the
- * desired speed. We allow small margins and preserve as much of possible
- * of the input intent to keep compatibility.
- *
- * Locking: Caller should hold termios lock. This is already held
- * when calling this function from the driver termios handler.
- *
- * The ifdefs deal with platforms whose owners have yet to update them
- * and will all go away once this is done.
- */
-
-void tty_termios_encode_baud_rate(struct ktermios *termios,
- speed_t ibaud, speed_t obaud)
-{
- int i = 0;
- int ifound = -1, ofound = -1;
- int iclose = ibaud/50, oclose = obaud/50;
- int ibinput = 0;
-
- if (obaud == 0) /* CD dropped */
- ibaud = 0; /* Clear ibaud to be sure */
-
- termios->c_ispeed = ibaud;
- termios->c_ospeed = obaud;
-
-#ifdef BOTHER
- /* If the user asked for a precise weird speed give a precise weird
- answer. If they asked for a Bfoo speed they may have problems
- digesting non-exact replies so fuzz a bit */
-
- if ((termios->c_cflag & CBAUD) == BOTHER)
- oclose = 0;
- if (((termios->c_cflag >> IBSHIFT) & CBAUD) == BOTHER)
- iclose = 0;
- if ((termios->c_cflag >> IBSHIFT) & CBAUD)
- ibinput = 1; /* An input speed was specified */
-#endif
- termios->c_cflag &= ~CBAUD;
-
- /*
- * Our goal is to find a close match to the standard baud rate
- * returned. Walk the baud rate table and if we get a very close
- * match then report back the speed as a POSIX Bxxxx value by
- * preference
- */
-
- do {
- if (obaud - oclose <= baud_table[i] &&
- obaud + oclose >= baud_table[i]) {
- termios->c_cflag |= baud_bits[i];
- ofound = i;
- }
- if (ibaud - iclose <= baud_table[i] &&
- ibaud + iclose >= baud_table[i]) {
- /* For the case input == output don't set IBAUD bits
- if the user didn't do so */
- if (ofound == i && !ibinput)
- ifound = i;
-#ifdef IBSHIFT
- else {
- ifound = i;
- termios->c_cflag |= (baud_bits[i] << IBSHIFT);
- }
-#endif
- }
- } while (++i < n_baud_table);
-
- /*
- * If we found no match then use BOTHER if provided or warn
- * the user their platform maintainer needs to wake up if not.
- */
-#ifdef BOTHER
- if (ofound == -1)
- termios->c_cflag |= BOTHER;
- /* Set exact input bits only if the input and output differ or the
- user already did */
- if (ifound == -1 && (ibaud != obaud || ibinput))
- termios->c_cflag |= (BOTHER << IBSHIFT);
-#else
- if (ifound == -1 || ofound == -1)
- pr_warn_once("tty: Unable to return correct speed data as your architecture needs updating.\n");
-#endif
-}
-EXPORT_SYMBOL_GPL(tty_termios_encode_baud_rate);
-
-/**
- * tty_encode_baud_rate - set baud rate of the tty
- * @ibaud: input baud rate
- * @obad: output baud rate
- *
- * Update the current termios data for the tty with the new speed
- * settings. The caller must hold the termios_rwsem for the tty in
- * question.
- */
-
-void tty_encode_baud_rate(struct tty_struct *tty, speed_t ibaud, speed_t obaud)
-{
- tty_termios_encode_baud_rate(&tty->termios, ibaud, obaud);
-}
-EXPORT_SYMBOL_GPL(tty_encode_baud_rate);
-
/**
* tty_termios_copy_hw - copy hardware settings
* @new: New termios
--
2.9.3
Hi Nicolas,
On Thu, Mar 23, 2017 at 05:03:01PM -0400, Nicolas Pitre wrote:
> Here's some numbers using a minimal ARM config.
>
> When CONFIG_TTY=y, the following files are linked into the kernel:
>
> text data bss dec hex filename
> 8796 128 0 8924 22dc drivers/tty/n_tty.o
> 12846 276 44 13166 336e drivers/tty/serial/serial_core.o
> 4852 489 49 5390 150e drivers/tty/sysrq.o
> 1376 0 0 1376 560 drivers/tty/tty_buffer.o
> 13571 172 132 13875 3633 drivers/tty/tty_io.o
> 3072 0 0 3072 c00 drivers/tty/tty_ioctl.o
> 2457 2 120 2579 a13 drivers/tty/tty_ldisc.o
> 1328 0 0 1328 530 drivers/tty/tty_ldsem.o
> 316 0 0 316 13c drivers/tty/tty_mutex.o
> 2516 0 0 2516 9d4 drivers/tty/tty_port.o
> 51130 1067 345 52542 cd3e (TOTALS)
>
> With CONFIG_TTY=n and CONFIG_MINITTY_SERIAL=y, the above is replaced by:
>
> text data bss dec hex filename
> 8776 8 108 8892 22bc drivers/tty/serial/minitty_serial.o
tty_baudrate.o is missing here. It is included in serial_core.o when
CONFIG_TTY=y.
baruch
--
http://baruch.siach.name/blog/ ~. .~ Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
- [email protected] - tel: +972.52.368.4656, http://www.tkos.co.il -
meta-comment, any reason you didn't cc: linux-serial@vger as well?
On Thu, Mar 23, 2017 at 05:03:01PM -0400, Nicolas Pitre wrote:
> Many embedded systems don't need the full TTY layer support. Most of the
> time, the TTY layer is only a conduit for outputting debugging messages
> over a serial port. The TTY layer also implements many features that are
> very unlikely to ever be used in such a setup. There is great potential
> for both code and dynamic memory size reduction on small systems. This is
> what this patch series is achieving.
>
> The existing TTY code is quite large and complex. Trying to shrink it is
> rather risky as the potential for breakage is non negligeable. Therefore,
> the approach used here consists in the creation of the minimal code that
> interface with the existing UART drivers and provide TTY-like character
> devices to user space. When the regular TTY layer is disabled, then the
> minitty layer replacement is proposed by Kconfig.
>
> Of course, making it "mini" means there are limitations to what it does:
>
> - This supports serial ports only. No VT's, no PTY's.
>
> - The default n_tty line discipline is hardcoded and no other line
> discipline are supported.
>
> - The line discipline features are not all implemented. Notably, XON/XOFF
> is currently not implemented (although this might not require a lot of
> code to do it).
>
> - Hung-up state is not implemented.
>
> - No error handling on RX bytes other than counting them.
>
> - Behavior in the presence of overflows is most likely different from the
> full TTY code.
>
> - Job control is currently not supported (this may change in the future and
> be configurable).
>
> But again, most small embedded systems simply don't need those things.
This is true, and I like the overall idea, but I don't like all of the
code duplication. Also, who is going to maintain this? I'm not going
to be able to even build it, let alone test it, for the systems I
normally use, and now you have tagged me as maintaining it for forever
:(
> Here's some numbers using a minimal ARM config.
>
> When CONFIG_TTY=y, the following files are linked into the kernel:
>
> text data bss dec hex filename
> 8796 128 0 8924 22dc drivers/tty/n_tty.o
> 12846 276 44 13166 336e drivers/tty/serial/serial_core.o
> 4852 489 49 5390 150e drivers/tty/sysrq.o
> 1376 0 0 1376 560 drivers/tty/tty_buffer.o
> 13571 172 132 13875 3633 drivers/tty/tty_io.o
> 3072 0 0 3072 c00 drivers/tty/tty_ioctl.o
> 2457 2 120 2579 a13 drivers/tty/tty_ldisc.o
> 1328 0 0 1328 530 drivers/tty/tty_ldsem.o
> 316 0 0 316 13c drivers/tty/tty_mutex.o
> 2516 0 0 2516 9d4 drivers/tty/tty_port.o
> 51130 1067 345 52542 cd3e (TOTALS)
>
> With CONFIG_TTY=n and CONFIG_MINITTY_SERIAL=y, the above is replaced by:
>
> text data bss dec hex filename
> 8776 8 108 8892 22bc drivers/tty/serial/minitty_serial.o
>
> That's it! And the runtime buffer usage is much less as well.
Is there some way to just reorginize the existing code to get you almost
this same size? Make ptys and other line diciplines options to select,
and slim down the io path by removing features there.
And that serial core looks huge from what you are showing is really
needed, any way to slim that down by just making features in it
configurable?
Again, I like the idea, but worry that with this change, we would have
two different tty layers we have to maintain for the next 20+ years, and
we have a hard time keeping one stable and working today :)
thanks,
greg k-h
On Fri, 24 Mar 2017, Baruch Siach wrote:
> Hi Nicolas,
>
> On Thu, Mar 23, 2017 at 05:03:01PM -0400, Nicolas Pitre wrote:
> > Here's some numbers using a minimal ARM config.
> >
> > When CONFIG_TTY=y, the following files are linked into the kernel:
> >
> > text data bss dec hex filename
> > 8796 128 0 8924 22dc drivers/tty/n_tty.o
> > 12846 276 44 13166 336e drivers/tty/serial/serial_core.o
> > 4852 489 49 5390 150e drivers/tty/sysrq.o
> > 1376 0 0 1376 560 drivers/tty/tty_buffer.o
> > 13571 172 132 13875 3633 drivers/tty/tty_io.o
> > 3072 0 0 3072 c00 drivers/tty/tty_ioctl.o
> > 2457 2 120 2579 a13 drivers/tty/tty_ldisc.o
> > 1328 0 0 1328 530 drivers/tty/tty_ldsem.o
> > 316 0 0 316 13c drivers/tty/tty_mutex.o
> > 2516 0 0 2516 9d4 drivers/tty/tty_port.o
> > 51130 1067 345 52542 cd3e (TOTALS)
> >
> > With CONFIG_TTY=n and CONFIG_MINITTY_SERIAL=y, the above is replaced by:
> >
> > text data bss dec hex filename
> > 8776 8 108 8892 22bc drivers/tty/serial/minitty_serial.o
>
> tty_baudrate.o is missing here. It is included in serial_core.o when
> CONFIG_TTY=y.
It is also included when CONFIG_MINITTY_SERIAL=y, so for comparison
purpose I didn't list common files here.
Nicolas
On Fri, 24 Mar 2017, Greg Kroah-Hartman wrote:
> meta-comment, any reason you didn't cc: linux-serial@vger as well?
I didn't realize such a list even existed. I looked up "TTY LAYER" in
the maintainer file.
> On Thu, Mar 23, 2017 at 05:03:01PM -0400, Nicolas Pitre wrote:
> > Many embedded systems don't need the full TTY layer support. Most of the
> > time, the TTY layer is only a conduit for outputting debugging messages
> > over a serial port. The TTY layer also implements many features that are
> > very unlikely to ever be used in such a setup. There is great potential
> > for both code and dynamic memory size reduction on small systems. This is
> > what this patch series is achieving.
> >
> > The existing TTY code is quite large and complex. Trying to shrink it is
> > rather risky as the potential for breakage is non negligeable. Therefore,
> > the approach used here consists in the creation of the minimal code that
> > interface with the existing UART drivers and provide TTY-like character
> > devices to user space. When the regular TTY layer is disabled, then the
> > minitty layer replacement is proposed by Kconfig.
> >
> > Of course, making it "mini" means there are limitations to what it does:
> >
> > - This supports serial ports only. No VT's, no PTY's.
> >
> > - The default n_tty line discipline is hardcoded and no other line
> > discipline are supported.
> >
> > - The line discipline features are not all implemented. Notably, XON/XOFF
> > is currently not implemented (although this might not require a lot of
> > code to do it).
> >
> > - Hung-up state is not implemented.
> >
> > - No error handling on RX bytes other than counting them.
> >
> > - Behavior in the presence of overflows is most likely different from the
> > full TTY code.
> >
> > - Job control is currently not supported (this may change in the future and
> > be configurable).
> >
> > But again, most small embedded systems simply don't need those things.
>
> This is true, and I like the overall idea, but I don't like all of the
> code duplication. Also, who is going to maintain this? I'm not going
> to be able to even build it, let alone test it, for the systems I
> normally use, and now you have tagged me as maintaining it for forever
> :(
I'll maintain it. Will put the needed entry in MAINTAINERS.
Why do you say you won't be able to build it? I didn't try but it is
meant to build with any serial driver.
> > Here's some numbers using a minimal ARM config.
> >
> > When CONFIG_TTY=y, the following files are linked into the kernel:
> >
> > text data bss dec hex filename
> > 8796 128 0 8924 22dc drivers/tty/n_tty.o
> > 12846 276 44 13166 336e drivers/tty/serial/serial_core.o
> > 4852 489 49 5390 150e drivers/tty/sysrq.o
> > 1376 0 0 1376 560 drivers/tty/tty_buffer.o
> > 13571 172 132 13875 3633 drivers/tty/tty_io.o
> > 3072 0 0 3072 c00 drivers/tty/tty_ioctl.o
> > 2457 2 120 2579 a13 drivers/tty/tty_ldisc.o
> > 1328 0 0 1328 530 drivers/tty/tty_ldsem.o
> > 316 0 0 316 13c drivers/tty/tty_mutex.o
> > 2516 0 0 2516 9d4 drivers/tty/tty_port.o
> > 51130 1067 345 52542 cd3e (TOTALS)
> >
> > With CONFIG_TTY=n and CONFIG_MINITTY_SERIAL=y, the above is replaced by:
> >
> > text data bss dec hex filename
> > 8776 8 108 8892 22bc drivers/tty/serial/minitty_serial.o
> >
> > That's it! And the runtime buffer usage is much less as well.
>
> Is there some way to just reorginize the existing code to get you almost
> this same size? Make ptys and other line diciplines options to select,
> and slim down the io path by removing features there.
>
> And that serial core looks huge from what you are showing is really
> needed, any way to slim that down by just making features in it
> configurable?
>
> Again, I like the idea, but worry that with this change, we would have
> two different tty layers we have to maintain for the next 20+ years, and
> we have a hard time keeping one stable and working today :)
That's the crux of the argument: touching the current TTY layer is NOT
going to help keeping it stable. Here, not only I did remove features,
but the ones I kept were reimplemented to be much smaller and
potentially less scalable and performant too. The ultimate goal here is
to have the smallest code possible with very simple locking and not
necessarily the most scalable code. That in itself is contradictory with
the regular TTY code and warrants a separate implementation. And because
it is so small, it is much easier to understand and much easier to
maintain.
Where code sharing made sense, I did factor out common parts already,
such as the baudrate handling. I intend to do the same to add job
control support.
Nicolas
On Fri, Mar 24, 2017 at 08:31:45AM -0400, Nicolas Pitre wrote:
> On Fri, 24 Mar 2017, Greg Kroah-Hartman wrote:
>
> > meta-comment, any reason you didn't cc: linux-serial@vger as well?
>
> I didn't realize such a list even existed. I looked up "TTY LAYER" in
> the maintainer file.
Ah, didn't notice the list wasn't included there, I'll go fix that...
> > Again, I like the idea, but worry that with this change, we would have
> > two different tty layers we have to maintain for the next 20+ years, and
> > we have a hard time keeping one stable and working today :)
>
> That's the crux of the argument: touching the current TTY layer is NOT
> going to help keeping it stable. Here, not only I did remove features,
> but the ones I kept were reimplemented to be much smaller and
> potentially less scalable and performant too. The ultimate goal here is
> to have the smallest code possible with very simple locking and not
> necessarily the most scalable code. That in itself is contradictory with
> the regular TTY code and warrants a separate implementation. And because
> it is so small, it is much easier to understand and much easier to
> maintain.
So, what you are really saying here is "the current tty layer is too
messy, too complex, too big, and not understandable, so I'm going to
route around it by rewriting the whole thing just for my single-use-case
because I don't want to touch it."
That's a horrid thing to do.
Factoring things out is great. Routing around the existing working code
just because you want something "simpler" is not great. Refactor and
fix things up so you do understand it, because by ignoring it, you are
going to end up making the same mistakes that have already been fixed
with the existing 20+ years of tty layer development.
So please, take what we have, refactor, and carve things up so that the
_same_ code paths are being used for both "big and little" tty layers.
That way _everyone_ benifits, no need to have totally separate code
paths, and totally different files that different people maintain.
> Where code sharing made sense, I did factor out common parts already,
> such as the baudrate handling. I intend to do the same to add job
> control support.
The first two patches were great, I like those. Keep that work up, just
make it so that a single line disipline attached to a serial port,
without the pty stuff, works just fine and is tiny. I don't see why
that can't be possible.
thanks,
greg k-h
On Fri, 24 Mar 2017, Greg Kroah-Hartman wrote:
> On Fri, Mar 24, 2017 at 08:31:45AM -0400, Nicolas Pitre wrote:
> > That's the crux of the argument: touching the current TTY layer is NOT
> > going to help keeping it stable. Here, not only I did remove features,
> > but the ones I kept were reimplemented to be much smaller and
> > potentially less scalable and performant too. The ultimate goal here is
> > to have the smallest code possible with very simple locking and not
> > necessarily the most scalable code. That in itself is contradictory with
> > the regular TTY code and warrants a separate implementation. And because
> > it is so small, it is much easier to understand and much easier to
> > maintain.
>
> So, what you are really saying here is "the current tty layer is too
> messy, too complex, too big, and not understandable, so I'm going to
> route around it by rewriting the whole thing just for my single-use-case
> because I don't want to touch it."
That's not exactly what I'm saying.
Yes, the current TTY code is big. It has to, given that it is extremely
flexible, it can scale up and still be robust, and it covers a large
amount of use cases. Because of those characteristics, it fundamentally
cannot be made small. You just can't have it all.
I'm not saying that the current code is not understandable. I spent
considerable amount of my time understanding it, first and foremost to
get to know what I'm talking about, and find ways to shrink its memory
footprint initially. It is certainly complex because of the flexibility
and robustness it provides. My code most likely wouldn't perform as well
in the presence of multiple high-throughput channels for example. But
that's not my concern.
I'm concerned about small embedded systems where 85% of that code is
useless. In some cases the ability to change baudrate is also unneeded
so I intend to make that part configurable too.
But in the end there is simply no way I could achieve the same footprint
reduction with the existing code. This is clearly impossible.
For example, my code perform line discipline handling in the very same
buffer where the RX interrupt is storing new data. The existing TTY code
has up to 3 buffering layers because of the needed modularisation to
support swappable line discipline modules, etc. It is simply
unreasonable to expect that the later can be turned into the former
without either breaking things or severely restricting its scope.
Let's be honest here: the existing code _could_ possibly be reduced of
course. That would require a lot of efforts to gain 50% reduction maybe?
What I'm looking at with my proposal here is a 6x reduction factor and
I'm still not done with it. There is no way I could do that with the
existing code.
Let me give you some background as to what my fundamental motivation is,
and then maybe you'll understand why I'm doing this.
What is the biggest buzzword in the IT industry right now? It is IOT.
Most IOT targets are so small that people are rewriting new operating
systems from scratch for them. Lots of fragmentation already exists.
We're talking about systems with less than one megabyte of RAM,
sometimes much less. Still, those things are being connected to the
internet. And this is going to be a total security nightmare.
I wish to be able to leverage the Linux ecosystem for as much of the IOT
space as possible to avoid the worst of those nightmares. The Linux
ecosystem has a *lot* of knowledgeable people around it, a lot of
testing infrastructure and tooling available already, etc. If a
security issue turns up on Linux, it has a greater chance of being
caught early, or fixed quickly otherwise, and finding people with the
right knowledge is easier on Linux than it could be on any RTOS out
there. Still with me so far?
Yes we have tools that can automatically reduce the kernel size. We can
use LTO with the compiler, etc. LTO is pretty good already. It can
typically reduce the kernel size by 20%. If all system calls are
disabled except for a few ones, then LTO can get rid of another 20%.
The minimal kernel I get is still 400-500 KB in size. That's still too
big. Part of the size is this 60 KB of TTY + serial driver code just to
send some debugging messages out or do simple shell interactions! Now
with this mini TTY and one of the existing UART driver I'm down to 20
KB and there is still room for more reduction.
There is also this 120 KB of VFS code that is always there even though
there is no real filesystem at all configured in the kernel. There is
that other 100 KB of core driver support code despite the fact that the
set of drivers I'm using are very simple and basic. Etc.
For Linux to be suitable, it has to be small, damn small. My target is
256 KB of RAM. And if you look at the kind of application those 256 KB
systems are doing, it's basically one main task typically acquiring
sensor data and sending it in some crypted protocol over a wireless
network on the internet, and possibly accepting commands back. So what
do you need from the OS to achieve that? A few system calls, a minimal
scheduler, minimal memory management, minimal filesystem structure and
minimal network stack. And your user app.
So, why not having each of those blocks be created using the existing
Linux syscall interface and internal API? At that point, it should be
possible to take your standard full-featured Linux workstation and
develop your user app on it, run it there using all the existing native
debugging tools, etc. Also, it should be possible to swap some of those
kernel blocks for the tiny alternative in your kernel config and still
be able to boot such a kernel on your PC workstation and validate them
there, test them with the existing fuzers, etc. That's what I have here
with this mini TTY implementation. In the end you just take the mini
version of everything for the final target and you're done. And you
don't have to learn a whole new development environment and program
model, etc.
I hope you'd agree with me that for such a goal, I cannot just try to
shrink the existing code. There has to be a parallel implementation of
some blocks alongside the main one that preserves the existing API but
that provides much less scalability and fewer features. Next on my list
would be a cache-less, completely serialized VFS alternative that has
only what's needed to make the link between the read/write syscalls, a
filesystem driver and a block driver. And by being really small, the
maintenance cost of a parallel implementation isn't very high, certainly
much less than trying to maintain a single version that can scale to
both extremes.
Hence this series, which I hope could be the beginning of a trend for
allowing Linux into the largest computing device deployment to come.
Nicolas
On 24 March 2017 at 13:53, Greg Kroah-Hartman
<[email protected]> wrote:
> On Fri, Mar 24, 2017 at 08:31:45AM -0400, Nicolas Pitre wrote:
>> On Fri, 24 Mar 2017, Greg Kroah-Hartman wrote:
>>
>> > meta-comment, any reason you didn't cc: linux-serial@vger as well?
>>
>> I didn't realize such a list even existed. I looked up "TTY LAYER" in
>> the maintainer file.
>
> Ah, didn't notice the list wasn't included there, I'll go fix that...
>
>> > Again, I like the idea, but worry that with this change, we would have
>> > two different tty layers we have to maintain for the next 20+ years, and
>> > we have a hard time keeping one stable and working today :)
>>
>> That's the crux of the argument: touching the current TTY layer is NOT
>> going to help keeping it stable. Here, not only I did remove features,
>> but the ones I kept were reimplemented to be much smaller and
>> potentially less scalable and performant too. The ultimate goal here is
>> to have the smallest code possible with very simple locking and not
>> necessarily the most scalable code. That in itself is contradictory with
>> the regular TTY code and warrants a separate implementation. And because
>> it is so small, it is much easier to understand and much easier to
>> maintain.
>
> So, what you are really saying here is "the current tty layer is too
> messy, too complex, too big, and not understandable, so I'm going to
> route around it by rewriting the whole thing just for my single-use-case
> because I don't want to touch it."
>
> That's a horrid thing to do.
>
> Factoring things out is great. Routing around the existing working code
> just because you want something "simpler" is not great. Refactor and
> fix things up so you do understand it, because by ignoring it, you are
> going to end up making the same mistakes that have already been fixed
> with the existing 20+ years of tty layer development.
>
> So please, take what we have, refactor, and carve things up so that the
> _same_ code paths are being used for both "big and little" tty layers.
> That way _everyone_ benifits, no need to have totally separate code
> paths, and totally different files that different people maintain.
>
As I understand it, the memory saving is not only due to having less
code, but also due to the fact that functionality that exists as
distinct layers in the full featured TTY stack is collapsed into a
single layer, requiring substantially less memory for buffers.
I guess you could call collapsing layers like this 'routing around
it', but the point is that the reason for doing so is not that the
code is too complex or too big, but simply that the flexibility
offered by a deep stack is fundamentally irreconcilable with a shallow
one that is hardwired for a serial debug port.
>> Where code sharing made sense, I did factor out common parts already,
>> such as the baudrate handling. I intend to do the same to add job
>> control support.
>
> The first two patches were great, I like those. Keep that work up, just
> make it so that a single line disipline attached to a serial port,
> without the pty stuff, works just fine and is tiny. I don't see why
> that can't be possible.
>
> thanks,
>
> greg k-h
>
> _______________________________________________
> linux-arm-kernel mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel