From: sashiko-bot@kernel.org
To: "Shan-Chun Hung" <shanchun1218@gmail.com>,
dmaengine@vger.kernel.org, devicetree@vger.kernel.org
Cc: robh@kernel.org, conor+dt@kernel.org, vkoul@kernel.org,
Frank.Li@kernel.org
Subject: Re: [PATCH 2/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver
Date: Tue, 03 Nov 2026 02:43:08 +0000 [thread overview]
Message-ID: <sashiko-outbox-194466@kernel.org> (raw)
In-Reply-To: <20261103022202.2139674-3-shanchun1218@gmail.com>
Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
- [Critical] dmaengine: ma35d1: premature register setup in ma35d1_desc_alloc() and prep
- [Critical] dmaengine: ma35d1: active DMA after timeout and double completion in IRQ
- [High] dmaengine: ma35d1: missing PDMA_TBINTDIS in ma35d1_prep_dma_memcpy()
- [High] dmaengine: ma35d1: unvalidated SG segment lengths in ma35d1_prep_slave_sg()
- [High] dmaengine: ma35d1: broken residue and status reporting in ma35d1_tx_status()
- [High] dmaengine: ma35d1: unlocked PDMA_TOUTEN RMW in ma35d1_pdma_interrupt()
- [High] dmaengine: ma35d1: state bugs in ma35d1_of_xlate() and free_chan_resources()
- [High] dmaengine: ma35d1-pdma: fix signed dma-channels pointer and zero bounds check
- [High] dmaengine: ma35d1-pdma: unsafe teardown order in ma35d1_pdma_remove()
- [Medium] dmaengine: ma35d1-pdma: disable channel timeout in ma35d1_terminate_all()
- [Medium] dmaengine: ma35d1-pdma: unreleased ch->desc and stale ch->cfg in ma35d1_free_chan_resources()
--
commit f0cfa08c80802c32fef4b715415e04126d30e654
Author: Shan-Chun Hung <shanchun1218@gmail.com>
dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver
This commit adds a DMAEngine driver for the PDMA controller in the Nuvoton
MA35D1 SoC, supporting up to 10 channels with memory-to-memory, slave
scatter-gather, and cyclic transfers, as well as per-channel peripheral RX
timeouts.
> diff --git a/drivers/dma/ma35d1-pdma.c b/drivers/dma/ma35d1-pdma.c
> new file mode 100644
> index 0000000000000..176bc56c80639
> --- /dev/null
> +++ b/drivers/dma/ma35d1-pdma.c
> @@ -0,0 +1,780 @@
[ ... ]
> +#define PDMA_WIDTH(w) FIELD_PREP(GENMASK(13, 12), (w) >> 1)
> +#define PDMA_TXCNT(cnt) FIELD_PREP(GENMASK(31, 16), (cnt) - 1)
> +#define PDMA_GET_TXCNT(ctrl) FIELD_GET(GENMASK(31, 16), readl(ctrl))
> +#define PDMA_CH_OFFSET(ch) ((ch) * 0x10)
[ ... ]
> +static int ma35d1_terminate_all(struct dma_chan *chan)
> +{
> + LIST_HEAD(head);
> + struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
> + unsigned long flags, pdmadev_flags;
> +
> + spin_lock_irqsave(&ch->vc.lock, flags);
> +
> + spin_lock_irqsave(&ch->pdmadev->lock, pdmadev_flags);
> + ma35d1_pdma_reg_clear(ch, PDMA_CHCTL, BIT(ch->id));
> + spin_unlock_irqrestore(&ch->pdmadev->lock, pdmadev_flags);
[Severity: Medium]
When a channel configured with a peripheral timeout is terminated in
ma35d1_terminate_all(), only BIT(ch->id) in PDMA_CHCTL is cleared, leaving
BIT(ch->id) enabled in PDMA_TOUTEN and PDMA_TOUTIEN from
ma35d1_pdma_start_desc() -> ma35d1_pdma_timeout().
Could the hardware timeout counter fire after termination and cause
ma35d1_pdma_interrupt() to prematurely complete a subsequently started
descriptor on the same channel? Should PDMA_TOUTEN and PDMA_TOUTIEN also be
cleared here?
> +
> + if (ch->desc) {
> + vchan_terminate_vdesc(&ch->desc->vd);
> + ch->desc = NULL;
> + }
[ ... ]
> +static void ma35d1_pdma_setup_channel(struct ma35d1_chan *ch)
> +{
> + u32 offset, reg, shift;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&ch->pdmadev->lock, flags);
> +
> + if ((readl(ch->base + PDMA_CH_OFFSET(ch->id)) & GENMASK(1, 0)) != 0) {
> + reg = readl(ch->base + PDMA_CHCTL);
> + writel(0, ch->base + PDMA_CH_OFFSET(ch->id));
> + ma35d1_pdma_reg_set(ch, PDMA_CHRST, BIT(ch->id));
> + writel(reg | BIT(ch->id), ch->base + PDMA_CHCTL);
> + } else {
> + writel(0, ch->base + PDMA_CH_OFFSET(ch->id));
> + ma35d1_pdma_reg_set(ch, PDMA_CHCTL, BIT(ch->id));
> + }
> + ma35d1_pdma_reg_set(ch, PDMA_INTEN, BIT(ch->id));
> +
> + offset = PDMA_REQSEL + (ch->id & ~GENMASK(1, 0));
> + shift = (ch->id & GENMASK(1, 0)) * 8;
> + ma35d1_pdma_reg_clear(ch, offset, GENMASK(7, 0) << shift);
> + ma35d1_pdma_reg_set(ch, offset, ch->request << shift);
> +
> + spin_unlock_irqrestore(&ch->pdmadev->lock, flags);
> +}
[ ... ]
> +static struct ma35d1_desc *ma35d1_desc_alloc(struct ma35d1_chan *ch, u32 sg_len)
> +{
> + struct ma35d1_desc *d;
> +
> + d = kzalloc(struct_size(d, sg, sg_len), GFP_ATOMIC);
> + if (!d)
> + return NULL;
> +
> + d->sg_len = sg_len;
> + d->dma_len = sizeof(*d->sg) * sg_len;
> + d->dma_addr = dma_map_single(ch->dev, d->sg, d->dma_len, DMA_TO_DEVICE);
> + if (dma_mapping_error(ch->dev, d->dma_addr)) {
> + dev_err(ch->dev, "dma mapping error for channel\n");
> + kfree(d);
> + return NULL;
> + }
> +
> + writel(d->dma_addr, ch->base + PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_NEXT);
> +
> + return d;
> +}
> +
> +static struct dma_async_tx_descriptor *
> +ma35d1_desc_submit(struct ma35d1_chan *ch, struct ma35d1_desc *d, bool cyclic,
> + unsigned long tx_flags)
> +{
> + dma_sync_single_for_device(ch->dev, d->dma_addr, d->dma_len, DMA_TO_DEVICE);
> + d->cyclic = cyclic;
> + d->ch = ch;
> + ch->error = false;
> +
> + return vchan_tx_prep(&ch->vc, &d->vd, tx_flags);
> +}
[Severity: Critical]
Hardware registers and per-channel state are being modified during descriptor
preparation (ma35d1_prep_dma_memcpy(), ma35d1_prep_slave_sg(), and
ma35d1_prep_dma_cyclic()) rather than when the descriptor is actually started
in ma35d1_pdma_start_desc() -> ma35d1_pdma_start():
1. ma35d1_desc_alloc() writes d->dma_addr directly to PDMA_DSCT_NEXT before
d->sg is populated or synced with dma_sync_single_for_device(), and without
holding ch->vc.lock.
2. Immediately after ma35d1_desc_alloc(), the prep functions call
ma35d1_pdma_setup_channel(ch). If bits 1:0 of PDMA_DSCT_CTL are non-zero (for
example, while an active transfer is running, or after a timeout or
ma35d1_terminate_all()), ma35d1_pdma_setup_channel() writes 0 to PDMA_DSCT_CTL
and asserts BIT(ch->id) in PDMA_CHRST. Doesn't resetting the channel here
abort any in-flight transfer and wipe out the PDMA_DSCT_NEXT value that
ma35d1_desc_alloc() just programmed?
3. Conversely, when a descriptor is dequeued in ma35d1_pdma_start_desc() ->
ma35d1_pdma_start(), neither ma35d1_pdma_setup_channel(ch) nor the write of
d->dma_addr to PDMA_DSCT_NEXT is performed:
static void ma35d1_pdma_start(struct ma35d1_chan *ch)
{
struct ma35d1_desc *d = ch->desc;
if (d->sg_len == 1 && !d->cyclic) {
...
} else {
/* Trigger scatter-gather mode. */
ma35d1_pdma_reg_clear(ch, PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_CTL, PDMA_OP_MSK);
ma35d1_pdma_reg_set(ch, PDMA_CH_OFFSET(ch->id) + PDMA_DSCT_CTL, PDMA_OP_SCATTER);
}
If multiple descriptors are prepared before dma_async_issue_pending(), won't
PDMA_DSCT_NEXT be overwritten by the last prepared descriptor, and subsequent
descriptors started from ma35d1_pdma_complete() never program PDMA_DSCT_NEXT
or re-enable BIT(ch->id) in PDMA_CHCTL?
4. In ma35d1_desc_submit(), ch->error = false is written without holding
ch->vc.lock, which races with ma35d1_pdma_interrupt() and ma35d1_tx_status().
Should channel setup and writing PDMA_DSCT_NEXT be moved into
ma35d1_pdma_start() under ch->vc.lock?
> +
> +static struct dma_async_tx_descriptor *
> +ma35d1_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
> + size_t len, unsigned long tx_flags)
> +{
[ ... ]
> + for (i = 0; i < sg_len; i++) {
> + tlen = min_t(size_t, len, max_chunk);
> + d->sg[i].src = src;
> + d->sg[i].dst = dst;
> + d->sg[i].ctl = PDMA_WIDTH(width) | PDMA_TXCNT(tlen / width) | PDMA_OP_SCATTER;
> + d->sg[i].next = d->dma_addr + PDMA_CH_OFFSET(i + 1);
> + src += tlen;
> + dst += tlen;
> + len -= tlen;
> + }
> + d->sg[sg_len - 1].ctl &= ~(PDMA_OP_MSK | PDMA_TBINTDIS);
> + d->sg[sg_len - 1].ctl |= PDMA_OP_BASIC;
[Severity: High]
In ma35d1_prep_dma_memcpy(), the final SG entry clears PDMA_TBINTDIS, but
intermediate entries in the loop never set PDMA_TBINTDIS in d->sg[i].ctl
(unlike ma35d1_prep_slave_sg()).
When len > max_chunk (so sg_len > 1), won't the controller raise a transfer
done interrupt (PDMA_TDSTS) as soon as the first chunk (d->sg[0]) finishes,
causing ma35d1_pdma_interrupt() -> ma35d1_pdma_complete() to complete and
free the descriptor while the remaining chunks are still in flight?
> +
> + return ma35d1_desc_submit(ch, d, false, tx_flags);
> +}
> +
> +static struct dma_async_tx_descriptor *
> +ma35d1_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
> + u32 sg_len, enum dma_transfer_direction dir,
> + unsigned long tx_flags, void *context)
> +{
[ ... ]
> + for_each_sg(sgl, sgent, sg_len, i) {
> + if (dir == DMA_MEM_TO_DEV) {
> + d->sg[i].ctl = PDMA_WIDTH(width) | PDMA_DAFIX;
> + d->sg[i].src = sg_dma_address(sgent);
> + d->sg[i].dst = ch->cfg.dst_addr;
> + } else {
> + d->sg[i].ctl = PDMA_WIDTH(width) | PDMA_SAFIX;
> + d->sg[i].src = ch->cfg.src_addr;
> + d->sg[i].dst = sg_dma_address(sgent);
> + }
> + d->sg[i].ctl |= PDMA_TXCNT(sg_dma_len(sgent) / width) | PDMA_TBINTDIS |
> + PDMA_TXTYPE | PDMA_OP_SCATTER;
> + d->sg[i].next = d->dma_addr + PDMA_CH_OFFSET(i + 1);
> + }
[Severity: High]
In ma35d1_prep_slave_sg(), sg_dma_len(sgent) / width is passed directly to
PDMA_TXCNT(cnt), which encodes FIELD_PREP(GENMASK(31, 16), (cnt) - 1), without
validating the segment length (unlike ma35d1_prep_dma_cyclic() and
ma35d1_prep_dma_memcpy()).
If sg_dma_len(sgent) < width (or 0), sg_dma_len(sgent) / width is 0 and
(cnt) - 1 underflows to 0xffff (65536 beats), causing an out-of-bounds DMA
transfer. Conversely, if sg_dma_len(sgent) / width > PDMA_MAX_CHAN_BEATS,
the 16-bit field overflows and truncates the transfer, and if
sg_dma_len(sgent) % width != 0, trailing bytes are dropped.
Should ma35d1_prep_slave_sg() validate (or split) each SG entry's length and
alignment, and should ma35d1_pdma_probe() also advertise the maximum segment
size via dma_set_max_seg_size()?
[ ... ]
> +static struct dma_chan *ma35d1_of_xlate(struct of_phandle_args *dma_spec, struct of_dma *of)
> +{
> + struct ma35d1_pdmadev *pdmadev = of->of_dma_data;
> + unsigned int chan_id, reqsel;
> +
> + if (dma_spec->args_count != 2)
> + return NULL;
> +
> + chan_id = dma_spec->args[0];
> + reqsel = dma_spec->args[1];
> + if (chan_id >= pdmadev->nr_chans || reqsel > GENMASK(7, 0))
> + return NULL;
> +
> + pdmadev->channels[chan_id].request = reqsel;
> +
> + return dma_get_slave_channel(&pdmadev->channels[chan_id].vc.chan);
> +}
[Severity: High]
In ma35d1_of_xlate(), pdmadev->channels[chan_id].request = reqsel is written
before calling dma_get_slave_channel().
If chan_id is already in use by another consumer, dma_get_slave_channel()
will return NULL, but the active channel's request field will already have
been overwritten with reqsel. Should request only be assigned after
dma_get_slave_channel() succeeds?
> +
> +static void ma35d1_free_chan_resources(struct dma_chan *chan)
> +{
> + struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
> +
> + vchan_free_chan_resources(&ch->vc);
> + ch->request = 0;
> +}
[Severity: Medium]
When a channel is released in ma35d1_free_chan_resources(), ch->request is
reset to 0, but ch->cfg (including ch->cfg.peripheral_config) is not cleared,
and any active descriptor in ch->desc (which ma35d1_pdma_start_desc() unlinked
from vc.desc_issued via list_del(&vd->node)) is not terminated or freed.
If a slave consumer configures peripheral_config via ma35d1_slave_config() and
later releases the channel, won't a subsequent DMA_MEMCPY user (or a slave
consumer that does not set peripheral_config) inherit the stale
ch->cfg.peripheral_config pointer in ma35d1_pdma_start_desc() ->
ma35d1_pdma_timeout() and erroneously enable peripheral timeout interrupts?
[ ... ]
> +static enum dma_status ma35d1_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> + struct dma_tx_state *txstate)
> +{
> + enum dma_status ret;
> + struct ma35d1_chan *ch = to_ma35d1_pdma_chan(chan);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&ch->vc.lock, flags);
> +
> + ret = dma_cookie_status(chan, cookie, txstate);
> + if (ret == DMA_COMPLETE) {
> + if (ch->error)
> + ret = DMA_ERROR;
> + else
> + dma_set_residue(txstate, ch->remain);
> + }
> +
> + spin_unlock_irqrestore(&ch->vc.lock, flags);
> +
> + return ret;
> +}
[Severity: High]
There appear to be several issues with status and residue reporting in
ma35d1_tx_status() and ma35d1_pdma_interrupt():
1. ma35d1_tx_status() only calls dma_set_residue() when ret == DMA_COMPLETE.
For DMA_IN_PROGRESS transfers (including cyclic transfers, where cookie status
is always DMA_IN_PROGRESS), residue is never computed and remains 0.
2. Error and residue state are stored in per-channel fields (ch->error and
ch->remain) rather than per-descriptor, and ch->error is cleared in
ma35d1_desc_submit() whenever a new descriptor is prepared. Won't preparing a
new descriptor clear ch->error before a client queries the failed transfer's
cookie, or cause an abort to report DMA_ERROR for previously completed cookies
on that channel? Also, ch->desc->vd.tx_result is never updated on abort or
timeout, so callback_result clients will always see DMA_TRANS_NOERROR with 0
residue.
3. In ma35d1_pdma_interrupt(), timeout residue is computed as:
ch->remain = PDMA_GET_TXCNT(ch->base + PDMA_CH_OFFSET(ch->id)) *
ch->desc->width;
Because PDMA_TXCNT(cnt) programs the hardware with (cnt) - 1, doesn't reading
PDMA_GET_TXCNT() without adding 1 under-report the remaining bytes in the
current segment by one beat (ch->desc->width bytes), and ignore any remaining
segments when sg_len > 1?
[ ... ]
> +static irqreturn_t ma35d1_pdma_interrupt(int irq, void *devid)
> +{
> + int i;
> + struct ma35d1_pdmadev *pdmadev = devid;
> + struct ma35d1_chan *ch = &pdmadev->channels[pdmadev->nr_chans - 1];
> + unsigned int timeout_sts, done_sts, abort_sts;
> + unsigned long pending;
> +
> + /*
> + * Read the interrupt flags. Normally, only one is set per channel.
> + * Check and clear each flag separately.
> + */
> + done_sts = readl(ch->base + PDMA_TDSTS);
> + timeout_sts = readl(ch->base + PDMA_INTSTS);
> + abort_sts = readl(ch->base + PDMA_ABTSTS);
> +
> + pending = (done_sts | abort_sts | (timeout_sts >> 8)) &
> + GENMASK(pdmadev->nr_chans - 1, 0);
> +
> + for_each_set_bit(i, &pending, pdmadev->nr_chans) {
> + unsigned long flags;
> +
> + ch = &pdmadev->channels[i];
> +
> + spin_lock_irqsave(&ch->vc.lock, flags);
> +
> + if (done_sts & BIT(i)) {
> + writel(BIT(i), ch->base + PDMA_TDSTS);
> + if (ch->desc) {
> + ch->remain = 0;
> + if (ch->desc->cyclic)
> + vchan_cyclic_callback(&ch->desc->vd);
> + else
> + ma35d1_pdma_complete(ch);
> + }
> + }
> +
> + if (timeout_sts & BIT(i + 8)) {
> + if (ch->desc)
> + ch->remain = PDMA_GET_TXCNT(ch->base + PDMA_CH_OFFSET(ch->id)) *
> + ch->desc->width;
> + ma35d1_pdma_reg_clear(ch, PDMA_TOUTEN, BIT(i));
[Severity: High]
PDMA_TOUTEN is a controller-wide register shared across all channels, and
ma35d1_pdma_timeout() serializes read-modify-write updates to PDMA_TOUTEN
using ch->pdmadev->lock.
Here in ma35d1_pdma_interrupt(), ma35d1_pdma_reg_clear(ch, PDMA_TOUTEN, BIT(i))
performs a read-modify-write while holding only the per-channel ch->vc.lock.
Can this race with another CPU starting a descriptor on a different channel in
ma35d1_issue_pending() -> ma35d1_pdma_start_desc() -> ma35d1_pdma_timeout()
and clobber PDMA_TOUTEN?
> + writel(BIT(i + 8), ch->base + PDMA_INTSTS);
> + ma35d1_pdma_complete(ch);
> + }
> +
> + if (abort_sts & BIT(i)) {
> + writel(BIT(i), ch->base + PDMA_ABTSTS);
> + ch->error = true;
> + ma35d1_pdma_complete(ch);
> + }
> +
> + spin_unlock_irqrestore(&ch->vc.lock, flags);
> + }
> +
> + return IRQ_HANDLED;
> +}
[Severity: Critical]
In ma35d1_pdma_interrupt(), there are a few issues with how channel completion
and interrupt status are handled:
1. When a timeout interrupt fires (timeout_sts & BIT(i + 8)), the transfer has
not completed all beats and the hardware channel remains enabled in PDMA_CHCTL
with an active operating mode in PDMA_DSCT_CTL. Calling ma35d1_pdma_complete()
without disabling or resetting the hardware channel will complete and free
ch->desc (via vchan_complete() -> ma35d1_pdma_desc_free()) while the hardware
channel is still active. If another peripheral request arrives when no next
descriptor was queued, won't the controller DMA into the unmapped/freed client
buffer or fetch freed SG descriptors?
2. Because done_sts, timeout_sts, and abort_sts are handled in three sequential
if blocks that each call ma35d1_pdma_complete(ch) (which completes ch->desc and
immediately starts the next queued descriptor in ch->desc), if more than one
status bit is set for channel i in the same interrupt, won't the second block
immediately complete the newly started next descriptor (or free an active
cyclic descriptor)?
3. If pending is 0, should ma35d1_pdma_interrupt() return IRQ_NONE instead of
unconditionally returning IRQ_HANDLED?
> +
> +static int ma35d1_pdma_probe(struct platform_device *pdev)
> +{
> + int i, irq, nr_chans, ret;
> + struct clk *pdma_clk;
> + struct device *dev = &pdev->dev;
> + struct device_node *node = dev->of_node;
> + struct ma35d1_chan *ch;
> + struct ma35d1_pdmadev *pdmadev;
> + void __iomem *reg_base;
[ ... ]
> + ret = of_property_read_u32(node, "dma-channels", &nr_chans);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get dma-channels\n");
> +
> + if (nr_chans > PDMA_MAX_CHANS) {
> + dev_warn(dev, "dma-channels %d exceeds max %d, clamping\n",
> + nr_chans, PDMA_MAX_CHANS);
> + nr_chans = PDMA_MAX_CHANS;
> + }
[Severity: High]
In ma35d1_pdma_probe(), nr_chans is declared as a signed int and passed as
&nr_chans to of_property_read_u32(), which expects a u32 * (triggering a
-Wpointer-sign warning), and the bounds check only tests nr_chans >
PDMA_MAX_CHANS without rejecting nr_chans <= 0.
If dma-channels in the Device Tree is 0 (or has bit 31 set, making nr_chans
negative), devm_kzalloc() will allocate 0 elements for pdmadev->channels[],
causing out-of-bounds accesses in ma35d1_pdma_interrupt() at
&pdmadev->channels[pdmadev->nr_chans - 1] and in ma35d1_pdma_remove() at
pdmadev->channels[0].base. Should nr_chans be a u32 and checked for
nr_chans == 0?
[ ... ]
> + ret = devm_request_irq(dev, irq, ma35d1_pdma_interrupt, 0, pdev->name, pdmadev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to request IRQ\n");
> +
> + ret = dmaenginem_async_device_register(&pdmadev->ddev);
> + if (ret)
> + return ret;
> +
> + ret = of_dma_controller_register(node, ma35d1_of_xlate, pdmadev);
> + if (ret)
> + return dev_err_probe(dev, ret, "of_dma_controller_register failed\n");
> +
> + platform_set_drvdata(pdev, pdmadev);
> +
> + return 0;
> +}
> +
> +static void ma35d1_pdma_remove(struct platform_device *pdev)
> +{
> + struct ma35d1_pdmadev *pdmadev = platform_get_drvdata(pdev);
> + int i;
> +
> + of_dma_controller_free(pdev->dev.of_node);
> + writel(0, pdmadev->channels[0].base + PDMA_INTEN);
> +
> + for (i = 0; i < pdmadev->nr_chans; i++)
> + tasklet_kill(&pdmadev->channels[i].vc.task);
> +}
[Severity: High]
In ma35d1_pdma_probe(), the interrupt handler and dma_device are registered via
devm_request_irq() and dmaenginem_async_device_register(), so devres only
releases them after ma35d1_pdma_remove() returns.
During ma35d1_pdma_remove():
1. Active channels in PDMA_CHCTL and timeout interrupts in PDMA_TOUTIEN /
PDMA_TOUTEN are not disabled.
2. The IRQ is not freed or synchronized before tasklet_kill(), so an in-flight
ma35d1_pdma_interrupt() on another CPU (or a timeout interrupt) can call
vchan_cookie_complete() or vchan_cyclic_callback() and reschedule vc.task
after tasklet_kill() finishes.
3. Because pdmadev->ddev is still registered during ma35d1_pdma_remove(), a
concurrent client can still prepare descriptors and re-enable PDMA_INTEN in
ma35d1_pdma_setup_channel().
Could this allow vc.task to run after devres frees pdmadev? Also, if
of_dma_controller_register() fails in ma35d1_pdma_probe(), the channel
tasklets are not killed on error unwind.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261103022202.2139674-1-shanchun1218@gmail.com?part=2
next prev parent reply other threads:[~2026-11-03 2:43 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 2:22 [PATCH 0/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver Shan-Chun Hung
2026-11-03 2:22 ` [PATCH 1/2] dt-bindings: dma: nuvoton,ma35d1-pdma: Document MA35D1 PDMA controller Shan-Chun Hung
2026-11-03 2:36 ` sashiko-bot
2026-11-04 14:03 ` Krzysztof Kozlowski
2026-11-03 2:22 ` [PATCH 2/2] dmaengine: ma35d1-pdma: Add MA35D1 PDMA driver Shan-Chun Hung
2026-11-03 2:43 ` sashiko-bot [this message]
2026-11-04 19:30 ` Frank Li
2026-11-04 14:06 ` Krzysztof Kozlowski
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=sashiko-outbox-194466@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Frank.Li@kernel.org \
--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=shanchun1218@gmail.com \
--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.