mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Frank Li <Frank.li@oss.nxp.com>
To: Rui Wang <wr574332525@163.com>
Cc: vkoul@kernel.org, Eugeniy.Paltsev@synopsys.com,
	Frank.Li@kernel.org, dmaengine@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH v4] dmaengine: dw-axi-dmac: report paused state and residue in tx_status
Date: Wed, 23 Sep 2026 14:23:34 -0500	[thread overview]
Message-ID: <arQnNiDh1Gp7OXUE@SMW015318> (raw)
In-Reply-To: <20260923060045.5571-1-wr574332525@163.com>

On Wed, Sep 23, 2026 at 02:00:45PM +0800, Rui Wang wrote:
> [You don't often get email from wr574332525@163.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> The driver implements device_pause/device_resume, but device_tx_status
> keeps reporting DMA_IN_PROGRESS for a paused channel, so clients cannot
> tell a paused channel apart from a running one. Report DMA_PAUSED when
> the channel is paused and the cookie is still in flight, including for
> callers that pass a NULL dma_tx_state, and clear the stale is_paused
> flag in dma_chan_terminate_all(), which disables the channel and thus
> implicitly cancels the paused state.
>
> Also, the residue of an in-flight transfer is currently derived from
> the number of completed LLI blocks, so it only advances in block-size
> steps and stays stale for the duration of a large block. Read the
> current hardware pointer (CH_SAR for MEM_TO_DEV and MEM_TO_MEM, CH_DAR
> for DEV_TO_MEM) and walk the descriptor's LLIs to compute how many
> bytes have actually been transferred. The 64-bit pointer is sampled
> with a tearing-safe double read, as the transfer may be running
> concurrently. The pointer is only consulted for the descriptor most
> recently programmed into the hardware, tracked in the previously
> unused chan->desc field; any other descriptor has not been started yet
> and keeps reporting its full length. When the transfer has just
> completed but the descriptor has not been reaped yet, the pointer sits
> at the end of the last block and the residue naturally reads as 0;
> conversely, a channel whose enable bit is still set never reports full
> completion, so a stale pointer left by a previous transfer reusing the
> same buffer cannot be mistaken for a finished one. Cyclic descriptors
> keep the block-granular accounting.
>
> Tested on an FPGA platform.
>
> Signed-off-by: Rui Wang <wr574332525@163.com>
> ---
> Changes in v4:
> - Track which descriptor the hardware pointer registers belong to via
>   the (previously unused) chan->desc field instead of testing the head
>   of desc_issued: a queued-but-never-started descriptor must not be
>   matched against the stale pointer left by a previous transfer, which
>   could otherwise falsely report full completion when its buffer is
>   reused.
> - Clear chan->desc when its descriptor is reaped or the channel is
>   terminated.
> - Link to v3: https://lore.kernel.org/dmaengine/20260923044104.3234-1-wr574332525@163.com/
> Changes in v3:
> - Report DMA_PAUSED also to callers passing a NULL dma_tx_state.
> - Never report full completion while the channel enable bit is still
>   set, so a stale pointer (e.g. a new transfer reusing the buffer of a
>   just-finished one) is not mistaken for completion.
> - Link to v2: https://lore.kernel.org/dmaengine/20260923034653.1413-1-wr574332525@163.com/
> Changes in v2:
> - Sample the 64-bit SAR/DAR with a tearing-safe double read instead of
>   lo_hi_readq(), avoiding a torn pointer when the low half wraps.
> - Read the hardware pointer for the in-flight descriptor even after the
>   hardware has just completed it (channel enable self-cleared, IRQ not
>   yet handled), so the residue reads 0 instead of jumping back to the
>   full length.
> - Link to v1: https://lore.kernel.org/dmaengine/20260923030201.859-1-wr574332525@163.com/
> ---
>  .../dma/dw-axi-dmac/dw-axi-dmac-platform.c    | 114 ++++++++++++++++--
>  1 file changed, 106 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> index eebed2474..66da247ff 100644
> --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> @@ -352,33 +352,122 @@ static void vchan_desc_put(struct virt_dma_desc *vdesc)
>         axi_desc_put(vd_to_axi_desc(vdesc));
>  }
>
> +/*
> + * Read a 64-bit channel address register while the transfer may be
> + * running. A plain lo_hi_readq() tears if the low half wraps (carrying
> + * into the high half) between the two 32-bit reads, so sample the high
> + * half twice and re-sample the low half if it moved; a second 4 GiB
> + * wrap cannot happen within these few instructions.
> + */
> +static u64 axi_chan_readq(struct axi_dma_chan *chan, u32 reg)
> +{
> +       u32 hi, lo, hi2;
> +
> +       hi = readl(chan->chan_regs + reg + 4);
> +       lo = readl(chan->chan_regs + reg);
> +       hi2 = readl(chan->chan_regs + reg + 4);
> +       if (unlikely(hi != hi2)) {
> +               /* Low half wrapped in between, take consistent samples */
> +               lo = readl(chan->chan_regs + reg);
> +               hi = hi2;
> +       }

common pattern for this type problem is use do while loop.

do {
	hi = readl(base + REG_HI);
	lo = readl(base + REG_LO);
	hi2 = readl(base + REG_HI);
} while (hi != hi2);

> +
> +       return (u64)hi << 32 | lo;
> +}
> +
> +/*
> + * Return the number of bytes already transferred by the descriptor
> + * currently on the hardware (running, paused or just completed), based
> + * on the current read or write position: CH_SAR for MEM_TO_DEV and
> + * MEM_TO_MEM, CH_DAR for DEV_TO_MEM. Must be called with vc.lock held.
> + */
> +static u32 axi_chan_get_xferred(struct axi_dma_chan *chan,
> +                               struct axi_dma_desc *desc)
> +{
> +       struct axi_dma_hw_desc *hw_desc;
> +       bool dst = chan->direction == DMA_DEV_TO_MEM;
> +       u64 pos, start;
> +       u32 xferred = 0;
> +       int i;
> +
> +       pos = axi_chan_readq(chan, dst ? CH_DAR : CH_SAR);
> +
> +       for (i = 0; i < desc->nr_hw_descs; i++) {
> +               hw_desc = &desc->hw_desc[i];
> +               start = le64_to_cpu(dst ? hw_desc->lli->dar : hw_desc->lli->sar);
> +
> +               /* Current position is inside this block: partial progress */
> +               if (pos >= start && pos <= start + hw_desc->len)
> +                       return xferred + (u32)(pos - start);
> +
> +               xferred += hw_desc->len;
> +       }
> +
> +       /* Position doesn't match any block, be conservative */
> +       return 0;
> +}
> +
>  static enum dma_status
>  dma_chan_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
>                    struct dma_tx_state *txstate)
>  {
>         struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan);
>         struct virt_dma_desc *vdesc;
> +       struct axi_dma_desc *desc;
>         enum dma_status status;
>         u32 completed_length;
>         unsigned long flags;
> -       u32 completed_blocks;
>         size_t bytes = 0;
>         u32 length;
> -       u32 len;
>
>         status = dma_cookie_status(dchan, cookie, txstate);
> -       if (status == DMA_COMPLETE || !txstate)
> +       if (status == DMA_COMPLETE)
>                 return status;
>
>         spin_lock_irqsave(&chan->vc.lock, flags);
>
> +       if (chan->is_paused && status == DMA_IN_PROGRESS)
> +               status = DMA_PAUSED;
> +
> +       if (!txstate) {
> +               spin_unlock_irqrestore(&chan->vc.lock, flags);
> +               return status;
> +       }
> +
>         vdesc = vchan_find_desc(&chan->vc, cookie);
>         if (vdesc) {
> -               length = vd_to_axi_desc(vdesc)->length;
> -               completed_blocks = vd_to_axi_desc(vdesc)->completed_blocks;
> -               len = vd_to_axi_desc(vdesc)->hw_desc[0].len;
> -               completed_length = completed_blocks * len;
> -               bytes = length - completed_length;
> +               desc = vd_to_axi_desc(vdesc);
> +               length = desc->length;
> +
> +               if (chan->cyclic) {
> +                       completed_length = desc->completed_blocks *
> +                                          desc->hw_desc[0].len;
> +               } else if (desc == chan->desc) {
> +                       /*
> +                        * chan->desc is the descriptor last programmed into the
> +                        * hardware, so the pointer registers belong to it. Never
> +                        * match a queued-but-never-started descriptor against
> +                        * the stale pointer left by a previous transfer.
> +                        *
> +                        * If the transfer has just finished but the interrupt
> +                        * has not reaped the descriptor yet, the pointer sits at
> +                        * the end and the residue reads 0. Conversely, the
> +                        * hardware clears the channel enable bit on completion,
> +                        * so a still-enabled channel cannot be done: right after
> +                        * the start the pointer may still alias the end of a
> +                        * previous transfer reusing the same buffer. Never report
> +                        * full completion while the channel runs.
> +                        */
> +                       completed_length = axi_chan_get_xferred(chan, desc);
> +                       if (completed_length == length &&
> +                           axi_chan_is_hw_enable(chan))
> +                               completed_length = length - 1;

suppose you should call DMA done handle here. return length -1 is workaround

> +               } else {
> +                       /* Still queued, nothing transferred yet */
> +                       completed_length = 0;
> +               }
> +
> +               bytes = length - min_t(u32, completed_length, length);

now direct use min()

Frank

>         }
>
>         spin_unlock_irqrestore(&chan->vc.lock, flags);
> @@ -468,6 +557,9 @@ static void axi_chan_block_xfer_start(struct axi_dma_chan *chan,
>         }
>         axi_chan_config_write(chan, &config);
>
> +       /* The hardware pointer registers now belong to this descriptor */
> +       chan->desc = first;
> +
>         write_chan_llp(chan, first->hw_desc[0].llp | lms);
>
>         irq_mask = DWAXIDMAC_IRQ_DMA_TRF | DWAXIDMAC_IRQ_ALL_ERR;
> @@ -1077,6 +1169,8 @@ static noinline void axi_chan_handle_err(struct axi_dma_chan *chan, u32 status)
>         }
>         /* Remove the completed descriptor from issued list */
>         list_del(&vd->node);
> +       if (chan->desc == vd_to_axi_desc(vd))
> +               chan->desc = NULL;
>
>         /* WARN about bad descriptor */
>         dev_err(chan2dev(chan),
> @@ -1140,6 +1234,8 @@ static void axi_chan_block_xfer_complete(struct axi_dma_chan *chan)
>         } else {
>                 /* Remove the completed descriptor from issued list before completing */
>                 list_del(&vd->node);
> +               if (chan->desc == vd_to_axi_desc(vd))
> +                       chan->desc = NULL;
>                 vchan_cookie_complete(vd);
>         }
>
> @@ -1205,7 +1301,9 @@ static int dma_chan_terminate_all(struct dma_chan *dchan)
>
>         vchan_get_all_descriptors(&chan->vc, &head);
>
> +       chan->desc = NULL;
>         chan->cyclic = false;
> +       chan->is_paused = false;
>         spin_unlock_irqrestore(&chan->vc.lock, flags);
>
>         vchan_dma_desc_free_list(&chan->vc, &head);
> --
> 2.43.0
>

  reply	other threads:[~2026-09-23 19:23 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-23  6:00 Rui Wang
2026-09-23 19:23 ` Frank Li [this message]
2026-09-24  2:56   ` Rui Wang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=arQnNiDh1Gp7OXUE@SMW015318 \
    --to=frank.li@oss.nxp.com \
    --cc=Eugeniy.Paltsev@synopsys.com \
    --cc=Frank.Li@kernel.org \
    --cc=dmaengine@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=vkoul@kernel.org \
    --cc=wr574332525@163.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®