From 67ec4318c031c0ebaaceff126dc58341bf214432 Mon Sep 17 00:00:00 2001 From: wanglongjie Date: Tue, 14 Oct 2025 16:37:04 +0800 Subject: [PATCH] embsys: console monitor Modify the serial port driver subsystem to add maintenance and diagnostic information in abnormal serial port scenarios. This resolves the issue of system tasks being blocked for extended periods due to serial port abnormalities, making it difficult to locate the problem, thereby improving fault diagnosis efficiency. Signed-off-by: wanglongjie Signed-off-by: Wenya Zhang Reviewed-by: Huang Jian Link: https://gitee.com/anolis/embedded-kernel/pulls/1034 --- drivers/tty/Makefile | 1 + drivers/tty/n_tty.c | 26 ++++ drivers/tty/serial/8250/8250_port.c | 20 ++- drivers/tty/serial/amba-pl011.c | 25 +++- drivers/tty/tty_stallinfo.c | 223 ++++++++++++++++++++++++++++ include/linux/tty_stallinfo.h | 21 +++ 6 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 drivers/tty/tty_stallinfo.c create mode 100644 include/linux/tty_stallinfo.h diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 07aca5184a55..f79c6bc06af5 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 6c9a408d67cd..b35bed1d39db 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 3b55c275ecb7..dbd5095ed784 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 cc47b4997379..b77616abec53 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 000000000000..e351081b3cb3 --- /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 000000000000..29dcc5b65282 --- /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 -- Gitee