From 9205218e491d70bf45f4527cd3bcb5cd3734ae17 Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Fri, 17 Jun 2016 12:05:46 +0200 Subject: tty/serial: atmel: re-integrate status check in irq handler The IRQ status check and related actions was done in the tasklet without benefit. So, move it back to the IRQ context to simplify IRQ handling and having the possibility to split the tasklet in two separated ones for receive and transmit actions. Signed-off-by: Nicolas Ferre Signed-off-by: Ludovic Desroches Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 43 ++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 26 deletions(-) (limited to 'drivers/tty/serial/atmel_serial.c') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 954941dd8124..8854ac6f383d 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -146,9 +146,7 @@ struct atmel_uart_port { struct scatterlist sg_tx; struct scatterlist sg_rx; struct tasklet_struct tasklet; - unsigned int irq_status; unsigned int irq_status_prev; - unsigned int status_change; unsigned int tx_len; struct circ_buf rx_ring; @@ -1237,14 +1235,27 @@ atmel_handle_status(struct uart_port *port, unsigned int pending, unsigned int status) { struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); + unsigned int status_change; if (pending & (ATMEL_US_RIIC | ATMEL_US_DSRIC | ATMEL_US_DCDIC | ATMEL_US_CTSIC)) { - atmel_port->irq_status = status; - atmel_port->status_change = atmel_port->irq_status ^ - atmel_port->irq_status_prev; + status_change = status ^ atmel_port->irq_status_prev; atmel_port->irq_status_prev = status; - tasklet_schedule(&atmel_port->tasklet); + + if (status_change & (ATMEL_US_RI | ATMEL_US_DSR + | ATMEL_US_DCD | ATMEL_US_CTS)) { + /* TODO: All reads to CSR will clear these interrupts! */ + if (status_change & ATMEL_US_RI) + port->icount.rng++; + if (status_change & ATMEL_US_DSR) + port->icount.dsr++; + if (status_change & ATMEL_US_DCD) + uart_handle_dcd_change(port, !(status & ATMEL_US_DCD)); + if (status_change & ATMEL_US_CTS) + uart_handle_cts_change(port, !(status & ATMEL_US_CTS)); + + wake_up_interruptible(&port->state->port.delta_msr_wait); + } } } @@ -1575,31 +1586,12 @@ static void atmel_tasklet_func(unsigned long data) { struct uart_port *port = (struct uart_port *)data; struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); - unsigned int status = atmel_port->irq_status; - unsigned int status_change = atmel_port->status_change; /* The interrupt handler does not take the lock */ spin_lock(&port->lock); atmel_port->schedule_tx(port); - if (status_change & (ATMEL_US_RI | ATMEL_US_DSR - | ATMEL_US_DCD | ATMEL_US_CTS)) { - /* TODO: All reads to CSR will clear these interrupts! */ - if (status_change & ATMEL_US_RI) - port->icount.rng++; - if (status_change & ATMEL_US_DSR) - port->icount.dsr++; - if (status_change & ATMEL_US_DCD) - uart_handle_dcd_change(port, !(status & ATMEL_US_DCD)); - if (status_change & ATMEL_US_CTS) - uart_handle_cts_change(port, !(status & ATMEL_US_CTS)); - - wake_up_interruptible(&port->state->port.delta_msr_wait); - - atmel_port->status_change = 0; - } - atmel_port->schedule_rx(port); spin_unlock(&port->lock); @@ -1833,7 +1825,6 @@ static int atmel_startup(struct uart_port *port) /* Save current CSR for comparison in atmel_tasklet_func() */ atmel_port->irq_status_prev = atmel_get_lines_status(port); - atmel_port->irq_status = atmel_port->irq_status_prev; /* * Finally, enable the serial port -- cgit v1.2.3 From 00e8e65870cc581a0a8496e95480ea1b39aec58b Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Fri, 17 Jun 2016 12:05:47 +0200 Subject: tty/serial: atmel: split tx and rx paths Split TX and RX paths to not schedule RX tasklet on TX events and vice versa. Signed-off-by: Nicolas Ferre Signed-off-by: Ludovic Desroches Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 53 +++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 19 deletions(-) (limited to 'drivers/tty/serial/atmel_serial.c') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 8854ac6f383d..6c1ec7d0d497 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -145,7 +145,8 @@ struct atmel_uart_port { dma_cookie_t cookie_rx; struct scatterlist sg_tx; struct scatterlist sg_rx; - struct tasklet_struct tasklet; + struct tasklet_struct tasklet_rx; + struct tasklet_struct tasklet_tx; unsigned int irq_status_prev; unsigned int tx_len; @@ -708,7 +709,7 @@ static void atmel_rx_chars(struct uart_port *port) status = atmel_uart_readl(port, ATMEL_US_CSR); } - tasklet_schedule(&atmel_port->tasklet); + tasklet_schedule(&atmel_port->tasklet_rx); } /* @@ -779,7 +780,7 @@ static void atmel_complete_tx_dma(void *arg) * remaining data from the beginning of xmit->buf to xmit->head. */ if (!uart_circ_empty(xmit)) - tasklet_schedule(&atmel_port->tasklet); + tasklet_schedule(&atmel_port->tasklet_tx); spin_unlock_irqrestore(&port->lock, flags); } @@ -964,7 +965,7 @@ static void atmel_complete_rx_dma(void *arg) struct uart_port *port = arg; struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); - tasklet_schedule(&atmel_port->tasklet); + tasklet_schedule(&atmel_port->tasklet_rx); } static void atmel_release_rx_dma(struct uart_port *port) @@ -1004,7 +1005,7 @@ static void atmel_rx_from_dma(struct uart_port *port) if (dmastat == DMA_ERROR) { dev_dbg(port->dev, "Get residue error, restart tasklet\n"); atmel_uart_writel(port, ATMEL_US_IER, ATMEL_US_TIMEOUT); - tasklet_schedule(&atmel_port->tasklet); + tasklet_schedule(&atmel_port->tasklet_rx); return; } @@ -1158,7 +1159,7 @@ static void atmel_uart_timer_callback(unsigned long data) struct uart_port *port = (void *)data; struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); - tasklet_schedule(&atmel_port->tasklet); + tasklet_schedule(&atmel_port->tasklet_rx); mod_timer(&atmel_port->uart_timer, jiffies + uart_poll_timeout(port)); } @@ -1181,7 +1182,7 @@ atmel_handle_receive(struct uart_port *port, unsigned int pending) if (pending & (ATMEL_US_ENDRX | ATMEL_US_TIMEOUT)) { atmel_uart_writel(port, ATMEL_US_IDR, (ATMEL_US_ENDRX | ATMEL_US_TIMEOUT)); - tasklet_schedule(&atmel_port->tasklet); + tasklet_schedule(&atmel_port->tasklet_rx); } if (pending & (ATMEL_US_RXBRK | ATMEL_US_OVRE | @@ -1193,7 +1194,7 @@ atmel_handle_receive(struct uart_port *port, unsigned int pending) if (pending & ATMEL_US_TIMEOUT) { atmel_uart_writel(port, ATMEL_US_IDR, ATMEL_US_TIMEOUT); - tasklet_schedule(&atmel_port->tasklet); + tasklet_schedule(&atmel_port->tasklet_rx); } } @@ -1223,7 +1224,7 @@ atmel_handle_transmit(struct uart_port *port, unsigned int pending) /* Either PDC or interrupt transmission */ atmel_uart_writel(port, ATMEL_US_IDR, atmel_port->tx_done_mask); - tasklet_schedule(&atmel_port->tasklet); + tasklet_schedule(&atmel_port->tasklet_tx); } } @@ -1582,18 +1583,25 @@ static int atmel_prepare_rx_pdc(struct uart_port *port) /* * tasklet handling tty stuff outside the interrupt handler. */ -static void atmel_tasklet_func(unsigned long data) +static void atmel_tasklet_rx_func(unsigned long data) { struct uart_port *port = (struct uart_port *)data; struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); /* The interrupt handler does not take the lock */ spin_lock(&port->lock); - - atmel_port->schedule_tx(port); - atmel_port->schedule_rx(port); + spin_unlock(&port->lock); +} +static void atmel_tasklet_tx_func(unsigned long data) +{ + struct uart_port *port = (struct uart_port *)data; + struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); + + /* The interrupt handler does not take the lock */ + spin_lock(&port->lock); + atmel_port->schedule_tx(port); spin_unlock(&port->lock); } @@ -1777,7 +1785,8 @@ static int atmel_startup(struct uart_port *port) return retval; } - tasklet_enable(&atmel_port->tasklet); + tasklet_enable(&atmel_port->tasklet_rx); + tasklet_enable(&atmel_port->tasklet_tx); /* * Initialize DMA (if necessary) @@ -1906,8 +1915,10 @@ static void atmel_shutdown(struct uart_port *port) * Clear out any scheduled tasklets before * we destroy the buffers */ - tasklet_disable(&atmel_port->tasklet); - tasklet_kill(&atmel_port->tasklet); + tasklet_disable(&atmel_port->tasklet_rx); + tasklet_disable(&atmel_port->tasklet_tx); + tasklet_kill(&atmel_port->tasklet_rx); + tasklet_kill(&atmel_port->tasklet_tx); /* * Ensure everything is stopped and @@ -2302,9 +2313,12 @@ static int atmel_init_port(struct atmel_uart_port *atmel_port, port->irq = pdev->resource[1].start; port->rs485_config = atmel_config_rs485; - tasklet_init(&atmel_port->tasklet, atmel_tasklet_func, + tasklet_init(&atmel_port->tasklet_rx, atmel_tasklet_rx_func, + (unsigned long)port); + tasklet_init(&atmel_port->tasklet_tx, atmel_tasklet_tx_func, (unsigned long)port); - tasklet_disable(&atmel_port->tasklet); + tasklet_disable(&atmel_port->tasklet_rx); + tasklet_disable(&atmel_port->tasklet_tx); memset(&atmel_port->rx_ring, 0, sizeof(atmel_port->rx_ring)); @@ -2786,7 +2800,8 @@ static int atmel_serial_remove(struct platform_device *pdev) struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); int ret = 0; - tasklet_kill(&atmel_port->tasklet); + tasklet_kill(&atmel_port->tasklet_rx); + tasklet_kill(&atmel_port->tasklet_tx); device_init_wakeup(&pdev->dev, 0); -- cgit v1.2.3 From 637ba54f6051903e1f0011b75ee36b2a29696c6d Mon Sep 17 00:00:00 2001 From: Ludovic Desroches Date: Fri, 17 Jun 2016 12:05:48 +0200 Subject: tty/serial: atmel: add comment for the ring buffer size macro There is a macro named ATMEL_SERIAL_RINGSIZE which suggesting that it corresponds to the real size of the ring buffer. Let warn people that there is a factor of four since allocation size is sizeof(struct atmel_uart_char) * ATMEL_SERIAL_RINGSIZE. Signed-off-by: Ludovic Desroches Signed-off-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/tty/serial/atmel_serial.c') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 6c1ec7d0d497..857016367a7a 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -108,6 +108,12 @@ struct atmel_uart_char { u16 ch; }; +/* + * Be careful, the real size of the ring buffer is + * sizeof(atmel_uart_char) * ATMEL_SERIAL_RINGSIZE. It means that ring buffer + * can contain up to 1024 characters in PIO mode and up to 4096 characters in + * DMA mode. + */ #define ATMEL_SERIAL_RINGSIZE 1024 /* -- cgit v1.2.3 From 0058f0871efe7b01c6f2b3046c68196ab73e96da Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Sat, 28 May 2016 00:54:08 +0200 Subject: tty/serial: atmel: fix RS485 half duplex with DMA When using DMA, half duplex doesn't work properly because rx is not stopped before starting tx. Ensure we call atmel_stop_rx() in the DMA case. Signed-off-by: Alexandre Belloni Acked-by: Nicolas Ferre Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'drivers/tty/serial/atmel_serial.c') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 857016367a7a..f887c1df23e6 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -487,19 +487,21 @@ static void atmel_start_tx(struct uart_port *port) { struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); - if (atmel_use_pdc_tx(port)) { - if (atmel_uart_readl(port, ATMEL_PDC_PTSR) & ATMEL_PDC_TXTEN) - /* The transmitter is already running. Yes, we - really need this.*/ - return; + if (atmel_use_pdc_tx(port) && (atmel_uart_readl(port, ATMEL_PDC_PTSR) + & ATMEL_PDC_TXTEN)) + /* The transmitter is already running. Yes, we + really need this.*/ + return; + if (atmel_use_pdc_tx(port) || atmel_use_dma_tx(port)) if ((port->rs485.flags & SER_RS485_ENABLED) && !(port->rs485.flags & SER_RS485_RX_DURING_TX)) atmel_stop_rx(port); + if (atmel_use_pdc_tx(port)) /* re-enable PDC transmit */ atmel_uart_writel(port, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN); - } + /* Enable interrupts */ atmel_uart_writel(port, ATMEL_US_IER, atmel_port->tx_done_mask); } -- cgit v1.2.3 From 98f2082c3ac4042189723c120553310700b583bb Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Sun, 26 Jun 2016 09:44:49 +0200 Subject: tty/serial: atmel: enforce tasklet init and termination sequences As some race conditions are identified in the termination process of tasklets, enforce the atmel_shutdown() sequence. This way we make sure that no new tasklets or software timer are scheduled during shutdown process. An atomic flag is positioned to give this information throughout the code. We also remove tasklet_disable() calls that were leading to deadlocks while stopping the driver. A simpler init/kill sequence is used. Signed-off-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 61 ++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 23 deletions(-) (limited to 'drivers/tty/serial/atmel_serial.c') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index f887c1df23e6..2eaa18ddef61 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -153,6 +153,7 @@ struct atmel_uart_port { struct scatterlist sg_rx; struct tasklet_struct tasklet_rx; struct tasklet_struct tasklet_tx; + atomic_t tasklet_shutdown; unsigned int irq_status_prev; unsigned int tx_len; @@ -286,6 +287,13 @@ static bool atmel_use_fifo(struct uart_port *port) return atmel_port->fifo_size; } +static void atmel_tasklet_schedule(struct atmel_uart_port *atmel_port, + struct tasklet_struct *t) +{ + if (!atomic_read(&atmel_port->tasklet_shutdown)) + tasklet_schedule(t); +} + static unsigned int atmel_get_lines_status(struct uart_port *port) { struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); @@ -717,7 +725,7 @@ static void atmel_rx_chars(struct uart_port *port) status = atmel_uart_readl(port, ATMEL_US_CSR); } - tasklet_schedule(&atmel_port->tasklet_rx); + atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_rx); } /* @@ -788,7 +796,7 @@ static void atmel_complete_tx_dma(void *arg) * remaining data from the beginning of xmit->buf to xmit->head. */ if (!uart_circ_empty(xmit)) - tasklet_schedule(&atmel_port->tasklet_tx); + atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_tx); spin_unlock_irqrestore(&port->lock, flags); } @@ -973,7 +981,7 @@ static void atmel_complete_rx_dma(void *arg) struct uart_port *port = arg; struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); - tasklet_schedule(&atmel_port->tasklet_rx); + atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_rx); } static void atmel_release_rx_dma(struct uart_port *port) @@ -1013,7 +1021,7 @@ static void atmel_rx_from_dma(struct uart_port *port) if (dmastat == DMA_ERROR) { dev_dbg(port->dev, "Get residue error, restart tasklet\n"); atmel_uart_writel(port, ATMEL_US_IER, ATMEL_US_TIMEOUT); - tasklet_schedule(&atmel_port->tasklet_rx); + atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_rx); return; } @@ -1167,8 +1175,11 @@ static void atmel_uart_timer_callback(unsigned long data) struct uart_port *port = (void *)data; struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); - tasklet_schedule(&atmel_port->tasklet_rx); - mod_timer(&atmel_port->uart_timer, jiffies + uart_poll_timeout(port)); + if (!atomic_read(&atmel_port->tasklet_shutdown)) { + tasklet_schedule(&atmel_port->tasklet_rx); + mod_timer(&atmel_port->uart_timer, + jiffies + uart_poll_timeout(port)); + } } /* @@ -1190,7 +1201,8 @@ atmel_handle_receive(struct uart_port *port, unsigned int pending) if (pending & (ATMEL_US_ENDRX | ATMEL_US_TIMEOUT)) { atmel_uart_writel(port, ATMEL_US_IDR, (ATMEL_US_ENDRX | ATMEL_US_TIMEOUT)); - tasklet_schedule(&atmel_port->tasklet_rx); + atmel_tasklet_schedule(atmel_port, + &atmel_port->tasklet_rx); } if (pending & (ATMEL_US_RXBRK | ATMEL_US_OVRE | @@ -1202,7 +1214,8 @@ atmel_handle_receive(struct uart_port *port, unsigned int pending) if (pending & ATMEL_US_TIMEOUT) { atmel_uart_writel(port, ATMEL_US_IDR, ATMEL_US_TIMEOUT); - tasklet_schedule(&atmel_port->tasklet_rx); + atmel_tasklet_schedule(atmel_port, + &atmel_port->tasklet_rx); } } @@ -1232,7 +1245,7 @@ atmel_handle_transmit(struct uart_port *port, unsigned int pending) /* Either PDC or interrupt transmission */ atmel_uart_writel(port, ATMEL_US_IDR, atmel_port->tx_done_mask); - tasklet_schedule(&atmel_port->tasklet_tx); + atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_tx); } } @@ -1793,8 +1806,11 @@ static int atmel_startup(struct uart_port *port) return retval; } - tasklet_enable(&atmel_port->tasklet_rx); - tasklet_enable(&atmel_port->tasklet_tx); + atomic_set(&atmel_port->tasklet_shutdown, 0); + tasklet_init(&atmel_port->tasklet_rx, atmel_tasklet_rx_func, + (unsigned long)port); + tasklet_init(&atmel_port->tasklet_tx, atmel_tasklet_tx_func, + (unsigned long)port); /* * Initialize DMA (if necessary) @@ -1913,31 +1929,36 @@ static void atmel_shutdown(struct uart_port *port) { struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); + /* Disable interrupts at device level */ + atmel_uart_writel(port, ATMEL_US_IDR, -1); + + /* Prevent spurious interrupts from scheduling the tasklet */ + atomic_inc(&atmel_port->tasklet_shutdown); + /* * Prevent any tasklets being scheduled during * cleanup */ del_timer_sync(&atmel_port->uart_timer); + /* Make sure that no interrupt is on the fly */ + synchronize_irq(port->irq); + /* * Clear out any scheduled tasklets before * we destroy the buffers */ - tasklet_disable(&atmel_port->tasklet_rx); - tasklet_disable(&atmel_port->tasklet_tx); tasklet_kill(&atmel_port->tasklet_rx); tasklet_kill(&atmel_port->tasklet_tx); /* * Ensure everything is stopped and - * disable all interrupts, port and break condition. + * disable port and break condition. */ atmel_stop_rx(port); atmel_stop_tx(port); atmel_uart_writel(port, ATMEL_US_CR, ATMEL_US_RSTSTA); - atmel_uart_writel(port, ATMEL_US_IDR, -1); - /* * Shut-down the DMA. @@ -2321,13 +2342,6 @@ static int atmel_init_port(struct atmel_uart_port *atmel_port, port->irq = pdev->resource[1].start; port->rs485_config = atmel_config_rs485; - tasklet_init(&atmel_port->tasklet_rx, atmel_tasklet_rx_func, - (unsigned long)port); - tasklet_init(&atmel_port->tasklet_tx, atmel_tasklet_tx_func, - (unsigned long)port); - tasklet_disable(&atmel_port->tasklet_rx); - tasklet_disable(&atmel_port->tasklet_tx); - memset(&atmel_port->rx_ring, 0, sizeof(atmel_port->rx_ring)); if (pdata && pdata->regs) { @@ -2712,6 +2726,7 @@ static int atmel_serial_probe(struct platform_device *pdev) atmel_port->uart.line = ret; atmel_serial_probe_fifos(atmel_port, pdev); + atomic_set(&atmel_port->tasklet_shutdown, 0); spin_lock_init(&atmel_port->lock_suspended); ret = atmel_init_port(atmel_port, pdev); -- cgit v1.2.3