Tool/software: Linux
We have am5708 based custom board running linux 4.4.41 (TI linux based). UART1 is used as communication bus to control slave devices using rs485 based protocol (8 bits / 115200 baud) . For this we implemented a line discipline on top of tty subsystem / omap serial driver (drivers/tty/serial/omap_serial.c). UART2 is used for serial console.
The communication bus through UART1 is working as expected but we observe during endurance tests that after a couple of hours stressing the bus the linux system hangs due to endless interrupt loop caused by an unexpected UART_IIR_MSI interrupt.
Our protocol line discipline or any other software component does NOT enable the MSI interrupt (i.e. modem status register (bit 3) interrupt) in the UART_IER register for UART1.
With JTAG debug we determined that serial_omap_irq() gets called and UART_IIR_MSI is raised. The UART_IER_MSI bit check in check_modem_status() function shows it is not set.
We search for a clarification/guideline why this UART_IIR_MSI interrupt can occur when it is not enabled in IER register and why it not gets cleared resulting in endless loop / hang of our system.
Thanks in advance,
Andre Hoffard
static irqreturn_t serial_omap_irq(int irq, void *dev_id) { struct uart_omap_port *up = dev_id; unsigned int iir, lsr; unsigned int type; irqreturn_t ret = IRQ_NONE; int max_count = 256; spin_lock(&up->port.lock); pm_runtime_get_sync(up->dev); do { iir = serial_in(up, UART_IIR); if (iir & UART_IIR_NO_INT) break; ret = IRQ_HANDLED; lsr = serial_in(up, UART_LSR); /* extract IRQ type from IIR register */ type = iir & 0x3e; switch (type) { case UART_IIR_MSI: check_modem_status(up); break; case UART_IIR_THRI: transmit_chars(up, lsr); break; case UART_IIR_RX_TIMEOUT: /* FALLTHROUGH */ case UART_IIR_RDI: serial_omap_rdi(up, lsr); break; case UART_IIR_RLSI: serial_omap_rlsi(up, lsr); break; case UART_IIR_CTS_RTS_DSR: /* simply try again */ break; case UART_IIR_XOFF: /* FALLTHROUGH */ default: break; } } while (!(iir & UART_IIR_NO_INT) && max_count--); spin_unlock(&up->port.lock); tty_flip_buffer_push(&up->port.state->port); pm_runtime_mark_last_busy(up->dev); pm_runtime_put_autosuspend(up->dev); up->port_activity = jiffies; return ret; } static unsigned int check_modem_status(struct uart_omap_port *up) { unsigned int status; status = serial_in(up, UART_MSR); status |= up->msr_saved_flags; up->msr_saved_flags = 0; if ((status & UART_MSR_ANY_DELTA) == 0) return status; if (status & UART_MSR_ANY_DELTA && up->ier & UART_IER_MSI && up->port.state != NULL) { if (status & UART_MSR_TERI) up->port.icount.rng++; if (status & UART_MSR_DDSR) up->port.icount.dsr++; if (status & UART_MSR_DDCD) uart_handle_dcd_change (&up->port, status & UART_MSR_DCD); if (status & UART_MSR_DCTS) uart_handle_cts_change (&up->port, status & UART_MSR_CTS); wake_up_interruptible(&up->port.state->port.delta_msr_wait); } return status; }