Re: [PATCH v3 11/24] dmaengine: dw-edma: Serialize LL event capture with channel kicks

Koichiro Den <[email protected]> Tue, 28 Jul 2026 15:03:53 +0900
Newsgroups org.kernel.vger.dmaengine,org.kernel.vger.linux-kernel
Message-ID <edypqjxsplctght75uuixih4lvyddonptxjq6mnjw4emgwa4mr@wjn5xbvvfwj4>
On Mon, Jul 27, 2026 at 04:35:28PM -0400, Frank Li wrote:
> On Tue, Jul 28, 2026 at 02:03:10AM +0900, Koichiro Den wrote:
> > The hard IRQ path clears interrupt status before deferred handling takes
> > vc.lock. If a doorbell starts a new hardware run in between, the
> > deferred handler can apply an event from the previous run.
> >
> > Use the event lock to serialize the interrupt status read and clear,
> > snapshot recording, and LL doorbell writes. Do not write another
> > doorbell while a snapshot is pending. A ring reset discards the snapshot
> > under the same lock. When events coalesce, keep STOP over PROGRESS.
> >
> > Changing chan->request alone neither changes the ring nor writes a
> > doorbell, so it does not invalidate the snapshot. Preserve it across
> > request changes. A later patch uses PROGRESS and STOP to decide whether
> > a pending STOP or PAUSE request can finish.
> >
> > Signed-off-by: Koichiro Den <[email protected]>
> > ---
> > Changes in v3:
> >   - New patch to serialize IRQ event capture with LL doorbell writes and
> >     defer doorbells while an LL event is pending. (Sashiko)
> >   - Add explicit PROGRESS and STOP types for deferred LL snapshots.
> >     (Frank)
> >
> >  drivers/dma/dw-edma/dw-edma-core.c    | 101 +++++++++++++++++++++++++-
> >  drivers/dma/dw-edma/dw-edma-core.h    |  37 ++++++++++
> >  drivers/dma/dw-edma/dw-edma-v0-core.c |  64 ++++++++--------
> >  drivers/dma/dw-edma/dw-hdma-v0-core.c |  32 +++++---
> >  4 files changed, 187 insertions(+), 47 deletions(-)
> >
> > diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> > index e3d0559206d8..3970b066dca2 100644
> > --- a/drivers/dma/dw-edma/dw-edma-core.c
> > +++ b/drivers/dma/dw-edma/dw-edma-core.c
> > @@ -33,7 +33,8 @@ struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd)
> >
> >  enum dw_edma_deferred_event {
> >  	DW_EDMA_DEFERRED_DONE	= BIT(0),
> > -	DW_EDMA_DEFERRED_ABORT	= BIT(1),
> > +	DW_EDMA_DEFERRED_LL	= BIT(1),
> > +	DW_EDMA_DEFERRED_ABORT	= BIT(2),
> >  };
> >
> >  static inline
> > @@ -74,6 +75,52 @@ static void vchan_free_desc(struct virt_dma_desc *vdesc)
> >  	kfree(vd2dw_edma_desc(vdesc));
> >  }
> >
> > +static void dw_edma_ll_snapshot_discard_locked(struct dw_edma_chan *chan)
> > +{
> > +	lockdep_assert_held(dw_edma_event_lock(chan));
> > +
> > +	chan->ll_irq.event = DW_EDMA_LL_EVENT_NONE;
> > +}
> > +
> > +static void dw_edma_ll_snapshot_discard(struct dw_edma_chan *chan)
> > +{
> > +	guard(raw_spinlock_irqsave)(dw_edma_event_lock(chan));
> > +
> > +	dw_edma_ll_snapshot_discard_locked(chan);
> > +}
> > +
> > +/* Must be called with vc.lock held. */
> > +static bool
> > +dw_edma_ll_snapshot_take(struct dw_edma_chan *chan,
> > +			 struct dw_edma_ll_snapshot *snapshot)
> > +{
> > +	guard(raw_spinlock_irqsave)(dw_edma_event_lock(chan));
> 
> Not sure previous why use raw_spinlock, suppose it is wrong. should be
> spin_lock.

Thanks for pointing it out. I checked all event_lock critical sections again,
and yes, you're absolutely right. spinlock_t is enough because:
- the lock serializes interrupt status/LLP capture with an LL kick
  (doorbell). There is no point in ensuring the holder non-preemptible on
  PREEMPT_RT.
- dw_edma_record_irq() builds the new snapshot locally and publishes it only
  once it is fully formed.

> 
> > +
> > +	if (chan->ll_irq.event == DW_EDMA_LL_EVENT_NONE)
> > +		return false;
> > +
> > +	*snapshot = chan->ll_irq;
> > +	dw_edma_ll_snapshot_discard_locked(chan);
> > +
> > +	return true;
> > +}
> > +
> ...
> >
> > +/*
> > + * Lock ordering:
> > + *
> > + *   chan->vc.lock -> dw_edma_event_lock(chan)
> > + *
> > + * Interrupt providers invoke the dw_edma_handler_t callback with the event
> > + * lock held. The callback must not take vc.lock.
> > + */
> > +static inline raw_spinlock_t *dw_edma_event_lock(struct dw_edma_chan *chan)
> > +{
> > +	if (chan->dw->core->event_scope == DW_EDMA_EVENT_PER_DIR)
> > +		return &chan->dw->event_lock[chan->dir];
> > +
> > +	return &chan->event_lock;
> 
> can we change chan:: event_lock to pointer,
> 
> when DW_EDMA_EVENT_PER_DIR
> 
> 	chan->event_lock -> chan->dw->event_lock[chan->dir]
> 
> else
> 	chan->event_lock -> create a lock when register HDMA channel.
> 
> So needn't DW_EDMA_EVENT_PER_DIR,

That looks simpler. Thanks for the suggestion, I'll rework it that way.

> 
> 
> > +}
> > +
> >  /*
> >   * Return the current LL entry index. A negative value means that the channel
> >   * context is not initialized or was lost after a link reset.
> > diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
> > index b3098eb83442..43e33a400a3e 100644
> > --- a/drivers/dma/dw-edma/dw-edma-v0-core.c
> > +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
> > @@ -339,8 +339,8 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
> >  			   dw_edma_handler_t handler)
> >  {
> >  	struct dw_edma *dw = dw_irq->dw;
> > -	unsigned long total, pos, val;
> > -	irqreturn_t ret = IRQ_NONE;
> > +	u8 events[EDMA_V0_MAX_NR_CH] = {};
> > +	unsigned long total, pos, val, active = 0;
> >  	struct dw_edma_chan *chan;
> >  	unsigned long off;
> >  	unsigned long *mask;
> > @@ -356,45 +356,45 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
> >  		mask = dw_irq->rd_mask;
> >  	}
> >
> > -	/*
> > -	 * DONE and ABORT status share one register, and on remote setups
> > -	 * every read is a non-posted round trip across the PCIe link. Take
> > -	 * one snapshot and derive both views from it. An abort raised
> > -	 * after the snapshot is deferred, not lost: only bits observed in
> > -	 * the snapshot are ever cleared below, so its status remains set and
> > -	 * triggers another handler pass.
> > -	 */
> > -	sts = GET_RW_32(dw, dir, int_status);
> > -
> > -	val = FIELD_GET(EDMA_V0_DONE_INT_MASK, sts);
> > -	val &= *mask;
> > -	for_each_set_bit(pos, &val, total) {
> > -		chan = &dw->chan[pos + off];
> > +	scoped_guard(raw_spinlock_irqsave, &dw->event_lock[dir]) {
> 
> use helper dw_edma_event_lock()

Sure. My mistake.

Thanks for the review.
Koichiro

> 
> Frank
> > +		/*
> > +		 * DONE and ABORT status share one register, and on remote setups
> > +		 * every read is a non-posted round trip across the PCIe link. Take
> > +		 * one snapshot and derive both views from it.
> > +		 */
> > +		sts = GET_RW_32(dw, dir, int_status);
> >
> > -		if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
> > -			continue;
> > +		val = FIELD_GET(EDMA_V0_DONE_INT_MASK, sts);
> > +		val &= *mask;
> > +		for_each_set_bit(pos, &val, total) {
> > +			chan = &dw->chan[pos + off];
> >
> > -		dw_edma_v0_core_clear_done_int(chan);
> > -		handler(chan, DW_EDMA_IRQ_DONE);
> > +			if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
> > +				continue;
> >
> > -		ret = IRQ_HANDLED;
> > -	}
> > +			events[pos] |= DW_EDMA_IRQ_DONE;
> > +			active |= BIT(pos);
> > +			dw_edma_v0_core_clear_done_int(chan);
> > +		}
> >
> > -	val = FIELD_GET(EDMA_V0_ABORT_INT_MASK, sts);
> > -	val &= *mask;
> > -	for_each_set_bit(pos, &val, total) {
> > -		chan = &dw->chan[pos + off];
> > +		val = FIELD_GET(EDMA_V0_ABORT_INT_MASK, sts);
> > +		val &= *mask;
> > +		for_each_set_bit(pos, &val, total) {
> > +			chan = &dw->chan[pos + off];
> >
> > -		if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
> > -			continue;
> > +			if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
> > +				continue;
> >
> > -		dw_edma_v0_core_clear_abort_int(chan);
> > -		handler(chan, DW_EDMA_IRQ_ABORT);
> > +			events[pos] |= DW_EDMA_IRQ_ABORT;
> > +			active |= BIT(pos);
> > +			dw_edma_v0_core_clear_abort_int(chan);
> > +		}
> >
> > -		ret = IRQ_HANDLED;
> > +		for_each_set_bit(pos, &active, total)
> > +			handler(&dw->chan[pos + off], events[pos]);
> >  	}
> >
> > -	return ret;
> > +	return active ? IRQ_HANDLED : IRQ_NONE;
> >  }
> >
> >  static void dw_edma_v0_write_ll_data(struct dw_edma_chan *chan, int i,
> > diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
> > index 64a07fbc5be3..bbf9ee0392f8 100644
> > --- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
> > +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
> > @@ -177,10 +177,12 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
> >  			   dw_edma_handler_t handler)
> >  {
> >  	struct dw_edma *dw = dw_irq->dw;
> > -	unsigned long total, pos, val;
> > +	unsigned int events;
> > +	unsigned long total, pos;
> >  	irqreturn_t ret = IRQ_NONE;
> >  	struct dw_edma_chan *chan;
> >  	unsigned long off, *mask;
> > +	u32 val;
> >
> >  	if (dir == EDMA_DIR_WRITE) {
> >  		total = dw->wr_ch_cnt;
> > @@ -197,20 +199,28 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
> >  		if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
> >  			continue;
> >
> > -		val = dw_hdma_v0_core_status_int(chan);
> > -		if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) {
> > -			dw_hdma_v0_core_clear_done_int(chan);
> > -			handler(chan, DW_EDMA_IRQ_STOP);
> > +		events = 0;
> > +		scoped_guard(raw_spinlock_irqsave, dw_edma_event_lock(chan)) {
> > +			val = dw_hdma_v0_core_status_int(chan);
> >
> > -			ret = IRQ_HANDLED;
> > -		}
> > +			if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) {
> > +				events |= DW_EDMA_IRQ_STOP;
> > +				dw_hdma_v0_core_clear_done_int(chan);
> > +			}
> >
> > -		if (FIELD_GET(HDMA_V0_ABORT_INT_MASK, val)) {
> > -			dw_hdma_v0_core_clear_abort_int(chan);
> > -			handler(chan, DW_EDMA_IRQ_ABORT);
> > +			if (FIELD_GET(HDMA_V0_ABORT_INT_MASK, val)) {
> > +				events |= DW_EDMA_IRQ_ABORT;
> > +				dw_hdma_v0_core_clear_abort_int(chan);
> > +			}
> >
> > -			ret = IRQ_HANDLED;
> > +			if (events)
> > +				handler(chan, events);
> >  		}
> > +
> > +		if (!events)
> > +			continue;
> > +
> > +		ret = IRQ_HANDLED;
> >  	}
> >
> >  	return ret;
> > --
> > 2.51.0
> >