From: sashiko-bot@kernel.org
To: "Jelly Jia" <Jelly.Jia@cixtech.com>
Cc: robh@kernel.org, vkoul@kernel.org, dmaengine@vger.kernel.org,
conor+dt@kernel.org, Frank.Li@kernel.org,
devicetree@vger.kernel.org
Subject: Re: [PATCH v1 2/5] dmaengine: arm-dma350: Add slave transfer support
Date: Mon, 07 Sep 2026 03:49:17 +0000 [thread overview]
Message-ID: <20260907034918.09C681F00A3A@smtp.kernel.org> (raw)
In-Reply-To: <20260907033404.596839-3-Jelly.Jia@cixtech.com>
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
New issues:
- [High] DMA descriptors in memory are written using native endianness, causing corruption on big-endian CPUs.
- [High] Cyclic DMA residue calculation relies on fragile software tracking, lacks sub-period precision, and permanently desynchronizes if interrupts coalesce.
- [High] d350_of_xlate() incorrectly fails to allocate a channel if the first free channel lacks trigger support.
- [High] Valid DMA address of 0 is erroneously treated as a sentinel, disabling descriptor linking and breaking cyclic/SG transfers.
- [High] Calling dma_free_coherent() from the vchan_complete tasklet causes sleeping in atomic context.
- [High] Race condition between reading CH_LINKADDR and CH_XSIZE leads to invalid, non-monotonic DMA residue calculations.
- [Medium] Trigger block size ignores configured maxburst for DEV_TO_MEM transfers, degrading performance.
- [Medium] Torn read of the 64-bit hardware CH_LINKADDR register due to missing retry loop.
Pre-existing issues:
- [High] Spurious DMA_ERROR returned for descriptors that have been submitted but not yet issued.
--
commit e35af7caf5e3e88f06c1135c2c306f3cb90773b3
Author: Jelly Jia <Jelly.Jia@cixtech.com>
dmaengine: arm-dma350: Add slave transfer support
Add DMA slave support to the Arm DMA-350 driver, which so far only
supports memory-to-memory transfers.
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 4e17130de6c87..85e8c5e428044 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
[ ... ]
> +static void d350_free_cmds(struct device *dev, struct d350_desc *desc)
> +{
> + if (desc->cmds)
> + dma_free_coherent(dev, desc->cmds_size, desc->cmds,
> + desc->cmds_dma);
> + kfree(desc->cmd_len);
> +}
> +
> static void d350_desc_free(struct virt_dma_desc *vd)
> {
> - kfree(to_d350_desc(vd));
> + struct d350_desc *desc = to_d350_desc(vd);
> +
> + d350_free_cmds(vd->tx.chan->device->dev, desc);
> + kfree(desc);
> +}
[Severity: High]
Can this sleep in atomic context?
d350_desc_free() is registered as the virt-dma desc_free callback, which
executes in a tasklet (softirq) context. However, dma_free_coherent()
might sleep (e.g., when flushing IOMMU page tables), which could trigger
scheduling-while-atomic BUGs.
[ ... ]
> +static struct dma_chan *d350_of_xlate(struct of_phandle_args *dma_spec,
> + struct of_dma *ofdma)
> +{
> + struct d350 *dmac = ofdma->of_dma_data;
> + struct dma_chan *chan;
> + struct d350_chan *dch;
> + u32 req;
[ ... ]
> + chan = dma_get_any_slave_channel(&dmac->dma);
> + if (!chan) {
> + dev_err(dmac->dma.dev, "can't get a dma channel\n");
> + return NULL;
> + }
> +
> + dch = to_d350_chan(chan);
> + if (!dch->has_trig) {
> + dev_err(dmac->dma.dev, "channel %d has no trigger support\n",
> + chan->chan_id);
> + dma_release_channel(chan);
> + return NULL;
> + }
[Severity: High]
Does this incorrectly reject valid requests on heterogeneous DMA configurations?
If dma_get_any_slave_channel() happens to return a channel lacking trigger
support, the request is dropped entirely instead of continuing to search
the remaining free channels for a capable one.
[ ... ]
> +static void d350_fill_slave_cmd(struct d350_chan *dch, struct d350_desc *desc,
> + u32 *cmd, dma_addr_t mem, dma_addr_t dev_dma_addr,
> + size_t len, dma_addr_t link_addr,
> + enum dma_transfer_direction direction,
> + enum dma_slave_buswidth width, u32 maxburst,
> + enum ch_ctrl_donetype donetype)
> +{
> + bool mem_to_dev = direction == DMA_MEM_TO_DEV;
> + u16 xsize, xsizehi;
> + u32 devcfg;
> + u32 memcfg;
> + u32 trigcfg;
> +
> + desc->tsz = __ffs(width);
> + xsize = lower_16_bits(len >> desc->tsz);
> + xsizehi = upper_16_bits(len >> desc->tsz);
> + devcfg = d350_device_transcfg(maxburst);
> + memcfg = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
> +
> + trigcfg = FIELD_PREP(CH_TRIGINCFG_BLKSIZE,
> + mem_to_dev ? maxburst - 1 : 0) |
[Severity: Medium]
Does this degrade performance for device-to-memory transfers?
The trigger block size ignores the configured maxburst and is hardcoded to 0
for DEV_TO_MEM transfers. This means only a single item is processed per
hardware trigger, which might cause peripheral FIFOs to overflow if they
expect burst servicing.
> + FIELD_PREP(CH_TRIGINCFG_MODE, CH_TRIGINCFG_MODE_PERIPH_FC) |
> + FIELD_PREP(CH_TRIGINCFG_TYPE, CH_TRIGINCFG_TYPE_HW) |
> + FIELD_PREP(CH_TRIGINCFG_SEL, dch->req);
> +
> + cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
> + LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_SRCTRANSCFG |
> + LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR |
> + LINK_LINKADDRHI |
> + (mem_to_dev ? LINK_DESTRIGINCFG : LINK_SRCTRIGINCFG);
[Severity: High]
Will this cause corruption on big-endian CPUs?
Native CPU-endian words are being written directly to the DMA command list
buffer (desc->cmds), which will be fetched by the little-endian hardware.
Should this use cpu_to_le32 wrappers to avoid swapped addresses and sizes?
[ ... ]
> + cmd[11] = trigcfg;
> + cmd[12] = lower_32_bits(link_addr) |
> + (link_addr ? CH_LINKADDR_EN : 0);
[Severity: High]
Is it safe to use link_addr as a boolean condition here?
dma_alloc_coherent() can validly return 0 as a bus address. If it does,
CH_LINKADDR_EN will not be set, which would prematurely terminate and break
cyclic or scatter-gather transfers.
> + cmd[13] = upper_32_bits(link_addr);
> +}
[ ... ]
> +static u32 d350_get_sg_residue(struct d350_chan *dch)
> +{
> + struct d350_desc *desc = dch->desc;
> + size_t cmd_size = D350_SLAVE_CMD_WORDS * sizeof(u32);
> + size_t cmd = 0, i;
> + u32 residue;
> + u64 next_cmd;
> +
> + if (!desc->cmd_len)
> + return d350_get_residue(dch);
> +
> + /*
> + * CH_LINKADDR points at the next command. Match it against the command
> + * array to find the command currently executing, then add every later
> + * command which has not started yet.
> + */
> + next_cmd = readl_relaxed(dch->base + CH_LINKADDR) & ~CH_LINKADDR_EN;
> + next_cmd |= (u64)readl_relaxed(dch->base + CH_LINKADDRHI) << 32;
[Severity: Medium]
Could this result in a torn read of the 64-bit hardware CH_LINKADDR register?
The two 32-bit reads are executed sequentially without protection against
concurrent hardware updates, unlike the safe retry loop you used for CH_XSIZE.
If the hardware advances the command chain between the reads, the CPU constructs
a torn address.
> +
> + if (!next_cmd) {
> + cmd = desc->ncmds - 1;
> + } else {
> + for (i = 1; i < desc->ncmds; i++) {
> + if (next_cmd == desc->cmds_bus + i * cmd_size) {
> + cmd = i - 1;
> + break;
> + }
> + }
> + if (i == desc->ncmds)
> + return dch->residue;
> + }
> +
> + residue = d350_get_residue(dch);
[Severity: High]
Is there a race condition between reading CH_LINKADDR and CH_XSIZE?
d350_get_residue() reads CH_XSIZE. If the hardware completes command N and
starts N+1 between the earlier read of CH_LINKADDR and the read of CH_XSIZE,
the driver calculates residue using command N's position in the loop but
command N+1's remaining bytes. This would double-count the size of N+1 and
cause the reported residue to jump upwards, breaking monotonicity.
> + for (i = cmd + 1; i < desc->ncmds; i++)
> + residue += desc->cmd_len[i];
> +
> + return residue;
> +}
[ ... ]
> +static u32 d350_get_cyclic_residue(struct d350_desc *desc)
> +{
> + return desc->bytes - desc->period * desc->period_len;
> +}
[Severity: High]
Does this provide sufficient precision for cyclic consumers like ALSA?
This calculates an inaccurate residue that ignores intra-period hardware
progress, returning only period boundaries. ALSA PCM pointers typically
require high-resolution, accurate sub-period updates.
[ ... ]
> static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> struct dma_tx_state *state)
> {
> struct d350_chan *dch = to_d350_chan(chan);
> struct virt_dma_desc *vd;
> enum dma_status status;
> unsigned long flags;
> u32 residue = 0;
>
> status = dma_cookie_status(chan, cookie, state);
>
> spin_lock_irqsave(&dch->vc.lock, flags);
> if (cookie == dch->cookie) {
> status = dch->status;
> if (status == DMA_IN_PROGRESS || status == DMA_PAUSED)
> - dch->residue = d350_get_residue(dch);
> + dch->residue = d350_get_active_residue(dch);
> residue = dch->residue;
> } else if ((vd = vchan_find_desc(&dch->vc, cookie))) {
> residue = d350_desc_bytes(to_d350_desc(vd));
> } else if (status == DMA_IN_PROGRESS) {
> /* Somebody else terminated it? */
> status = DMA_ERROR;
> }
[Severity: High]
This is a pre-existing issue, but does this incorrectly return DMA_ERROR for
submitted but unissued descriptors?
vchan_find_desc() only searches the desc_issued list. If a client queries
status after tx_submit but before issue_pending, this explicit overwrite
converts a valid DMA_IN_PROGRESS status into DMA_ERROR.
[ ... ]
> static irqreturn_t d350_irq(int irq, void *data)
> {
[ ... ]
> + if (irq_status & CH_STAT_INTR_ERR) {
[ ... ]
> + } else {
> + if (desc->cyclic) {
> + desc->period = (desc->period + 1) % desc->periods;
> + dch->residue = d350_get_cyclic_residue(desc);
> + vchan_cyclic_callback(vd);
[Severity: High]
Can this permanently desynchronize the cyclic stream?
Unconditionally advancing the period by 1 software counter will lag behind the
actual hardware position if interrupt coalescing occurs or interrupts are
delayed, causing multiple hardware periods to complete between IRQ handling.
> + } else {
> + dch->status = DMA_COMPLETE;
> + dch->residue = 0;
> + dch->desc = NULL;
> + vchan_cookie_complete(vd);
> + d350_start_next(dch);
> + }
> }
> spin_unlock(&dch->vc.lock);
>
> return IRQ_HANDLED;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260907033404.596839-1-Jelly.Jia@cixtech.com?part=2
next prev parent reply other threads:[~2026-09-07 3:49 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-07 3:33 [PATCH v1 0/5] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
2026-09-07 3:34 ` [PATCH v1 1/5] dmaengine: arm-dma350: Fix source trigger bit Jelly Jia
2026-09-07 3:41 ` sashiko-bot
2026-09-07 3:34 ` [PATCH v1 2/5] dmaengine: arm-dma350: Add slave transfer support Jelly Jia
2026-09-07 3:49 ` sashiko-bot [this message]
2026-09-07 3:34 ` [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration Jelly Jia
2026-09-07 17:15 ` Conor Dooley
2026-09-09 6:05 ` Jelly Jia
2026-09-09 10:45 ` Conor Dooley
2026-09-07 3:34 ` [PATCH v1 4/5] dmaengine: cix-sky1-dma350: Add Sky1 integration driver Jelly Jia
2026-09-07 3:44 ` sashiko-bot
2026-09-07 3:34 ` [PATCH v1 5/5] arm64: dts: cix: Add Sky1 DMA-350 nodes Jelly Jia
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=20260907034918.09C681F00A3A@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Frank.Li@kernel.org \
--cc=Jelly.Jia@cixtech.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dmaengine@vger.kernel.org \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vkoul@kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.