Commit d033e82d authored by Leilei Zhao's avatar Leilei Zhao Committed by Greg Kroah-Hartman

tty/serial: at91: handle IRQ status more safely

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: default avatarLeilei Zhao <leilei.zhao@atmel.com>
Acked-by: default avatarNicolas Ferre <nicolas.ferre@atmel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 74a76089
...@@ -165,6 +165,7 @@ struct atmel_uart_port { ...@@ -165,6 +165,7 @@ struct atmel_uart_port {
struct tasklet_struct tasklet; struct tasklet_struct tasklet;
unsigned int irq_status; unsigned int irq_status;
unsigned int irq_status_prev; unsigned int irq_status_prev;
unsigned int status_change;
struct circ_buf rx_ring; struct circ_buf rx_ring;
...@@ -1177,6 +1178,9 @@ atmel_handle_status(struct uart_port *port, unsigned int pending, ...@@ -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 if (pending & (ATMEL_US_RIIC | ATMEL_US_DSRIC | ATMEL_US_DCDIC
| ATMEL_US_CTSIC)) { | ATMEL_US_CTSIC)) {
atmel_port->irq_status = status; 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); tasklet_schedule(&atmel_port->tasklet);
} }
} }
...@@ -1523,17 +1527,14 @@ static void atmel_tasklet_func(unsigned long data) ...@@ -1523,17 +1527,14 @@ static void atmel_tasklet_func(unsigned long data)
{ {
struct uart_port *port = (struct uart_port *)data; struct uart_port *port = (struct uart_port *)data;
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
unsigned int status; unsigned int status = atmel_port->irq_status;
unsigned int status_change; unsigned int status_change = atmel_port->status_change;
/* The interrupt handler does not take the lock */ /* The interrupt handler does not take the lock */
spin_lock(&port->lock); spin_lock(&port->lock);
atmel_port->schedule_tx(port); 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 if (status_change & (ATMEL_US_RI | ATMEL_US_DSR
| ATMEL_US_DCD | ATMEL_US_CTS)) { | ATMEL_US_DCD | ATMEL_US_CTS)) {
/* TODO: All reads to CSR will clear these interrupts! */ /* TODO: All reads to CSR will clear these interrupts! */
...@@ -1548,7 +1549,7 @@ static void atmel_tasklet_func(unsigned long data) ...@@ -1548,7 +1549,7 @@ static void atmel_tasklet_func(unsigned long data)
wake_up_interruptible(&port->state->port.delta_msr_wait); 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); atmel_port->schedule_rx(port);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment