diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 07aca5184a55dd38036587c3485ba9f12d2e7ec7..f79c6bc06af5bf19f3fd8d9e8aae8aa905240c54 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -3,6 +3,7 @@ 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 tty_jobctrl.o \ n_null.o +obj-$(CONFIG_CONSOLE_MONITOR) += tty_stallinfo.o obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-$(CONFIG_AUDIT) += tty_audit.o diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 6c9a408d67cd68282696f49715e95662a0d018b5..b35bed1d39db88f4879ee64e8a42291344ed45b6 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -50,6 +50,10 @@ #include "tty.h" +#ifdef CONFIG_CONSOLE_MONITOR +#include +#endif + /* * Until this number of characters is queued in the xmit buffer, select will * return "we have room for writes". @@ -1252,11 +1256,17 @@ static bool n_tty_receive_char_flow_ctrl(struct tty_struct *tty, u8 c, if (c == START_CHAR(tty)) { start_tty(tty); process_echoes(tty); + #ifdef CONFIG_CONSOLE_MONITOR + write_to_tty_record((void *)tty, c, RECD_SPECIAL_KEY); + #endif return true; } /* STOP_CHAR */ stop_tty(tty); + #ifdef CONFIG_CONSOLE_MONITOR + write_to_tty_record((void *)tty, c, RECD_SPECIAL_KEY); + #endif return true; } @@ -2423,7 +2433,23 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, } up_read(&tty->termios_rwsem); + #ifdef CONFIG_CONSOLE_MONITOR + if (wait_woken(&wait, TASK_INTERRUPTIBLE, + HZ * get_step_timeout()) == 0) { + + if (tty->port->console) { + tty_stall_info(tty); + retval = -EIO; + down_read(&tty->termios_rwsem); + break; + } + /* must in 'timeo == 0' */ + wait_woken(&wait, TASK_INTERRUPTIBLE, + MAX_SCHEDULE_TIMEOUT); + } + #else wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); + #endif down_read(&tty->termios_rwsem); } diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 3b55c275ecb7ea54f888277b827ca7bef53c034c..dbd5095ed7849808a68a177d71379996940c4c09 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -33,6 +33,10 @@ #include #include +#ifdef CONFIG_CONSOLE_MONITOR +#include +#endif + #include #include @@ -1960,14 +1964,21 @@ int serial8250_handle_irq(struct uart_port *port, unsigned int iir) d = irq_get_irq_data(port->irq); if (d && irqd_is_wakeup_set(d)) pm_wakeup_event(tport->tty->dev, 0); - if (!up->dma || handle_rx_dma(up, iir)) + if (!up->dma || handle_rx_dma(up, iir)) { status = serial8250_rx_chars(up, status); + #ifdef CONFIG_CONSOLE_MONITOR + record_uart_irqinfo(port, UART_IRQ_RX); + #endif + } } serial8250_modem_status(up); if ((status & UART_LSR_THRE) && (up->ier & UART_IER_THRI)) { - if (!up->dma || up->dma->tx_err) + if (!up->dma || up->dma->tx_err) { serial8250_tx_chars(up); - else if (!up->dma->tx_running) + #ifdef CONFIG_CONSOLE_MONITOR + record_uart_irqinfo(port, UART_IRQ_TX); + #endif + } else if (!up->dma->tx_running) __stop_tx(up); } @@ -3309,6 +3320,9 @@ void serial8250_init_port(struct uart_8250_port *up) port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_8250_CONSOLE); up->cur_iotype = 0xFF; + #ifdef CONFIG_CONSOLE_MONITOR + register_uart_info_collect(get_uart_info); + #endif } EXPORT_SYMBOL_GPL(serial8250_init_port); diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index cc47b49973796fbd602cb1c9ca81fd12ae28947c..b77616abec5341c6dbfe72656bd2ee95b2cbcaa6 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -41,6 +41,9 @@ #include #include #include +#ifdef CONFIG_CONSOLE_MONITOR +#include +#endif #define UART_NR 14 @@ -1544,6 +1547,19 @@ static void check_apply_cts_event_workaround(struct uart_amba_port *uap) pl011_read(uap, REG_ICR); } +#ifdef CONFIG_CONSOLE_MONITOR +void get_pl011_status(struct uart_port *port) +{ + struct uart_amba_port *uap = container_of(port, struct uart_amba_port, port); + pr_info("ris=0x%x, mis=0x%x, fr=0x%x, lr=0x%x, imsc=0x%x\n", + pl011_read(uap, REG_RIS), + pl011_read(uap, REG_MIS), + pl011_read(uap, REG_FR), + pl011_read(uap, REG_LCRH_RX), + pl011_read(uap, ZX_UART011_IMSC)); +} +#endif + static irqreturn_t pl011_int(int irq, void *dev_id) { struct uart_amba_port *uap = dev_id; @@ -1566,13 +1582,20 @@ static irqreturn_t pl011_int(int irq, void *dev_id) pl011_dma_rx_irq(uap); else pl011_rx_chars(uap); + #ifdef CONFIG_CONSOLE_MONITOR + record_uart_irqinfo(&uap->port, UART_IRQ_RX); + #endif } if (status & (UART011_DSRMIS|UART011_DCDMIS| UART011_CTSMIS|UART011_RIMIS)) pl011_modem_status(uap); - if (status & UART011_TXIS) + if (status & UART011_TXIS) { pl011_tx_chars(uap, true); + #ifdef CONFIG_CONSOLE_MONITOR + record_uart_irqinfo(&uap->port, UART_IRQ_TX); + #endif + } if (pass_counter-- == 0) break; diff --git a/drivers/tty/tty_stallinfo.c b/drivers/tty/tty_stallinfo.c new file mode 100644 index 0000000000000000000000000000000000000000..e351081b3cb32613dc715b537bcca904acc54624 --- /dev/null +++ b/drivers/tty/tty_stallinfo.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static T_uart_print_info uart_print_info; +struct dentry *console_monitor_dir; +int step_timeout = 10; + +#define TTY_RECD_SIZE 10 +struct tty_record { + unsigned long var; + int flag; + unsigned long ts; + void *ptr; +} tty_record_data[TTY_RECD_SIZE]; + +/* uart */ +#define REC_UART_NUM 5 +struct uart_irq_data { + int irq; + unsigned long rx_count; + unsigned long last_rx_irqtime; + unsigned long tx_count; + unsigned long last_tx_irqtime; +} uart_irqinfo[REC_UART_NUM]; + +void record_uart_irqinfo(struct uart_port *port, int flag) +{ + int idx = port->irq % REC_UART_NUM; + + uart_irqinfo[idx].irq = port->irq; + + if (flag == UART_IRQ_RX) { + uart_irqinfo[idx].rx_count++; + uart_irqinfo[idx].last_rx_irqtime = jiffies; + } else { + uart_irqinfo[idx].tx_count++; + uart_irqinfo[idx].last_tx_irqtime = jiffies; + } +} + +static void print_uart_irqinfo(struct uart_port *port) +{ + int idx = port->irq % REC_UART_NUM; + + pr_info("current jiffies=%ld, last_rx_irqtime=%ld, count=%ld\n", + jiffies, uart_irqinfo[idx].last_rx_irqtime, + uart_irqinfo[idx].rx_count); + pr_info("current jiffies=%ld, last_tx_irqtime=%ld, count=%ld\n", + jiffies, uart_irqinfo[idx].last_tx_irqtime, + uart_irqinfo[idx].tx_count); +} + +#ifdef CONFIG_SERIAL_AMBA_PL011 +void __weak get_pl011_status(struct uart_port *port) { } +#else +void get_8250_status(struct uart_port *uport) +{ + unsigned int status, ier, iir, mcr; + + status = serial_port_in(uport, UART_LSR); + ier = serial_port_in(uport, UART_IER); + iir = serial_port_in(uport, UART_IIR); + mcr = serial_port_in(uport, UART_MCR); + pr_info("status=0x%x, ier=0x%x, iir=0x%x, mcr=0x%x\n", + status, ier, iir, mcr); +} +#endif + +void get_uart_info(struct tty_struct *tty) +{ + struct uart_state *state = (struct uart_state *)tty->driver_data; + struct uart_port *uport = state->uart_port; + struct circ_buf *xmit = &uport->state->xmit; + + pr_info("name=%s, icount.tx=%d, rx=%d\n", + uport->name, uport->icount.tx, uport->icount.rx); + pr_info("circ_buf free=%ld, all=%ld\n", + uart_circ_chars_free(xmit), UART_XMIT_SIZE); + +#ifdef CONFIG_SERIAL_AMBA_PL011 + get_pl011_status(uport); +#else + get_8250_status(uport); +#endif + + print_uart_irqinfo(uport); +} +void write_to_tty_record(void *ptr, unsigned long var, int flag) +{ + static int index; + + tty_record_data[index].var = var; + tty_record_data[index].flag = flag; + tty_record_data[index].ts = sched_clock(); + + tty_record_data[index].ptr = ptr; + + index = (index + 1) % TTY_RECD_SIZE; +} + +void print_tty_record(void *ptr) +{ + int i; + + for (i = 0; i < TTY_RECD_SIZE; i++) { + if (ptr == tty_record_data[i].ptr) { + pr_info("tty receive speical key[%d] ts=%ld, flag=%d var=0x%lx\n", + i, tty_record_data[i].ts, + tty_record_data[i].flag, tty_record_data[i].var); + } + } +} + +void register_uart_info_collect(T_uart_print_info getinfo) +{ + uart_print_info = getinfo; +} + + +void tty_stall_info(struct tty_struct *tty) +{ + static int limit = 5; + int saved_console_loglevel = console_loglevel; + + if (limit-- <= 0) + return; + + console_loglevel = CONSOLE_LOGLEVEL_MOTORMOUTH; + pr_info("\n"); + pr_info("cur: loglevel %d, %s:%d tgid:%d, %s write stall over %d seconds\n", + saved_console_loglevel, + current->comm, current->pid, + current->tgid, tty->name, get_step_timeout()); + pr_info("tty->stopped=%d, hw_stopped=%d, tco_stopped=%d\n", + tty->flow.stopped, tty->hw_stopped, tty->flow.tco_stopped); + pr_info("c_iflag=0%o c_oflag=0%o, c_cflag=0%o c_lflag=0%o\n", + tty->termios.c_iflag, tty->termios.c_oflag, + tty->termios.c_cflag, tty->termios.c_lflag); + print_tty_record((void *)tty); + + if (uart_print_info && tty->port->console) { + uart_print_info(tty); + msleep(20); + uart_print_info(tty); + } + console_loglevel = saved_console_loglevel; +} + +int get_step_timeout(void) +{ + return step_timeout; +} + +static int timeout_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "%d\n", step_timeout); + + return 0; +} + +static int timeout_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, timeout_proc_show, NULL); +} + +static ssize_t timeout_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *data) +{ + int ret, val; + + ret = kstrtoint_from_user(buffer, count, 10, &val); + if (ret == 0) { + step_timeout = val; + } else { + return -EINVAL; + } + + return count; +} + +static const struct file_operations step_timeout_fops = { + .owner = THIS_MODULE, + .open = timeout_proc_open, + .read = seq_read, + .release = single_release, + .write = timeout_proc_write, +}; + +static int __init console_monitor_module_init(void) +{ + int i; + + for (i = 0; i < TTY_RECD_SIZE; i++) { + tty_record_data[i].var = 0; + tty_record_data[i].flag = 0; + tty_record_data[i].ts = 0; + tty_record_data[i].ptr = NULL; + } + + console_monitor_dir = debugfs_create_dir("console_monitor", NULL); + debugfs_create_file("step_timeout", 0644, console_monitor_dir, NULL, &step_timeout_fops); + + return 0; +} + +static void __exit console_monitor_module_exit(void) +{ + debugfs_remove_recursive(console_monitor_dir); +} + +module_init(console_monitor_module_init); +module_exit(console_monitor_module_exit); +MODULE_LICENSE("GPL"); diff --git a/include/linux/tty_stallinfo.h b/include/linux/tty_stallinfo.h new file mode 100644 index 0000000000000000000000000000000000000000..29dcc5b65282cae5a31255c91ecbcf88256ed303 --- /dev/null +++ b/include/linux/tty_stallinfo.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_TTY_STALLINFO_H +#define _LINUX_TTY_STALLINFO_H +#include +#include + +#define RECD_SPECIAL_KEY 1 + +typedef void (*T_uart_print_info)(struct tty_struct *tty); +void register_uart_info_collect(T_uart_print_info getinfo); +void tty_stall_info(struct tty_struct *tty); +void write_to_tty_record(void *ptr, unsigned long var, int flag); + +/* uart */ +#define UART_IRQ_RX 1 +#define UART_IRQ_TX 0 +void get_uart_info(struct tty_struct *tty); +void record_uart_irqinfo(struct uart_port *port, int flag); +void get_pl011_status(struct uart_port *port); +int get_step_timeout(void); +#endif