From: sashiko-bot@kernel.org
To: "Tim Michals" <tcmichals@gmail.com>
Cc: linux-sunxi@lists.linux.dev, devicetree@vger.kernel.org,
robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v3 2/7] mailbox: sun55i: Add Allwinner A523 message box driver
Date: Sun, 01 Nov 2026 19:17:22 +0000 [thread overview]
Message-ID: <sashiko-outbox-192582@kernel.org> (raw)
In-Reply-To: <20261101185735.762559-3-tcmichals@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] mailbox: sun55i: TX IRQ race in sun55i_msgbox_shutdown() on shared route
- [High] mailbox: sun55i: UAF and race returning -EBUSY in sun55i_msgbox_send_data()
- [Medium] mailbox: sun55i: lock drop race enabling TX IRQ in sun55i_msgbox_send_data()
--
commit e3cf2bfaf2bd6f778a72431227fd527e0aef2173
Author: Tim Michals <tcmichals@gmail.com>
mailbox: sun55i: Add Allwinner A523 message box driver
This commit adds a mailbox controller driver for the Allwinner sun55i (A523)
message box, supporting 12 hardware FIFO channels between the Cortex-A55 host
and the CPUS, HiFi4 DSP, and XuanTie E906 processors with either polled or
write-IRQ TX completion.
> diff --git a/drivers/mailbox/sun55i-msgbox.c b/drivers/mailbox/sun55i-msgbox.c
> new file mode 100644
> index 0000000000000..1618c16219fc2
> --- /dev/null
> +++ b/drivers/mailbox/sun55i-msgbox.c
> @@ -0,0 +1,613 @@
[ ... ]
> +static void sun55i_msgbox_tx_irq_put(struct sun55i_msgbox *mbox, int local_n)
> +{
> + struct sun55i_tx_irq *tx = &mbox->tx_irqs[local_n];
> +
> + mutex_lock(&mbox->tx_irq_lock);
> + if (!--tx->users) {
> + free_irq(tx->irq, tx);
> + } else {
> + /* The line stays in use: let a running handler finish with us */
> + synchronize_irq(tx->irq);
> + }
> + mutex_unlock(&mbox->tx_irq_lock);
> +}
> +
> +static int sun55i_msgbox_send_data(struct mbox_chan *chan, void *data)
> +{
> + struct sun55i_msgbox *mbox = to_sun55i_msgbox(chan);
> + int n = chan - mbox->controller.chans;
> + int local_n, p, remote_id, remote_n;
> + unsigned long flags;
> + u32 msg = 0;
> + bool full;
> +
> + if (data)
> + memcpy(&msg, data, sizeof(msg));
> +
> + sun55i_msgbox_chan_to_route(n, &local_n, &p, &remote_id, &remote_n);
> +
> + /*
> + * Under the lock: in timer mode the poll timer may write a held
> + * message into the same FIFO from another CPU (last_tx_done()).
> + */
> + spin_lock_irqsave(&mbox->lock, flags);
> +
> + /*
> + * The core calls send_data() only with no request active, so a message
> + * still held here is one it gave up on (a blocking send timed out):
> + * drop it rather than send it after this newer one.
> + */
> + __clear_bit(n, &mbox->tx_held);
> +
> + /*
> + * Writing a full FIFO corrupts its count, so never do it. In write IRQ
> + * mode the IRQ armed below retries the message once a slot is free. In
> + * timer mode the core polls only a channel whose send succeeded, so
> + * hold the message and let last_tx_done() write it.
> + */
> + full = sun55i_msgbox_tx_count(mbox, remote_id, remote_n, p) >= SUN55I_FIFO_MAX;
> + if (!full) {
> + sun55i_msgbox_writel(msg, mbox->regs[remote_id] +
> + SUN55I_MSGBOX_MSG_FIFO(remote_n, p));
> + } else if (!mbox->tx_done_irq) {
> + mbox->tx_held_msg[n] = msg;
> + __set_bit(n, &mbox->tx_held);
> + }
> +
> + spin_unlock_irqrestore(&mbox->lock, flags);
[Severity: Medium]
Can dropping mbox->lock here before calling sun55i_msgbox_tx_irq_enable()
cause a premature TX completion when a previous blocking send timed out?
If an earlier blocking send timed out in mbox_send_message() ->
tx_tick(chan, -ETIME), SUN55I_MSGBOX_WR_IRQ_EN_BIT(p) stays enabled in the
remote bank. When the remote processor later reads a message and frees a slot,
SUN55I_MSGBOX_WR_IRQ_PEND_BIT(p) is latched in
SUN55I_MSGBOX_WRITE_IRQ_STATUS(remote_n).
When the next sun55i_msgbox_send_data() writes to SUN55I_MSGBOX_MSG_FIFO and
fills the FIFO to SUN55I_FIFO_MAX, releasing mbox->lock before clearing the
latched status bit in sun55i_msgbox_tx_irq_enable() opens a window where
sun55i_msgbox_tx_irq() on another CPU can acquire mbox->lock, see both
WR_IRQ_EN_BIT(p) and the stale latched WR_IRQ_PEND_BIT(p) set, and call
mbox_chan_txdone() to complete the new message while the FIFO is still full.
Should the write IRQ status clear and enable in sun55i_msgbox_tx_irq_enable()
be performed under the same mbox->lock critical section as the FIFO write?
> +
> + /*
> + * A kick is a doorbell, the data is in shared memory: it is done once
> + * the FIFO accepted it and has room for the next one. The write IRQ
> + * reports exactly that, at once or when the receiver reads a message.
> + */
> + if (mbox->tx_done_irq) {
> + sun55i_msgbox_tx_irq_enable(mbox, remote_id, remote_n, p, true);
> + return full ? -EBUSY : 0;
> + }
[Severity: High]
What happens in write-IRQ mode (mbox->tx_done_irq) when the hardware FIFO is
full and sun55i_msgbox_send_data() arms the write IRQ and returns -EBUSY?
In drivers/mailbox/mailbox.c, msg_submit() only sets chan->active_req = data
and decrements chan->msg_count when send_data() returns 0:
drivers/mailbox/mailbox.c:msg_submit() {
...
if (chan->cl->tx_prepare)
chan->cl->tx_prepare(chan->cl, data);
/* Try to submit a message to the MBOX controller */
err = chan->mbox->ops->send_data(chan, data);
if (!err) {
chan->active_req = data;
chan->msg_count--;
}
...
}
When send_data() returns -EBUSY, chan->active_req remains NULL and the
caller's data pointer stays in chan->msg_data[]. If a blocking send in
mbox_send_message() then times out in wait_for_completion_timeout(), it calls
tx_tick(chan, -ETIME):
drivers/mailbox/mailbox.c:tx_tick() {
...
spin_lock_irqsave(&chan->lock, flags);
mssg = chan->active_req;
chan->active_req = NULL;
spin_unlock_irqrestore(&chan->lock, flags);
/* Submit next message */
msg_submit(chan);
if (!mssg)
return;
...
}
Because chan->active_req is NULL, tx_tick() does not remove the timed-out
message from chan->msg_data[]. Instead, mbox_send_message() returns -ETIME to
the caller while its message pointer (often stack-allocated in blocking mode)
is still queued in chan->msg_data[] with the write IRQ enabled. When the
remote processor later drains a FIFO slot, sun55i_msgbox_tx_irq() ->
mbox_chan_txdone() -> tx_tick() -> msg_submit() will call cl->tx_prepare(),
sun55i_msgbox_send_data() (which runs memcpy(&msg, data, sizeof(msg))), and
cl->tx_done() on the dangling pointer.
Even without a timeout, while chan->active_req is NULL and the first message
waits in chan->msg_data[] for the write IRQ, a second mbox_send_message() call
on another CPU can run msg_submit() just as a FIFO slot frees: CPU 0 sees
chan->active_req == NULL, calls cl->tx_prepare() a second time on the first
message, writes it into the single free FIFO slot (refilling the FIFO), and
sets chan->active_req. The concurrent sun55i_msgbox_tx_irq() on CPU 1 then
calls mbox_chan_txdone() and prematurely completes the message while the FIFO
is full.
Should write-IRQ mode also buffer the message in mbox->tx_held_msg[n] and
return 0 when the FIFO is full, writing the held message from
sun55i_msgbox_tx_irq() once a slot is free?
[ ... ]
> +static void sun55i_msgbox_shutdown(struct mbox_chan *chan)
> +{
> + struct sun55i_msgbox *mbox = to_sun55i_msgbox(chan);
> + void __iomem *base = mbox->regs[SUN55I_PROC_ARM];
> + int n = chan - mbox->controller.chans;
> + int local_n, p, remote_id, remote_n;
> + u32 stale[SUN55I_FIFO_MAX];
> + unsigned long flags;
> + u32 val;
> +
> + sun55i_msgbox_chan_to_route(n, &local_n, &p, &remote_id, &remote_n);
> +
> + /* Disable the receive IRQ; a message held for a full FIFO is dropped */
> + spin_lock_irqsave(&mbox->lock, flags);
> + val = sun55i_msgbox_readl(base + SUN55I_MSGBOX_READ_IRQ_ENABLE(local_n));
> + val &= ~SUN55I_MSGBOX_RD_IRQ_EN_BIT(p);
> + sun55i_msgbox_writel(val, base + SUN55I_MSGBOX_READ_IRQ_ENABLE(local_n));
> + __clear_bit(n, &mbox->tx_held);
> + spin_unlock_irqrestore(&mbox->lock, flags);
> +
> + /* The handler delivers outside the lock: let it finish with this channel */
> + synchronize_irq(mbox->irq);
> +
> + /* Release the line only if this channel's startup() got it */
> + if (test_and_clear_bit(n, &mbox->tx_irq_held)) {
> + sun55i_msgbox_tx_irq_enable(mbox, remote_id, remote_n, p, false);
> + sun55i_msgbox_tx_irq_put(mbox, local_n);
> + /*
> + * A TX-done handler that was already running submits the next
> + * message through send_data(), which arms the IRQ again. It has
> + * finished now (free_irq() or synchronize_irq() above), so
> + * disarm once more.
> + */
> + sun55i_msgbox_tx_irq_enable(mbox, remote_id, remote_n, p, false);
> + }
[Severity: High]
Can a second sun55i_msgbox_tx_irq() invocation still race with channel
teardown here when another channel on the same route remains active
(tx->users > 1)?
In mbox_free_channel(), chan->mbox->ops->shutdown(chan) runs before chan->cl
is cleared, and queued messages may still be present in chan->msg_data[]. When
tx->users > 1 on local_n, sun55i_msgbox_tx_irq_put() only calls
synchronize_irq(tx->irq) and leaves tx->irq enabled at the interrupt
controller.
If an in-flight sun55i_msgbox_tx_irq() was already past mbox->lock on another
CPU, it calls:
sun55i_msgbox_tx_irq()
mbox_chan_txdone()
tx_tick()
msg_submit()
sun55i_msgbox_send_data()
sun55i_msgbox_tx_irq_enable(mbox, remote_id, remote_n, p, true)
Because the write IRQ is level-triggered whenever at least one FIFO slot is
free, re-enabling SUN55I_MSGBOX_WR_IRQ_EN_BIT(p) in sun55i_msgbox_send_data()
can immediately assert tx->irq again as soon as the first handler returns.
Waiters in sun55i_msgbox_tx_irq_put()'s synchronize_irq(tx->irq) only wait for
that first handler invocation to complete, so a second sun55i_msgbox_tx_irq()
can fire before the second sun55i_msgbox_tx_irq_enable(..., false) runs and
execute mbox_chan_txdone() -> tx_tick() concurrently with mbox_free_channel()
setting chan->cl = NULL (or re-arm the write IRQ again if more messages were
queued).
Would checking test_bit(n, &mbox->tx_irq_held) under mbox->lock before arming
the write IRQ in sun55i_msgbox_send_data() avoid re-enabling the IRQ after
sun55i_msgbox_shutdown() has begun?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261101185735.762559-1-tcmichals@gmail.com?part=2
next prev parent reply other threads:[~2026-11-01 19:17 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-01 18:57 [PATCH v3 0/7] Add Allwinner A523 message box and XuanTie E906 remoteproc support Tim Michals
2026-11-01 18:57 ` [PATCH v3 1/7] dt-bindings: mailbox: Add Allwinner A523 message box Tim Michals
2026-11-01 19:06 ` sashiko-bot
2026-11-02 10:17 ` Krzysztof Kozlowski
2026-11-01 18:57 ` [PATCH v3 2/7] mailbox: sun55i: Add Allwinner A523 message box driver Tim Michals
2026-11-01 19:17 ` sashiko-bot [this message]
2026-11-01 18:57 ` [PATCH v3 3/7] dt-bindings: remoteproc: Add Allwinner A523 RISC-V remote processor Tim Michals
2026-11-02 10:19 ` Krzysztof Kozlowski
2026-11-01 18:57 ` [PATCH v3 4/7] remoteproc: sunxi: Add Allwinner A523 XuanTie E906 driver Tim Michals
2026-11-01 19:24 ` sashiko-bot
2026-11-01 18:57 ` [PATCH v3 5/7] arm64: dts: allwinner: a523: Add message box and remoteproc nodes Tim Michals
2026-11-01 18:57 ` [PATCH v3 6/7] mailbox: sun55i: Add KUnit tests Tim Michals
2026-11-01 18:57 ` [PATCH v3 7/7] remoteproc: sunxi: " Tim Michals
2026-11-01 19:13 ` sashiko-bot
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-192582@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=linux-sunxi@lists.linux.dev \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=tcmichals@gmail.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