Part Number: AM5708
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;
}