From e34d583b476c3ce99f8eb720f95ad3562da3318a Mon Sep 17 00:00:00 2001 From: Shardar Shariff Md Date: Tue, 2 Aug 2016 19:05:26 +0530 Subject: [PATCH] serial: tegra: correct error handling sequence to avoid system hang - 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 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 Tested-by: Shreshtha Sahu Reviewed-by: Winnie Hsu --- drivers/tty/serial/serial-tegra.c | 161 +++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 36 deletions(-) diff --git a/drivers/tty/serial/serial-tegra.c b/drivers/tty/serial/serial-tegra.c index c1ed38631ea..ba43507c241 100644 --- a/drivers/tty/serial/serial-tegra.c +++ b/drivers/tty/serial/serial-tegra.c @@ -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; } -- 2.39.2