serial: tegra: correct error handling sequence to avoid system hang
Shardar Shariff Md [Tue, 2 Aug 2016 13:35:26 +0000 (18:35 +0530)]
- Correct the error handling sequence to avoid FIFO errors
i.e handle break error first followed by other errors as
handling break error will clear other errors
- Handle break error by fifo flush as per IAS
- serial: tegra: keep rx irq disabled if there are spurious
errors and then tty buffer is exhausted and re-enable the
interrupts after 500msec.

Bug 1785924

Change-Id: I68c8322e0ff6808ebd0d38141853f88900b912b2
Signed-off-by: Shardar Shariff Md <smohammed@nvidia.com>
Reviewed-on: http://git-master/r/1195970
(cherry picked from commit 16317988c222e831346902e28bfce5375bea3df2)
Reviewed-on: http://git-master/r/1198554
GVS: Gerrit_Virtual_Submit
Reviewed-by: Shreshtha Sahu <ssahu@nvidia.com>
Tested-by: Shreshtha Sahu <ssahu@nvidia.com>
Reviewed-by: Winnie Hsu <whsu@nvidia.com>

drivers/tty/serial/serial-tegra.c

index c1ed386..ba43507 100644 (file)
@@ -86,6 +86,8 @@
 #define TEGRA_TX_DMA                           2
 
 #define TEGRA_UART_FCR_IIR_FIFO_EN             0x40
+#define TEGRA_UART_MAX_RX_CHARS                        256
+#define TEGRA_UART_MAX_REPEAT_ERRORS           100
 
 /**
  * tegra_uart_chip_data: SOC specific data.
@@ -147,6 +149,8 @@ struct tegra_uart_port {
        bool                            use_rx_pio;
        struct timer_list           timer;
        int                 timer_timeout_jiffies;
+       struct timer_list           error_timer;
+       int                 error_timer_timeout_jiffies;
        bool                enable_rx_buffer_throttle;
        struct tegra_baud_tolerance             *baud_tolerance;
        int                                     n_adjustable_baud_rates;
@@ -281,6 +285,18 @@ static void tegra_uart_wait_sym_time(struct tegra_uart_port *tup,
                        tup->current_baud));
 }
 
+static void tegra_uart_disable_rx_irqs(struct tegra_uart_port *tup)
+{
+       unsigned long ier;
+
+       /* Disable Rx interrupts */
+       ier = tup->ier_shadow;
+       ier &= ~(UART_IER_RDI | UART_IER_RLSI | UART_IER_RTOIE |
+                       TEGRA_UART_IER_EORD);
+       tup->ier_shadow = ier;
+       tegra_uart_write(tup, ier, UART_IER);
+}
+
 static int tegra_uart_is_fifo_mode_enabled(struct tegra_uart_port *tup)
 {
        unsigned long iir;
@@ -392,39 +408,52 @@ static int tegra_set_baudrate(struct tegra_uart_port *tup, unsigned int baud)
        return 0;
 }
 
+static void tegra_uart_flush_fifo(struct tegra_uart_port *tup, u8 fcr_bits)
+{
+       unsigned long fcr = tup->fcr_shadow;
+       unsigned int lsr, tmout = 10000;
+
+       fcr |= fcr_bits & (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+       tegra_uart_write(tup, fcr, UART_FCR);
+
+       do {
+               lsr = tegra_uart_read(tup, UART_LSR);
+               if (!(lsr & UART_LSR_DR))
+                       break;
+               if (--tmout == 0)
+                       break;
+               udelay(1);
+       } while (1);
+}
+
 static char tegra_uart_decode_rx_error(struct tegra_uart_port *tup,
                        unsigned long lsr)
 {
        char flag = TTY_NORMAL;
 
        if (unlikely(lsr & TEGRA_UART_LSR_ANY)) {
-               if (lsr & UART_LSR_OE) {
-                       /* Overrrun error */
-                       flag |= TTY_OVERRUN;
-                       tup->uport.icount.overrun++;
-                       dev_err(tup->uport.dev, "Got overrun errors\n");
+               if (lsr & UART_LSR_BI) {
+                       tup->uport.icount.brk++;
+                       flag = TTY_BREAK;
+                       tegra_uart_flush_fifo(tup, UART_FCR_CLEAR_RCVR);
+
+                       if (tup->uport.ignore_status_mask & UART_LSR_BI)
+                               goto exit;
+                       dev_dbg(tup->uport.dev, "Got Break\n");
                } else if (lsr & UART_LSR_PE) {
                        /* Parity error */
                        flag |= TTY_PARITY;
                        tup->uport.icount.parity++;
-                       dev_err(tup->uport.dev, "Got Parity errors\n");
+                       dev_dbg(tup->uport.dev, "Got Parity errors\n");
                } else if (lsr & UART_LSR_FE) {
                        flag |= TTY_FRAME;
                        tup->uport.icount.frame++;
-                       dev_err(tup->uport.dev, "Got frame errors\n");
-               } else if (lsr & UART_LSR_BI) {
-                       tup->uport.icount.brk++;
-                       /* If FIFO read error without any data, reset Rx FIFO */
-                       if (!(lsr & UART_LSR_DR) && (lsr & UART_LSR_FIFOE))
-                               tegra_uart_fifo_reset(tup, UART_FCR_CLEAR_RCVR);
-                       else if (lsr & UART_LSR_FIFOE)
-                               dev_err(tup->uport.dev, "Got Receive Fifo errors\n");
-
-                       if (tup->uport.ignore_status_mask & UART_LSR_BI)
-                               goto exit;
-
-                       flag = TTY_BREAK;
-                       dev_err(tup->uport.dev, "Got Break\n");
+                       dev_dbg(tup->uport.dev, "Got frame errors\n");
+               } else if (lsr & UART_LSR_OE) {
+                       /* Overrrun error */
+                       flag |= TTY_OVERRUN;
+                       tup->uport.icount.overrun++;
+                       dev_dbg(tup->uport.dev, "Got overrun errors\n");
                }
                uart_insert_char(&tup->uport, lsr, UART_LSR_OE, 0, flag);
        }
@@ -597,10 +626,12 @@ static void tegra_uart_handle_tx_pio(struct tegra_uart_port *tup)
        return;
 }
 
-static void tegra_uart_handle_rx_pio(struct tegra_uart_port *tup,
+static int tegra_uart_handle_rx_pio(struct tegra_uart_port *tup,
                struct tty_port *tty)
 {
        int copied;
+       int max_rx_count = TEGRA_UART_MAX_RX_CHARS;
+       int error_count = 0;
 
        do {
                char flag = TTY_NORMAL;
@@ -612,8 +643,15 @@ static void tegra_uart_handle_rx_pio(struct tegra_uart_port *tup,
                        break;
 
                flag = tegra_uart_decode_rx_error(tup, lsr);
-               if (flag != TTY_NORMAL)
+               if (flag != TTY_NORMAL) {
+                       if (error_count++ > TEGRA_UART_MAX_REPEAT_ERRORS) {
+                               tegra_uart_disable_rx_irqs(tup);
+                               mod_timer(&tup->error_timer,
+                                       jiffies + tup->error_timer_timeout_jiffies);
+                               return -EIO;
+                       }
                        continue;
+               }
 
                ch = (unsigned char) tegra_uart_read(tup, UART_RX);
                tup->uport.icount.rx++;
@@ -623,12 +661,17 @@ static void tegra_uart_handle_rx_pio(struct tegra_uart_port *tup,
 
                if (!uart_handle_sysrq_char(&tup->uport, ch) && tty) {
                        copied = tty_insert_flip_char_lock(tty, ch, flag);
-                       if (copied != 1)
+                       if (copied != 1) {
                                dev_err(tup->uport.dev, "RxData PIO to tty layer failed\n");
+                               tegra_uart_disable_rx_irqs(tup);
+                               mod_timer(&tup->error_timer,
+                                       jiffies + tup->error_timer_timeout_jiffies);
+                               return -ENOSPC;
+                       }
                }
-       } while (1);
+       } while (max_rx_count--);
 
-       return;
+       return 0;
 }
 
 static void tegra_uart_rx_buffer_throttle_timer(unsigned long _data)
@@ -656,29 +699,51 @@ static void tegra_uart_rx_buffer_throttle_timer(unsigned long _data)
        spin_unlock_irqrestore(&u->lock, flags);
 }
 
-static void tegra_uart_copy_rx_to_tty(struct tegra_uart_port *tup,
+static void tegra_uart_rx_error_handle_timer(unsigned long _data)
+{
+       struct tegra_uart_port *tup = (struct tegra_uart_port *)_data;
+       struct uart_port *u = &tup->uport;
+       unsigned long flags;
+       unsigned long ier;
+
+       spin_lock_irqsave(&u->lock, flags);
+       ier = tup->ier_shadow;
+       ier |= (UART_IER_RLSI | UART_IER_RTOIE | TEGRA_UART_IER_EORD);
+       tup->ier_shadow = ier;
+       tegra_uart_write(tup, ier, UART_IER);
+       spin_unlock_irqrestore(&u->lock, flags);
+}
+
+static int tegra_uart_copy_rx_to_tty(struct tegra_uart_port *tup,
                struct tty_port *tty, int count)
 {
        int copied;
+       int ret = 0;
 
        tup->uport.icount.rx += count;
        if (!tty) {
                dev_err(tup->uport.dev, "No tty port\n");
-               return;
+               return -EINVAL;
        }
 
        if (tup->uport.ignore_status_mask & UART_LSR_DR)
-               return;
+               return 0;
 
        dma_sync_single_for_cpu(tup->uport.dev, tup->rx_dma_buf_phys,
                                TEGRA_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
 
        copied = tty_insert_flip_string_lock(tty,
                        ((unsigned char *)(tup->rx_dma_buf_virt)), count);
-       if (copied != count)
+       if (copied != count) {
                dev_err(tup->uport.dev, "RxData DMA copy to tty layer failed\n");
+               tegra_uart_disable_rx_irqs(tup);
+               mod_timer(&tup->error_timer,
+                               jiffies + tup->error_timer_timeout_jiffies);
+               ret = -ENOSPC;
+       }
        dma_sync_single_for_device(tup->uport.dev, tup->rx_dma_buf_phys,
                                TEGRA_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE);
+       return ret;
 }
 
 static void tegra_uart_rx_dma_complete(void *args)
@@ -693,6 +758,7 @@ static void tegra_uart_rx_dma_complete(void *args)
        struct dma_tx_state state;
        enum dma_status status;
        struct dma_async_tx_descriptor *prev_rx_dma_desc;
+       int ret;
 
        spin_lock_irqsave(&u->lock, flags);
 
@@ -709,11 +775,15 @@ static void tegra_uart_rx_dma_complete(void *args)
                set_rts(tup, false);
 
        /* If we are here, DMA is stopped */
-       if (count)
-               tegra_uart_copy_rx_to_tty(tup, port, count);
+       if (count) {
+               ret = tegra_uart_copy_rx_to_tty(tup, port, count);
+               if (ret)
+                       goto skip_pio;
+       }
 
        tegra_uart_handle_rx_pio(tup, port);
 
+skip_pio:
        if (tup->enable_rx_buffer_throttle) {
                rx_level = tty_buffer_get_level(port);
                if (rx_level > 70)
@@ -738,7 +808,7 @@ done:
        spin_unlock_irqrestore(&u->lock, flags);
 }
 
-static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup)
+static int tegra_uart_handle_rx_dma(struct tegra_uart_port *tup)
 {
        struct dma_tx_state state;
        struct tty_struct *tty = tty_port_tty_get(&tup->uport.state->port);
@@ -746,6 +816,7 @@ static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup)
        int count;
        int rx_level = 0;
        struct dma_async_tx_descriptor *prev_rx_dma_desc;
+       int ret;
 
        /* Deactivate flow control to stop sender */
        if (tup->rts_active)
@@ -757,11 +828,15 @@ static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup)
        count = tup->rx_bytes_requested - state.residue;
 
        /* If we are here, DMA is stopped */
-       if (count)
-               tegra_uart_copy_rx_to_tty(tup, port, count);
+       if (count) {
+               ret = tegra_uart_copy_rx_to_tty(tup, port, count);
+               if (ret)
+                       goto skip_pio;
+       }
 
-       tegra_uart_handle_rx_pio(tup, port);
+       ret = tegra_uart_handle_rx_pio(tup, port);
 
+skip_pio:
        if (tup->enable_rx_buffer_throttle) {
                rx_level = tty_buffer_get_level(port);
                if (rx_level > 70)
@@ -781,6 +856,8 @@ static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup)
                        set_rts(tup, true);
        } else if (tup->rts_active)
                set_rts(tup, true);
+
+       return ret;
 }
 
 static int tegra_uart_start_rx_dma(struct tegra_uart_port *tup)
@@ -862,13 +939,18 @@ static irqreturn_t tegra_uart_isr(int irq, void *data)
        unsigned long ier;
        bool is_rx_int = false;
        unsigned long flags;
+       int ret;
 
        spin_lock_irqsave(&u->lock, flags);
        while (1) {
                iir = tegra_uart_read(tup, UART_IIR);
                if (iir & UART_IIR_NO_INT) {
                        if (!tup->use_rx_pio && is_rx_int) {
-                               tegra_uart_handle_rx_dma(tup);
+                               ret = tegra_uart_handle_rx_dma(tup);
+                               if (ret) {
+                                       spin_unlock_irqrestore(&u->lock, flags);
+                                       return IRQ_HANDLED;
+                               }
                                if (tup->rx_in_progress) {
                                        ier = tup->ier_shadow;
                                        ier |= (UART_IER_RLSI | UART_IER_RTOIE |
@@ -962,6 +1044,8 @@ static void tegra_uart_stop_rx(struct uart_port *u)
                tty_flip_buffer_push(port);
                tty_kref_put(tty);
        }
+       del_timer_sync(&tup->error_timer);
+
        return;
 }
 
@@ -1651,6 +1735,11 @@ board_file:
                                (unsigned long)tup);
                tup->timer_timeout_jiffies = msecs_to_jiffies(10);
        }
+
+       setup_timer(&tup->error_timer, tegra_uart_rx_error_handle_timer,
+                       (unsigned long)tup);
+       tup->error_timer_timeout_jiffies = msecs_to_jiffies(500);
+
        return ret;
 }