tty/serial: at91: handle IRQ status more safely
authorLeilei Zhao <leilei.zhao@atmel.com>
Thu, 9 Apr 2015 02:48:15 +0000 (10:48 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 6 May 2015 20:26:58 +0000 (22:26 +0200)
Handle the changed flag of IRQ status in interruption
instead of handling it in tasklet due to the tasklet
may be scheduled more than once in one interruption.
Otherwise, the changed status may be processed more
than once which will lead to unexpected result. And
seriously, kernel will crash.

Signed-off-by: Leilei Zhao <leilei.zhao@atmel.com>
Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/atmel_serial.c

index 27dade29646b7c8d962494cf37daee398c774514..b1fcddd6652acc41da483ff9abcf2d14bec6e3e1 100644 (file)
@@ -165,6 +165,7 @@ struct atmel_uart_port {
        struct tasklet_struct   tasklet;
        unsigned int            irq_status;
        unsigned int            irq_status_prev;
+       unsigned int            status_change;
 
        struct circ_buf         rx_ring;
 
@@ -1177,6 +1178,9 @@ atmel_handle_status(struct uart_port *port, unsigned int pending,
        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;
+               atmel_port->irq_status_prev = status;
                tasklet_schedule(&atmel_port->tasklet);
        }
 }
@@ -1523,17 +1527,14 @@ 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;
-       unsigned int status_change;
+       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);
 
-       status = atmel_port->irq_status;
-       status_change = status ^ atmel_port->irq_status_prev;
-
        if (status_change & (ATMEL_US_RI | ATMEL_US_DSR
                                | ATMEL_US_DCD | ATMEL_US_CTS)) {
                /* TODO: All reads to CSR will clear these interrupts! */
@@ -1548,7 +1549,7 @@ static void atmel_tasklet_func(unsigned long data)
 
                wake_up_interruptible(&port->state->port.delta_msr_wait);
 
-               atmel_port->irq_status_prev = status;
+               atmel_port->status_change = 0;
        }
 
        atmel_port->schedule_rx(port);