diff mbox

[v2,2/3] serial: sh-sci: Stop TX DMA workqueue during port shutdown

Message ID 20180706090543.31625-3-geert+renesas@glider.be (mailing list archive)
State New, archived
Headers show

Commit Message

Geert Uytterhoeven July 6, 2018, 9:05 a.m. UTC
The transmit DMA workqueue is never stopped, hence the work function may
be called after the port has been shut down.

Fix this race condition by cancelling queued work, if any, before DMA
release.  Don't initialize the work if DMA initialization failed, as it
won't be used anyway.

Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
---
v2:
  - No changes.
---
 drivers/tty/serial/sh-sci.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Comments

Simon Horman July 11, 2018, 2:51 p.m. UTC | #1
On Fri, Jul 06, 2018 at 11:05:42AM +0200, Geert Uytterhoeven wrote:
> The transmit DMA workqueue is never stopped, hence the work function may
> be called after the port has been shut down.
> 
> Fix this race condition by cancelling queued work, if any, before DMA
> release.  Don't initialize the work if DMA initialization failed, as it
> won't be used anyway.
> 
> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> ---
> v2:
>   - No changes.
> ---
>  drivers/tty/serial/sh-sci.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c
> index 674dc65454ae0684..939749073e7bdb11 100644
> --- a/drivers/tty/serial/sh-sci.c
> +++ b/drivers/tty/serial/sh-sci.c
> @@ -1293,6 +1293,7 @@ static void sci_tx_dma_release(struct sci_port *s)
>  {
>  	struct dma_chan *chan = s->chan_tx_saved;
>  
> +	cancel_work_sync(&s->work_tx);
>  	s->chan_tx_saved = s->chan_tx = NULL;
>  	s->cookie_tx = -EINVAL;
>  	dmaengine_terminate_all(chan);
> @@ -1548,10 +1549,9 @@ static void sci_request_dma(struct uart_port *port)
>  				__func__, UART_XMIT_SIZE,
>  				port->state->xmit.buf, &s->tx_dma_addr);
>  
> +			INIT_WORK(&s->work_tx, work_fn_tx);
>  			s->chan_tx_saved = s->chan_tx = chan;

Is it ok that work_fn_tx reads and writes s->work_tx which
is set after the call to INIT_WORK() ?

>  		}
> -
> -		INIT_WORK(&s->work_tx, work_fn_tx);
>  	}
>  
>  	chan = sci_request_dma_chan(port, DMA_DEV_TO_MEM);
> -- 
> 2.17.1
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Geert Uytterhoeven July 11, 2018, 3:15 p.m. UTC | #2
Hi Simon,

On Wed, Jul 11, 2018 at 4:52 PM Simon Horman <horms@verge.net.au> wrote:
> On Fri, Jul 06, 2018 at 11:05:42AM +0200, Geert Uytterhoeven wrote:
> > The transmit DMA workqueue is never stopped, hence the work function may
> > be called after the port has been shut down.
> >
> > Fix this race condition by cancelling queued work, if any, before DMA
> > release.  Don't initialize the work if DMA initialization failed, as it
> > won't be used anyway.
> >
> > Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>

> > --- a/drivers/tty/serial/sh-sci.c
> > +++ b/drivers/tty/serial/sh-sci.c
> > @@ -1293,6 +1293,7 @@ static void sci_tx_dma_release(struct sci_port *s)
> >  {
> >       struct dma_chan *chan = s->chan_tx_saved;
> >
> > +     cancel_work_sync(&s->work_tx);
> >       s->chan_tx_saved = s->chan_tx = NULL;
> >       s->cookie_tx = -EINVAL;
> >       dmaengine_terminate_all(chan);
> > @@ -1548,10 +1549,9 @@ static void sci_request_dma(struct uart_port *port)
> >                               __func__, UART_XMIT_SIZE,
> >                               port->state->xmit.buf, &s->tx_dma_addr);
> >
> > +                     INIT_WORK(&s->work_tx, work_fn_tx);
> >                       s->chan_tx_saved = s->chan_tx = chan;
>
> Is it ok that work_fn_tx reads and writes s->work_tx which

writes s->chan_tx?

> is set after the call to INIT_WORK() ?

Yes, unlike interrupt handlers, it isn't called until software kicks the
workqueue using schedule_work(&s->work_tx);

> >               }
> > -
> > -             INIT_WORK(&s->work_tx, work_fn_tx);
> >       }
> >
> >       chan = sci_request_dma_chan(port, DMA_DEV_TO_MEM);

Gr{oetje,eeting}s,

                        Geert
Simon Horman July 13, 2018, 7:42 a.m. UTC | #3
On Wed, Jul 11, 2018 at 05:15:57PM +0200, Geert Uytterhoeven wrote:
> Hi Simon,
> 
> On Wed, Jul 11, 2018 at 4:52 PM Simon Horman <horms@verge.net.au> wrote:
> > On Fri, Jul 06, 2018 at 11:05:42AM +0200, Geert Uytterhoeven wrote:
> > > The transmit DMA workqueue is never stopped, hence the work function may
> > > be called after the port has been shut down.
> > >
> > > Fix this race condition by cancelling queued work, if any, before DMA
> > > release.  Don't initialize the work if DMA initialization failed, as it
> > > won't be used anyway.
> > >
> > > Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> 
> > > --- a/drivers/tty/serial/sh-sci.c
> > > +++ b/drivers/tty/serial/sh-sci.c
> > > @@ -1293,6 +1293,7 @@ static void sci_tx_dma_release(struct sci_port *s)
> > >  {
> > >       struct dma_chan *chan = s->chan_tx_saved;
> > >
> > > +     cancel_work_sync(&s->work_tx);
> > >       s->chan_tx_saved = s->chan_tx = NULL;
> > >       s->cookie_tx = -EINVAL;
> > >       dmaengine_terminate_all(chan);
> > > @@ -1548,10 +1549,9 @@ static void sci_request_dma(struct uart_port *port)
> > >                               __func__, UART_XMIT_SIZE,
> > >                               port->state->xmit.buf, &s->tx_dma_addr);
> > >
> > > +                     INIT_WORK(&s->work_tx, work_fn_tx);
> > >                       s->chan_tx_saved = s->chan_tx = chan;
> >
> > Is it ok that work_fn_tx reads and writes s->work_tx which
> 
> writes s->chan_tx?

Yes :)

> > is set after the call to INIT_WORK() ?
> 
> Yes, unlike interrupt handlers, it isn't called until software kicks the
> workqueue using schedule_work(&s->work_tx);

Ok, so we are sure access is only happening on one CPU?

> 
> > >               }
> > > -
> > > -             INIT_WORK(&s->work_tx, work_fn_tx);
> > >       }
> > >
> > >       chan = sci_request_dma_chan(port, DMA_DEV_TO_MEM);
> 
> Gr{oetje,eeting}s,
> 
>                         Geert
> 
> -- 
> Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
> 
> In personal conversations with technical people, I call myself a hacker. But
> when I'm talking to journalists I just say "programmer" or something like that.
>                                 -- Linus Torvalds
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Geert Uytterhoeven July 13, 2018, 8 a.m. UTC | #4
Hi Simon,

On Fri, Jul 13, 2018 at 9:42 AM Simon Horman <horms@verge.net.au> wrote:
> On Wed, Jul 11, 2018 at 05:15:57PM +0200, Geert Uytterhoeven wrote:
> > On Wed, Jul 11, 2018 at 4:52 PM Simon Horman <horms@verge.net.au> wrote:
> > > On Fri, Jul 06, 2018 at 11:05:42AM +0200, Geert Uytterhoeven wrote:
> > > > The transmit DMA workqueue is never stopped, hence the work function may
> > > > be called after the port has been shut down.
> > > >
> > > > Fix this race condition by cancelling queued work, if any, before DMA
> > > > release.  Don't initialize the work if DMA initialization failed, as it
> > > > won't be used anyway.
> > > >
> > > > Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> >
> > > > --- a/drivers/tty/serial/sh-sci.c
> > > > +++ b/drivers/tty/serial/sh-sci.c
> > > > @@ -1293,6 +1293,7 @@ static void sci_tx_dma_release(struct sci_port *s)
> > > >  {
> > > >       struct dma_chan *chan = s->chan_tx_saved;
> > > >
> > > > +     cancel_work_sync(&s->work_tx);
> > > >       s->chan_tx_saved = s->chan_tx = NULL;
> > > >       s->cookie_tx = -EINVAL;
> > > >       dmaengine_terminate_all(chan);
> > > > @@ -1548,10 +1549,9 @@ static void sci_request_dma(struct uart_port *port)
> > > >                               __func__, UART_XMIT_SIZE,
> > > >                               port->state->xmit.buf, &s->tx_dma_addr);
> > > >
> > > > +                     INIT_WORK(&s->work_tx, work_fn_tx);
> > > >                       s->chan_tx_saved = s->chan_tx = chan;
> > >
> > > Is it ok that work_fn_tx reads and writes s->work_tx which
> >
> > writes s->chan_tx?
>
> Yes :)
>
> > > is set after the call to INIT_WORK() ?
> >
> > Yes, unlike interrupt handlers, it isn't called until software kicks the
> > workqueue using schedule_work(&s->work_tx);
>
> Ok, so we are sure access is only happening on one CPU?

sci_request_dma() is called during early port setup, before it can be used.
So no other CPU should be using it (unless it contains a time machine ;-)

Gr{oetje,eeting}s,

                        Geert
Simon Horman July 17, 2018, 9:53 a.m. UTC | #5
On Fri, Jul 13, 2018 at 10:00:05AM +0200, Geert Uytterhoeven wrote:
> Hi Simon,
> 
> On Fri, Jul 13, 2018 at 9:42 AM Simon Horman <horms@verge.net.au> wrote:
> > On Wed, Jul 11, 2018 at 05:15:57PM +0200, Geert Uytterhoeven wrote:
> > > On Wed, Jul 11, 2018 at 4:52 PM Simon Horman <horms@verge.net.au> wrote:
> > > > On Fri, Jul 06, 2018 at 11:05:42AM +0200, Geert Uytterhoeven wrote:
> > > > > The transmit DMA workqueue is never stopped, hence the work function may
> > > > > be called after the port has been shut down.
> > > > >
> > > > > Fix this race condition by cancelling queued work, if any, before DMA
> > > > > release.  Don't initialize the work if DMA initialization failed, as it
> > > > > won't be used anyway.
> > > > >
> > > > > Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> > >
> > > > > --- a/drivers/tty/serial/sh-sci.c
> > > > > +++ b/drivers/tty/serial/sh-sci.c
> > > > > @@ -1293,6 +1293,7 @@ static void sci_tx_dma_release(struct sci_port *s)
> > > > >  {
> > > > >       struct dma_chan *chan = s->chan_tx_saved;
> > > > >
> > > > > +     cancel_work_sync(&s->work_tx);
> > > > >       s->chan_tx_saved = s->chan_tx = NULL;
> > > > >       s->cookie_tx = -EINVAL;
> > > > >       dmaengine_terminate_all(chan);
> > > > > @@ -1548,10 +1549,9 @@ static void sci_request_dma(struct uart_port *port)
> > > > >                               __func__, UART_XMIT_SIZE,
> > > > >                               port->state->xmit.buf, &s->tx_dma_addr);
> > > > >
> > > > > +                     INIT_WORK(&s->work_tx, work_fn_tx);
> > > > >                       s->chan_tx_saved = s->chan_tx = chan;
> > > >
> > > > Is it ok that work_fn_tx reads and writes s->work_tx which
> > >
> > > writes s->chan_tx?
> >
> > Yes :)
> >
> > > > is set after the call to INIT_WORK() ?
> > >
> > > Yes, unlike interrupt handlers, it isn't called until software kicks the
> > > workqueue using schedule_work(&s->work_tx);
> >
> > Ok, so we are sure access is only happening on one CPU?
> 
> sci_request_dma() is called during early port setup, before it can be used.
> So no other CPU should be using it (unless it contains a time machine ;-)

Thanks, that is what I was/am missing.
--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c
index 674dc65454ae0684..939749073e7bdb11 100644
--- a/drivers/tty/serial/sh-sci.c
+++ b/drivers/tty/serial/sh-sci.c
@@ -1293,6 +1293,7 @@  static void sci_tx_dma_release(struct sci_port *s)
 {
 	struct dma_chan *chan = s->chan_tx_saved;
 
+	cancel_work_sync(&s->work_tx);
 	s->chan_tx_saved = s->chan_tx = NULL;
 	s->cookie_tx = -EINVAL;
 	dmaengine_terminate_all(chan);
@@ -1548,10 +1549,9 @@  static void sci_request_dma(struct uart_port *port)
 				__func__, UART_XMIT_SIZE,
 				port->state->xmit.buf, &s->tx_dma_addr);
 
+			INIT_WORK(&s->work_tx, work_fn_tx);
 			s->chan_tx_saved = s->chan_tx = chan;
 		}
-
-		INIT_WORK(&s->work_tx, work_fn_tx);
 	}
 
 	chan = sci_request_dma_chan(port, DMA_DEV_TO_MEM);