From b3d0fa0d74a66db371f7b04bcb84a763ed8b6293 Mon Sep 17 00:00:00 2001 From: Daniel Thompson Date: Fri, 28 Mar 2014 09:28:11 +0000 Subject: serial: Emulate break using control characters Currently the magic SysRq functions can accessed by sending a break on the serial port. Unfortunately some networked serial proxies make it difficult to send a break meaning SysRq functions cannot be used. This patch provides a workaround by allowing the (fairly unlikely) sequence of ^B^R^K characters to emulate a real break. This approach is very nearly as robust as normal sysrq/break handling because all trigger recognition happens during interrupt handling. Only major difference is that to emulate a break we must enter the ISR four times (instead of twice) and manage an extra byte of state. No means is provided to escape the trigger sequence (and pass ^B^R^K to the underlying process) however the sequence is proved reasonably pretty collision resistant in practice. The most significant consequence is that ^B and ^B^R are delayed until a new character is observed. The most significant collision I am aware of is with emacs-like backward-char bindings (^B) because the character movement will become lumpy (two characters every two key presses rather than one character per key press). Arrow keys or ^B^B^F provide workarounds. Special note for tmux users: tmux defaults to using ^B as its escape character but does not have a default binding for ^B^R. Likewise tmux had no visual indicator showing the beginning of break sequence meaning delayed the delivery of ^B is not observable. Thus serial break emulation does not interfere with the use of tmux's default key bindings. Signed-off-by: Daniel Thompson --- include/linux/serial_core.h | 83 +++++++++++++++++++++++++++++++++++---------- lib/Kconfig.debug | 15 ++++++++ 2 files changed, 80 insertions(+), 18 deletions(-) diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 219b6a30d475..6c86843025f4 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -156,6 +156,9 @@ struct uart_port { struct console *cons; /* struct console, if any */ #if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ) unsigned long sysrq; /* sysrq timeout */ +#ifdef CONFIG_MAGIC_SYSRQ_BREAK_EMULATION + char sysrq_emul; /* emulation state */ +#endif #endif /* flags must be updated while holding port mutex */ @@ -383,24 +386,6 @@ extern void uart_handle_cts_change(struct uart_port *uport, extern void uart_insert_char(struct uart_port *port, unsigned int status, unsigned int overrun, unsigned int ch, unsigned int flag); -#ifdef SUPPORT_SYSRQ -static inline int -uart_handle_sysrq_char(struct uart_port *port, unsigned int ch) -{ - if (port->sysrq) { - if (ch && time_before(jiffies, port->sysrq)) { - handle_sysrq(ch); - port->sysrq = 0; - return 1; - } - port->sysrq = 0; - } - return 0; -} -#else -#define uart_handle_sysrq_char(port,ch) ({ (void)port; 0; }) -#endif - /* * We do the SysRQ and SAK checking like this... */ @@ -425,6 +410,68 @@ static inline int uart_handle_break(struct uart_port *port) return 0; } +#if defined(SUPPORT_SYSRQ) && defined(CONFIG_MAGIC_SYSRQ_BREAK_EMULATION) +/* + * Emulate a break if we are the serial console and receive ^B, ^R, ^K. + */ +static inline int +uart_handle_sysrq_break_emulation(struct uart_port *port, unsigned int ch) +{ + const unsigned int ctrlb = 'B' & 31; + const unsigned int ctrlr = 'R' & 31; + const unsigned int ctrlk = 'K' & 31; + + if (uart_console(port)) { + if ((port->sysrq_emul == 0 && ch == ctrlb) || + (port->sysrq_emul == ctrlb && ch == ctrlr)) { + /* for either of the first two trigger characters + * update the state variable and move on. + */ + port->sysrq_emul = ch; + return 1; + } else if (port->sysrq_emul == ctrlr && ch == ctrlk && + uart_handle_break(port)) { + /* the break has already been emulated whilst + * evaluating the condition, tidy up and move on + */ + port->sysrq_emul = 0; + return 1; + } + } + + if (port->sysrq_emul) { + /* received a partial (false) trigger, tidy up and move on */ + uart_insert_char(port, 0, 0, ctrlb, TTY_NORMAL); + if (port->sysrq_emul == ctrlr) + uart_insert_char(port, 0, 0, ctrlr, TTY_NORMAL); + port->sysrq_emul = 0; + } + + return 0; +} +#else +#define uart_handle_sysrq_break_emulation(port, ch) ({ (void)port; 0; }) +#endif + +#ifdef SUPPORT_SYSRQ +static inline int +uart_handle_sysrq_char(struct uart_port *port, unsigned int ch) +{ + if (port->sysrq) { + if (ch && time_before(jiffies, port->sysrq)) { + handle_sysrq(ch); + port->sysrq = 0; + return 1; + } + port->sysrq = 0; + } + + return uart_handle_sysrq_break_emulation(port, ch); +} +#else +#define uart_handle_sysrq_char(port, ch) ({ (void)port; 0; }) +#endif + /* * UART_ENABLE_MS - determine if port should enable modem status irqs */ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 75d24da2c892..be7bafbd891b 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -345,6 +345,21 @@ config MAGIC_SYSRQ_DEFAULT_ENABLE This may be set to 1 or 0 to enable or disable them all, or to a bitmask as described in Documentation/sysrq.txt. +config MAGIC_SYSRQ_BREAK_EMULATION + bool "Enable magic SysRq serial break emulation" + depends on MAGIC_SYSRQ && SERIAL_CORE_CONSOLE + default n + help + If you say Y here, then you can use the character sequence ^B^R^K + to simulate a BREAK on the serial console. This is useful if for + some reason you cannot send a BREAK to your console's serial port. + For example, if you have a serial device server that cannot + send a BREAK. Enabling this feature can delay the delivery of + characters to the TTY because the ^B and a subsequent ^R will be + delayed until we know what the next character is. + + If unsure, say N. + config DEBUG_KERNEL bool "Kernel debugging" help -- cgit v1.2.3