[RFC PATCH v3 22/35] dmaengine: dw-edma: Serialize RMW on shared interrupt registers

Koichiro Den posted 35 patches 1 month, 3 weeks ago
[RFC PATCH v3 22/35] dmaengine: dw-edma: Serialize RMW on shared interrupt registers
Posted by Koichiro Den 1 month, 3 weeks ago
The per-direction int_mask and linked_list_err_en registers are shared
between all channels. Updating them requires a read-modify-write
sequence, which can lose concurrent updates when multiple channels are
started in parallel. This may leave interrupts masked and stall
transfers under high load.

Protect the RMW sequences with dw->lock.

Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
 drivers/dma/dw-edma/dw-edma-core.h    |  3 ++-
 drivers/dma/dw-edma/dw-edma-v0-core.c | 13 ++++++++++---
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index f652d2e38843..d393976a8bfc 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -118,7 +118,8 @@ struct dw_edma {
 
 	struct dw_edma_chan		*chan;
 
-	raw_spinlock_t			lock;		/* Only for legacy */
+	/* For legacy + shared regs RMW among channels */
+	raw_spinlock_t			lock;
 
 	struct dw_edma_chip             *chip;
 
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index 42a254eb9379..770b011ba3e4 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -369,7 +369,8 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
 {
 	struct dw_edma_chan *chan = chunk->chan;
 	struct dw_edma *dw = chan->dw;
-	u32 tmp;
+	unsigned long flags;
+	u32 tmp, orig;
 
 	dw_edma_v0_core_write_chunk(chunk);
 
@@ -413,7 +414,9 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
 			}
 		}
 		/* Interrupt mask/unmask - done, abort */
+		raw_spin_lock_irqsave(&dw->lock, flags);
 		tmp = GET_RW_32(dw, chan->dir, int_mask);
+		orig = tmp;
 		if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) {
 			tmp |= FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
 			tmp |= FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
@@ -421,11 +424,15 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
 			tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
 			tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
 		}
-		SET_RW_32(dw, chan->dir, int_mask, tmp);
+		if (tmp != orig)
+			SET_RW_32(dw, chan->dir, int_mask, tmp);
 		/* Linked list error */
 		tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
+		orig = tmp;
 		tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
-		SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
+		if (tmp != orig)
+			SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
+		raw_spin_unlock_irqrestore(&dw->lock, flags);
 		/* Channel control */
 		SET_CH_32(dw, chan->dir, chan->id, ch_control1,
 			  (DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));
-- 
2.51.0
Re: [RFC PATCH v3 22/35] dmaengine: dw-edma: Serialize RMW on shared interrupt registers
Posted by Frank Li 1 month, 3 weeks ago
On Thu, Dec 18, 2025 at 12:15:56AM +0900, Koichiro Den wrote:
> The per-direction int_mask and linked_list_err_en registers are shared
> between all channels. Updating them requires a read-modify-write
> sequence, which can lose concurrent updates when multiple channels are
> started in parallel. This may leave interrupts masked and stall
> transfers under high load.
>
> Protect the RMW sequences with dw->lock.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---

I just posted a similar patch
https://lore.kernel.org/imx/20251212-edma_ll-v1-1-fc863d9f5ca3@nxp.com/

It change some method and I am working on add new request during dma engine
running.

At least, you can base on above thread.

Frank

>  drivers/dma/dw-edma/dw-edma-core.h    |  3 ++-
>  drivers/dma/dw-edma/dw-edma-v0-core.c | 13 ++++++++++---
>  2 files changed, 12 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> index f652d2e38843..d393976a8bfc 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.h
> +++ b/drivers/dma/dw-edma/dw-edma-core.h
> @@ -118,7 +118,8 @@ struct dw_edma {
>
>  	struct dw_edma_chan		*chan;
>
> -	raw_spinlock_t			lock;		/* Only for legacy */
> +	/* For legacy + shared regs RMW among channels */
> +	raw_spinlock_t			lock;
>
>  	struct dw_edma_chip             *chip;
>
> diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
> index 42a254eb9379..770b011ba3e4 100644
> --- a/drivers/dma/dw-edma/dw-edma-v0-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
> @@ -369,7 +369,8 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
>  {
>  	struct dw_edma_chan *chan = chunk->chan;
>  	struct dw_edma *dw = chan->dw;
> -	u32 tmp;
> +	unsigned long flags;
> +	u32 tmp, orig;
>
>  	dw_edma_v0_core_write_chunk(chunk);
>
> @@ -413,7 +414,9 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
>  			}
>  		}
>  		/* Interrupt mask/unmask - done, abort */
> +		raw_spin_lock_irqsave(&dw->lock, flags);
>  		tmp = GET_RW_32(dw, chan->dir, int_mask);
> +		orig = tmp;
>  		if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) {
>  			tmp |= FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
>  			tmp |= FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
> @@ -421,11 +424,15 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
>  			tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
>  			tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
>  		}
> -		SET_RW_32(dw, chan->dir, int_mask, tmp);
> +		if (tmp != orig)
> +			SET_RW_32(dw, chan->dir, int_mask, tmp);
>  		/* Linked list error */
>  		tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
> +		orig = tmp;
>  		tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
> -		SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
> +		if (tmp != orig)
> +			SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
> +		raw_spin_unlock_irqrestore(&dw->lock, flags);
>  		/* Channel control */
>  		SET_CH_32(dw, chan->dir, chan->id, ch_control1,
>  			  (DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));
> --
> 2.51.0
>
Re: [RFC PATCH v3 22/35] dmaengine: dw-edma: Serialize RMW on shared interrupt registers
Posted by Koichiro Den 1 month, 3 weeks ago
On Fri, Dec 19, 2025 at 09:39:37AM -0500, Frank Li wrote:
> On Thu, Dec 18, 2025 at 12:15:56AM +0900, Koichiro Den wrote:
> > The per-direction int_mask and linked_list_err_en registers are shared
> > between all channels. Updating them requires a read-modify-write
> > sequence, which can lose concurrent updates when multiple channels are
> > started in parallel. This may leave interrupts masked and stall
> > transfers under high load.
> >
> > Protect the RMW sequences with dw->lock.
> >
> > Signed-off-by: Koichiro Den <den@valinux.co.jp>
> > ---
> 
> I just posted a similar patch
> https://lore.kernel.org/imx/20251212-edma_ll-v1-1-fc863d9f5ca3@nxp.com/
> 
> It change some method and I am working on add new request during dma engine
> running.
> 
> At least, you can base on above thread.

I hadn't seen it, thanks for the pointer. I'll read through it to base my
work on your series.

Koichiro

> 
> Frank
> 
> >  drivers/dma/dw-edma/dw-edma-core.h    |  3 ++-
> >  drivers/dma/dw-edma/dw-edma-v0-core.c | 13 ++++++++++---
> >  2 files changed, 12 insertions(+), 4 deletions(-)
> >
> > diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> > index f652d2e38843..d393976a8bfc 100644
> > --- a/drivers/dma/dw-edma/dw-edma-core.h
> > +++ b/drivers/dma/dw-edma/dw-edma-core.h
> > @@ -118,7 +118,8 @@ struct dw_edma {
> >
> >  	struct dw_edma_chan		*chan;
> >
> > -	raw_spinlock_t			lock;		/* Only for legacy */
> > +	/* For legacy + shared regs RMW among channels */
> > +	raw_spinlock_t			lock;
> >
> >  	struct dw_edma_chip             *chip;
> >
> > diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
> > index 42a254eb9379..770b011ba3e4 100644
> > --- a/drivers/dma/dw-edma/dw-edma-v0-core.c
> > +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
> > @@ -369,7 +369,8 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
> >  {
> >  	struct dw_edma_chan *chan = chunk->chan;
> >  	struct dw_edma *dw = chan->dw;
> > -	u32 tmp;
> > +	unsigned long flags;
> > +	u32 tmp, orig;
> >
> >  	dw_edma_v0_core_write_chunk(chunk);
> >
> > @@ -413,7 +414,9 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
> >  			}
> >  		}
> >  		/* Interrupt mask/unmask - done, abort */
> > +		raw_spin_lock_irqsave(&dw->lock, flags);
> >  		tmp = GET_RW_32(dw, chan->dir, int_mask);
> > +		orig = tmp;
> >  		if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) {
> >  			tmp |= FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
> >  			tmp |= FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
> > @@ -421,11 +424,15 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
> >  			tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
> >  			tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
> >  		}
> > -		SET_RW_32(dw, chan->dir, int_mask, tmp);
> > +		if (tmp != orig)
> > +			SET_RW_32(dw, chan->dir, int_mask, tmp);
> >  		/* Linked list error */
> >  		tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
> > +		orig = tmp;
> >  		tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
> > -		SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
> > +		if (tmp != orig)
> > +			SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
> > +		raw_spin_unlock_irqrestore(&dw->lock, flags);
> >  		/* Channel control */
> >  		SET_CH_32(dw, chan->dir, chan->id, ch_control1,
> >  			  (DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));
> > --
> > 2.51.0
> >