diff options
| author | Yoshii Takashi <takashi.yoshii.zj@renesas.com> | 2012-03-14 03:14:43 -0400 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-04-02 12:27:22 -0400 |
| commit | c2de397f51b5a149dacfe2678ac232d422498774 (patch) | |
| tree | b178d80053d16fcd0fe5e95759a5a9c5541558c9 /drivers/tty | |
| parent | 8bb8ebe7b77228209006ea945104e37294608b93 (diff) | |
serial: sh-sci: fix a race of DMA submit_tx on transfer
commit 49d4bcaddca977fffdea8b0b71f6e5da96dac78e upstream.
When DMA is enabled, sh-sci transfer begins with
uart_start()
sci_start_tx()
if (cookie_tx < 0) schedule_work()
Then, starts DMA when wq scheduled, -- (A)
process_one_work()
work_fn_rx()
cookie_tx = desc->submit_tx()
And finishes when DMA transfer ends, -- (B)
sci_dma_tx_complete()
async_tx_ack()
cookie_tx = -EINVAL
(possible another schedule_work())
This A to B sequence is not reentrant, since controlling variables
(for example, cookie_tx above) are not queues nor lists. So, they
must be invoked as A B A B..., otherwise results in kernel crash.
To ensure the sequence, sci_start_tx() seems to test if cookie_tx < 0
(represents "not used") to call schedule_work().
But cookie_tx will not be set (to a cookie, also means "used") until
in the middle of work queue scheduled function work_fn_tx().
This gap between the test and set allows the breakage of the sequence
under the very frequently call of uart_start().
Another gap between async_tx_ack() and another schedule_work() results
in the same issue, too.
This patch introduces a new condition "cookie_tx == 0" just to mark
it is "busy" and assign it within spin-locked region to fill the gaps.
Signed-off-by: Takashi Yoshii <takashi.yoshii.zj@renesas.com>
Reviewed-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
| -rw-r--r-- | drivers/tty/serial/sh-sci.c | 15 |
1 files changed, 10 insertions, 5 deletions
diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index ebd8629c108..bead17e5634 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c | |||
| @@ -953,17 +953,20 @@ static void sci_dma_tx_complete(void *arg) | |||
| 953 | port->icount.tx += sg_dma_len(&s->sg_tx); | 953 | port->icount.tx += sg_dma_len(&s->sg_tx); |
| 954 | 954 | ||
| 955 | async_tx_ack(s->desc_tx); | 955 | async_tx_ack(s->desc_tx); |
| 956 | s->cookie_tx = -EINVAL; | ||
| 957 | s->desc_tx = NULL; | 956 | s->desc_tx = NULL; |
| 958 | 957 | ||
| 959 | if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) | 958 | if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) |
| 960 | uart_write_wakeup(port); | 959 | uart_write_wakeup(port); |
| 961 | 960 | ||
| 962 | if (!uart_circ_empty(xmit)) { | 961 | if (!uart_circ_empty(xmit)) { |
| 962 | s->cookie_tx = 0; | ||
| 963 | schedule_work(&s->work_tx); | 963 | schedule_work(&s->work_tx); |
| 964 | } else if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { | 964 | } else { |
| 965 | u16 ctrl = sci_in(port, SCSCR); | 965 | s->cookie_tx = -EINVAL; |
| 966 | sci_out(port, SCSCR, ctrl & ~SCSCR_TIE); | 966 | if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { |
| 967 | u16 ctrl = sci_in(port, SCSCR); | ||
| 968 | sci_out(port, SCSCR, ctrl & ~SCSCR_TIE); | ||
| 969 | } | ||
| 967 | } | 970 | } |
| 968 | 971 | ||
| 969 | spin_unlock_irqrestore(&port->lock, flags); | 972 | spin_unlock_irqrestore(&port->lock, flags); |
| @@ -1225,8 +1228,10 @@ static void sci_start_tx(struct uart_port *port) | |||
| 1225 | } | 1228 | } |
| 1226 | 1229 | ||
| 1227 | if (s->chan_tx && !uart_circ_empty(&s->port.state->xmit) && | 1230 | if (s->chan_tx && !uart_circ_empty(&s->port.state->xmit) && |
| 1228 | s->cookie_tx < 0) | 1231 | s->cookie_tx < 0) { |
| 1232 | s->cookie_tx = 0; | ||
| 1229 | schedule_work(&s->work_tx); | 1233 | schedule_work(&s->work_tx); |
| 1234 | } | ||
| 1230 | #endif | 1235 | #endif |
| 1231 | 1236 | ||
| 1232 | if (!s->chan_tx || port->type == PORT_SCIFA || port->type == PORT_SCIFB) { | 1237 | if (!s->chan_tx || port->type == PORT_SCIFA || port->type == PORT_SCIFB) { |
