struct mctrl_gpios *gpios;
+ /* counter to stop 0xff flood */
+ int idle_counter;
+
/* shadow registers */
unsigned int ucr1;
unsigned int ucr2;
imx_uart_writel(sport, ubir, UBIR);
imx_uart_writel(sport, ubmr, UBMR);
imx_uart_writel(sport, uts, IMX21_UTS);
+
+ sport->idle_counter = 0;
}
/* called with port.lock taken and irqs off */
return IRQ_HANDLED;
}
+/* Check if hardware Rx flood is in progress, and issue soft reset to stop it.
+ * This is to be called from Rx ISRs only when some bytes were actually
+ * received.
+ *
+ * A way to reproduce the flood (checked on iMX6SX) is: open iMX UART at 9600
+ * 8N1, and from external source send 0xf0 char at 115200 8N1. In about 90% of
+ * cases this starts a flood of "receiving" of 0xff characters by the iMX6 UART
+ * that is terminated by any activity on RxD line, or could be stopped by
+ * issuing soft reset to the UART (just stop/start of RX does not help). Note
+ * that what we do here is sending isolated start bit about 2.4 times shorter
+ * than it is to be on UART configured baud rate.
+ */
+static void imx_uart_check_flood(struct imx_port *sport, u32 usr2)
+{
+ /* To detect hardware 0xff flood we monitor RxD line between RX
+ * interrupts to isolate "receiving" of char(s) with no activity
+ * on RxD line, that'd never happen on actual data transfers.
+ *
+ * We use USR2_WAKE bit to check for activity on RxD line, but we have a
+ * race here if we clear USR2_WAKE when receiving of a char is in
+ * progress, so we might get RX interrupt later with USR2_WAKE bit
+ * cleared. Note though that as we don't try to clear USR2_WAKE when we
+ * detected no activity, this race may hide actual activity only once.
+ *
+ * Yet another case where receive interrupt may occur without RxD
+ * activity is expiration of aging timer, so we consider this as well.
+ *
+ * We use 'idle_counter' to ensure that we got at least so many RX
+ * interrupts without any detected activity on RxD line. 2 cases
+ * described plus 1 to be on the safe side gives us a margin of 3,
+ * below. In practice I was not able to produce a false positive to
+ * induce soft reset at regular data transfers even using 1 as the
+ * margin, so 3 is actually very strong.
+ *
+ * We count interrupts, not chars in 'idle-counter' for simplicity.
+ */
+
+ if (usr2 & USR2_WAKE) {
+ imx_uart_writel(sport, USR2_WAKE, USR2);
+ sport->idle_counter = 0;
+ } else if (++sport->idle_counter > 3) {
+ dev_warn(sport->port.dev, "RX flood detected: soft reset.");
+ imx_uart_soft_reset(sport); /* also clears 'sport->idle_counter' */
+ }
+}
+
static irqreturn_t __imx_uart_rxint(int irq, void *dev_id)
{
struct imx_port *sport = dev_id;
unsigned int rx, flg, ignored = 0;
struct tty_port *port = &sport->port.state->port;
+ u32 usr2;
+
+ usr2 = imx_uart_readl(sport, USR2);
- while (imx_uart_readl(sport, USR2) & USR2_RDR) {
- u32 usr2;
+ /* If we received something, check for 0xff flood */
+ if (usr2 & USR2_RDR)
+ imx_uart_check_flood(sport, usr2);
+ for ( ; usr2 & USR2_RDR; usr2 = imx_uart_readl(sport, USR2)) {
flg = TTY_NORMAL;
sport->port.icount.rx++;
status = dmaengine_tx_status(chan, sport->rx_cookie, &state);
if (status == DMA_ERROR) {
+ spin_lock(&sport->port.lock);
imx_uart_clear_rx_errors(sport);
+ spin_unlock(&sport->port.lock);
return;
}
- if (!(sport->port.ignore_status_mask & URXD_DUMMY_READ)) {
+ /*
+ * The state-residue variable represents the empty space
+ * relative to the entire buffer. Taking this in consideration
+ * the head is always calculated base on the buffer total
+ * length - DMA transaction residue. The UART script from the
+ * SDMA firmware will jump to the next buffer descriptor,
+ * once a DMA transaction if finalized (IMX53 RM - A.4.1.2.4).
+ * Taking this in consideration the tail is always at the
+ * beginning of the buffer descriptor that contains the head.
+ */
- /*
- * The state-residue variable represents the empty space
- * relative to the entire buffer. Taking this in consideration
- * the head is always calculated base on the buffer total
- * length - DMA transaction residue. The UART script from the
- * SDMA firmware will jump to the next buffer descriptor,
- * once a DMA transaction if finalized (IMX53 RM - A.4.1.2.4).
- * Taking this in consideration the tail is always at the
- * beginning of the buffer descriptor that contains the head.
- */
+ /* Calculate the head */
+ rx_ring->head = sg_dma_len(sgl) - state.residue;
- /* Calculate the head */
- rx_ring->head = sg_dma_len(sgl) - state.residue;
+ /* Calculate the tail. */
+ bd_size = sg_dma_len(sgl) / sport->rx_periods;
+ rx_ring->tail = ((rx_ring->head-1) / bd_size) * bd_size;
- /* Calculate the tail. */
- bd_size = sg_dma_len(sgl) / sport->rx_periods;
- rx_ring->tail = ((rx_ring->head-1) / bd_size) * bd_size;
+ if (rx_ring->head <= sg_dma_len(sgl) &&
+ rx_ring->head > rx_ring->tail) {
- if (rx_ring->head <= sg_dma_len(sgl) &&
- rx_ring->head > rx_ring->tail) {
+ /* Move data from tail to head */
+ r_bytes = rx_ring->head - rx_ring->tail;
- /* Move data from tail to head */
- r_bytes = rx_ring->head - rx_ring->tail;
+ /* If we received something, check for 0xff flood */
+ if (r_bytes > 0) {
+ spin_lock(&sport->port.lock);
+ imx_uart_check_flood(sport, imx_uart_readl(sport, USR2));
+ spin_unlock(&sport->port.lock);
+ }
+
+ if (!(sport->port.ignore_status_mask & URXD_DUMMY_READ)) {
/* CPU claims ownership of RX DMA buffer */
dma_sync_sg_for_cpu(sport->port.dev, sgl, 1,
- DMA_FROM_DEVICE);
+ DMA_FROM_DEVICE);
w_bytes = tty_insert_flip_string(port,
- sport->rx_buf + rx_ring->tail, r_bytes);
+ sport->rx_buf + rx_ring->tail, r_bytes);
/* UART retrieves ownership of RX DMA buffer */
dma_sync_sg_for_device(sport->port.dev, sgl, 1,
- DMA_FROM_DEVICE);
+ DMA_FROM_DEVICE);
if (w_bytes != r_bytes)
sport->port.icount.buf_overrun++;
sport->port.icount.rx += w_bytes;
- } else {
- WARN_ON(rx_ring->head > sg_dma_len(sgl));
- WARN_ON(rx_ring->head <= rx_ring->tail);
}
+ } else {
+ WARN_ON(rx_ring->head > sg_dma_len(sgl));
+ WARN_ON(rx_ring->head <= rx_ring->tail);
}
if (w_bytes) {
imx_uart_writel(sport, USR2_ORE, USR2);
}
+ sport->idle_counter = 0;
+
}
#define TXTL_DEFAULT 2 /* reset default */