aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Baldyga <r.baldyga@samsung.com>2015-07-31 04:58:28 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-08-05 01:07:24 -0400
commit736cd79f483fd7a1e0b71e6eaddf01d8d87fbbbb (patch)
tree035da7625ec0f5022a53d9346ee968cf9ff33541
parent81ccb2a69f76b88295a1da9fc9484df715fe3bfa (diff)
serial: samsung: fix DMA for FIFO smaller than cache line size
So far DMA mode were activated when only number of bytes to send was equal or greater than min_dma_size. Due to requirement that DMA transaction buffer should be aligned to cache line size, the excessive bytes were written to FIFO before starting DMA transaction. The problem occurred when FIFO size were smaller than cache alignment, because writing all excessive bytes to FIFO would fail. It happened in DMA mode when PIO interrupts disabled, which caused driver hung. The solution is to test if buffer is alligned to cache line size before activating DMA mode, and if it's not, running PIO mode to align buffer and then starting DMA transaction. In PIO mode, when interrupts are enabled, lack of space in FIFO isn't the problem, so buffer aligning will always finish with success. Cc: <stable@vger.kernel.org> # v3.18+ Reported-by: Krzysztof Kozlowski <k.kozlowski@samsung.com> Signed-off-by: Robert Baldyga <r.baldyga@samsung.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/tty/serial/samsung.c36
1 files changed, 21 insertions, 15 deletions
diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c
index faee021c27b0..856686d6dcdb 100644
--- a/drivers/tty/serial/samsung.c
+++ b/drivers/tty/serial/samsung.c
@@ -294,15 +294,6 @@ static int s3c24xx_serial_start_tx_dma(struct s3c24xx_uart_port *ourport,
294 if (ourport->tx_mode != S3C24XX_TX_DMA) 294 if (ourport->tx_mode != S3C24XX_TX_DMA)
295 enable_tx_dma(ourport); 295 enable_tx_dma(ourport);
296 296
297 while (xmit->tail & (dma_get_cache_alignment() - 1)) {
298 if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
299 return 0;
300 wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
301 xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
302 port->icount.tx++;
303 count--;
304 }
305
306 dma->tx_size = count & ~(dma_get_cache_alignment() - 1); 297 dma->tx_size = count & ~(dma_get_cache_alignment() - 1);
307 dma->tx_transfer_addr = dma->tx_addr + xmit->tail; 298 dma->tx_transfer_addr = dma->tx_addr + xmit->tail;
308 299
@@ -342,7 +333,8 @@ static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport)
342 } 333 }
343 334
344 if (!ourport->dma || !ourport->dma->tx_chan || 335 if (!ourport->dma || !ourport->dma->tx_chan ||
345 count < ourport->min_dma_size) 336 count < ourport->min_dma_size ||
337 xmit->tail & (dma_get_cache_alignment() - 1))
346 s3c24xx_serial_start_tx_pio(ourport); 338 s3c24xx_serial_start_tx_pio(ourport);
347 else 339 else
348 s3c24xx_serial_start_tx_dma(ourport, count); 340 s3c24xx_serial_start_tx_dma(ourport, count);
@@ -736,7 +728,7 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
736 struct uart_port *port = &ourport->port; 728 struct uart_port *port = &ourport->port;
737 struct circ_buf *xmit = &port->state->xmit; 729 struct circ_buf *xmit = &port->state->xmit;
738 unsigned long flags; 730 unsigned long flags;
739 int count; 731 int count, dma_count = 0;
740 732
741 spin_lock_irqsave(&port->lock, flags); 733 spin_lock_irqsave(&port->lock, flags);
742 734
@@ -744,8 +736,12 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
744 736
745 if (ourport->dma && ourport->dma->tx_chan && 737 if (ourport->dma && ourport->dma->tx_chan &&
746 count >= ourport->min_dma_size) { 738 count >= ourport->min_dma_size) {
747 s3c24xx_serial_start_tx_dma(ourport, count); 739 int align = dma_get_cache_alignment() -
748 goto out; 740 (xmit->tail & (dma_get_cache_alignment() - 1));
741 if (count-align >= ourport->min_dma_size) {
742 dma_count = count-align;
743 count = align;
744 }
749 } 745 }
750 746
751 if (port->x_char) { 747 if (port->x_char) {
@@ -766,14 +762,24 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
766 762
767 /* try and drain the buffer... */ 763 /* try and drain the buffer... */
768 764
769 count = port->fifosize; 765 if (count > port->fifosize) {
770 while (!uart_circ_empty(xmit) && count-- > 0) { 766 count = port->fifosize;
767 dma_count = 0;
768 }
769
770 while (!uart_circ_empty(xmit) && count > 0) {
771 if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull) 771 if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
772 break; 772 break;
773 773
774 wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]); 774 wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
775 xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); 775 xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
776 port->icount.tx++; 776 port->icount.tx++;
777 count--;
778 }
779
780 if (!count && dma_count) {
781 s3c24xx_serial_start_tx_dma(ourport, dma_count);
782 goto out;
777 } 783 }
778 784
779 if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) { 785 if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) {