[v2,2/7] mailbox: sun55i: add Allwinner sun55i/sun60i 4-port Message Box driver

Message ID 20260927002021.797069-3-tcmichals@gmail.com (mailing list archive)
State New
Headers
Series remoteproc: sunxi: Add Allwinner XuanTie E907 RemoteProc and Message Box support |

Commit Message

Tim Michals Sept. 27, 2026, 12:20 a.m. UTC
Add driver for the 4-port hardware Message Box controller found on
Allwinner A523, A527, T527 (sun55i) and A733 (sun60i) SoCs.

The controller acts as a hardware crossbar connecting four heterogeneous
processor cores (ARM Cortex-A55, Tensilica HiFi4 DSP, CPUS, and XuanTie
E907 RISC-V) across 12 logical channels with 8-entry hardware message FIFOs.

Signed-off-by: Tim Michals <tcmichals@gmail.com>
---
 drivers/mailbox/Kconfig         |  10 +
 drivers/mailbox/Makefile        |   1 +
 drivers/mailbox/sun55i-msgbox.c | 375 ++++++++++++++++++++++++++++++++
 drivers/mailbox/sun55i-msgbox.h |  58 +++++
 4 files changed, 444 insertions(+)
 create mode 100644 drivers/mailbox/sun55i-msgbox.c
 create mode 100644 drivers/mailbox/sun55i-msgbox.h
  

Comments

sashiko-bot@kernel.org Sept. 27, 2026, 12:33 a.m. UTC | #1
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] mailbox: sun55i: out-of-bounds array write in probe due to unbounded DT irq_cnt
- [High] mailbox: sun55i: incorrect last_tx_done polling condition breaks TX semantics
- [High] mailbox: sun55i: NULL pointer dereference in IRQ handler during teardown
- [High] mailbox: sun55i: lockless multi-IRQ concurrency causes FIFO underflow
--

commit d294abbd2180fd12203dc082ff7e6d574533c041
Author: Tim Michals <tcmichals@gmail.com>

mailbox: sun55i: add Allwinner sun55i/sun60i 4-port Message Box driver

This commit adds a mailbox driver for the 4-port hardware Message Box
controller found on Allwinner sun55i and sun60i SoCs. It implements hardware
FIFO communication across 12 logical channels for multiple heterogeneous cores.

> diff --git a/drivers/mailbox/sun55i-msgbox.c b/drivers/mailbox/sun55i-msgbox.c
> new file mode 100644
> index 0000000000000..928e52e08bad4
> --- /dev/null
> +++ b/drivers/mailbox/sun55i-msgbox.c
> @@ -0,0 +1,375 @@
[ ... ]
> +irqreturn_t sun55i_msgbox_irq(int irq, void *dev_id)
> +{
> +	struct sun55i_msgbox *mbox = dev_id;
> +	irqreturn_t ret = IRQ_NONE;
> +	int i, local_n, p, chan_idx;
> +
> +	for (local_n = 0; local_n < SUN55I_NUM_ROUTES; local_n++) {
> +		void __iomem *local_base = mbox->regs[0];

[Severity: High]
Does this lockless loop allow multiple concurrent handlers on different CPUs to
inspect and process the same hardware channels simultaneously?

Since sun55i_msgbox_probe() registers the exact same handler for multiple IRQs
using request_irq(), if two remote processors send messages concurrently, both
IRQs fire, executing the handler on different CPUs simultaneously.

Both CPUs would see the same pending bit and race to drain the exact same
channel, leading to a TOCTOU race where the second CPU reads from an empty FIFO,
resulting in a hardware underflow and delivery of a corrupt message.

> +		u32 en, stat, pending;
> +
> +		en = readl(local_base + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
> +		stat = readl(local_base + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n));
> +		pending = en & stat;
[ ... ]
> +static bool sun55i_msgbox_last_tx_done(struct mbox_chan *chan)
> +{
> +	struct sun55i_msgbox *mbox = to_sun55i_msgbox(chan);
> +	int n = chan - mbox->controller.chans;
> +	int local_n, p, remote_id, remote_n;
> +	u32 count;
> +
> +	sun55i_chan_to_route(n, &local_n, &p, &remote_id, &remote_n);
> +
> +	count = readl(mbox->regs[remote_id] + SUNXI_MSGBOX_MSG_STATUS(remote_n, p)) & MSG_NUM_MASK;
> +	return count < SUN55I_FIFO_MAX;
> +}

[Severity: High]
Does returning true when the FIFO is not full falsely signal transmission
completion to the polling framework?

The mailbox framework polls last_tx_done() to know when a message has been
consumed by the remote processor. Returning true while the message is still in
the FIFO (count < SUN55I_FIFO_MAX) falsely signals completion.

The framework will then prematurely send subsequent messages, leading to a
hardware FIFO overflow and dropped IPC messages.

[ ... ]
> +static int sun55i_msgbox_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	irq_cnt = platform_irq_count(pdev);
> +	if (irq_cnt < 0) {
> +		ret = irq_cnt;
> +		goto err_assert_reset;
> +	}
> +
> +	for (i = 0; i < irq_cnt; i++) {
> +		int irq = platform_get_irq(pdev, i);
> +
> +		if (irq < 0) {
> +			ret = irq;
> +			goto err_free_irqs;
> +		}
> +
> +		ret = request_irq(irq, sun55i_msgbox_irq,
> +				  IRQF_SHARED, dev_name(dev), mbox);
> +		if (ret) {
> +			dev_err(dev, "failed to request irq %d: %d\n", irq, ret);
> +			goto err_free_irqs;
> +		}
> +		mbox->irqs[i] = irq;
> +		mbox->num_irqs = i + 1;
> +	}

[Severity: High]
Can this write overflow the mbox->irqs array if irq_cnt exceeds 4?

The driver retrieves irq_cnt directly from the device tree via
platform_irq_count() without bounds checking. If a device tree provides 5 or
more interrupts, the unconditional loop will write out of bounds of the
fixed-size 4-element mbox->irqs array, corrupting memory of adjacent fields in
the sun55i_msgbox structure.

[ ... ]
> +static void sun55i_msgbox_remove(struct platform_device *pdev)
> +{
> +	struct sun55i_msgbox *mbox = platform_get_drvdata(pdev);
> +	int local_n, i;
> +
> +	mbox_controller_unregister(&mbox->controller);
> +
> +	/* Mask hardware interrupts and free IRQs before asserting reset and disabling clock */

[Severity: High]
Does unregistering the controller here before masking hardware interrupts and
freeing IRQs leave a race window open for a NULL pointer dereference?

During device removal, mbox_controller_unregister() calls mbox_free_channel(),
which invokes sun55i_msgbox_shutdown() and clears the chan->cl pointer.

While sun55i_msgbox_shutdown() masks the interrupt via MMIO, it fails to
synchronize the kernel IRQ subsystem via synchronize_irq().

If an interrupt handler is already running concurrently on another CPU, it will
proceed to read the FIFO and invoke mbox_chan_received_data() from
sun55i_msgbox_irq(), which dereferences the now-NULL chan->cl->rx_callback
pointer.

> +	for (local_n = 0; local_n < SUN55I_NUM_ROUTES; local_n++)
> +		writel(0, mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
> +
> +	for (i = 0; i < mbox->num_irqs; i++)
> +		free_irq(mbox->irqs[i], mbox);
> +
> +	reset_control_assert(mbox->reset);
> +	clk_disable_unprepare(mbox->clk);
> +}
  

Patch

diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index ab869769e278..9067f5161276 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -332,6 +332,16 @@  config SUN6I_MSGBOX
 	  various Allwinner SoCs. This mailbox is used for communication
 	  between the application CPUs and the power management coprocessor.
 
+config SUN55I_MSGBOX
+	tristate "Allwinner sun55i/sun60i 4-port Message Box"
+	depends on ARCH_SUNXI || COMPILE_TEST
+	default ARCH_SUNXI
+	help
+	  Mailbox implementation for the 4-port hardware message box present in
+	  Allwinner A523/A527/T527 (sun55i) and A733 (sun60i) SoCs. This mailbox
+	  provides 12 hardware FIFO channels for communication between the
+	  Cortex-A55 host, CPUS, HiFi4 DSP, and XuanTie E907 RISC-V coprocessors.
+
 config SPRD_MBOX
 	tristate "Spreadtrum Mailbox"
 	depends on ARCH_SPRD || COMPILE_TEST
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index 45dc59bb3e7f..aa17f9d0acd4 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -70,6 +70,7 @@  obj-$(CONFIG_MTK_VCP_MBOX)	+= mtk-vcp-mailbox.o
 obj-$(CONFIG_ZYNQMP_IPI_MBOX)	+= zynqmp-ipi-mailbox.o
 
 obj-$(CONFIG_SUN6I_MSGBOX)	+= sun6i-msgbox.o
+obj-$(CONFIG_SUN55I_MSGBOX)	+= sun55i-msgbox.o
 
 obj-$(CONFIG_SPRD_MBOX)		+= sprd-mailbox.o
 
diff --git a/drivers/mailbox/sun55i-msgbox.c b/drivers/mailbox/sun55i-msgbox.c
new file mode 100644
index 000000000000..928e52e08bad
--- /dev/null
+++ b/drivers/mailbox/sun55i-msgbox.c
@@ -0,0 +1,375 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Allwinner sun55i/sun60i 4-Port Hardware Message Box Driver
+ *
+ * Copyright (C) 2026 Tim Michals <tcmichals@gmail.com>
+ * Based on vendor sunxi-msgbox driver by Allwinner Technology Co., Ltd.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+
+#include "sun55i-msgbox.h"
+
+/*
+ * Hardware routing table for Cortex-A55 host (local_id = 0):
+ *   local_n = 0 -> CPUS (remote_id = SUN55I_PROC_CPUS, remote_n = 0) -> Channels 0..3
+ *   local_n = 1 -> DSP  (remote_id = SUN55I_PROC_DSP,  remote_n = 0) -> Channels 4..7
+ *   local_n = 2 -> RV   (remote_id = SUN55I_PROC_RV,   remote_n = 2) -> Channels 8..11
+ */
+const struct sun55i_route sun55i_msgbox_arm_routes[SUN55I_NUM_ROUTES] = {
+	[0] = { .remote_id = SUN55I_PROC_CPUS, .remote_n = 0 },
+	[1] = { .remote_id = SUN55I_PROC_DSP,  .remote_n = 0 },
+	[2] = { .remote_id = SUN55I_PROC_RV,   .remote_n = 2 },
+};
+
+#if IS_ENABLED(CONFIG_SUN55I_MSGBOX_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sun55i_msgbox_arm_routes);
+#endif
+
+static inline struct sun55i_msgbox *to_sun55i_msgbox(struct mbox_chan *chan)
+{
+	return chan->con_priv;
+}
+
+void sun55i_chan_to_route(int chan_idx, int *local_n, int *p,
+			  int *remote_id, int *remote_n)
+{
+	if (chan_idx < 0 || chan_idx >= SUN55I_NUM_CHANS) {
+		*local_n = 0;
+		*p = 0;
+		*remote_id = 0;
+		*remote_n = 0;
+		return;
+	}
+	*local_n = chan_idx / SUN55I_CHANS_PER_PROC;
+	*p = chan_idx % SUN55I_CHANS_PER_PROC;
+	*remote_id = sun55i_msgbox_arm_routes[*local_n].remote_id;
+	*remote_n = sun55i_msgbox_arm_routes[*local_n].remote_n;
+}
+
+#if IS_ENABLED(CONFIG_SUN55I_MSGBOX_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sun55i_chan_to_route);
+#endif
+
+irqreturn_t sun55i_msgbox_irq(int irq, void *dev_id)
+{
+	struct sun55i_msgbox *mbox = dev_id;
+	irqreturn_t ret = IRQ_NONE;
+	int i, local_n, p, chan_idx;
+
+	for (local_n = 0; local_n < SUN55I_NUM_ROUTES; local_n++) {
+		void __iomem *local_base = mbox->regs[0];
+		u32 en, stat, pending;
+
+		en = readl(local_base + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
+		stat = readl(local_base + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n));
+		pending = en & stat;
+
+		if (!pending)
+			continue;
+
+		for (p = 0; p < SUN55I_CHANS_PER_PROC; p++) {
+			if (!(pending & RD_IRQ_PEND_BIT(p)))
+				continue;
+
+			chan_idx = local_n * SUN55I_CHANS_PER_PROC + p;
+
+			/*
+			 * Clear pending status BEFORE draining the FIFO to seal
+			 * the TOCTOU race: any new message arriving while draining
+			 * will re-assert the hardware pending bit, guaranteeing
+			 * a subsequent interrupt and preventing blackholed IPC data.
+			 */
+			writel(RD_IRQ_PEND_BIT(p),
+			       local_base + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n));
+
+			/* Cap drain at FIFO_MAX to prevent CPU lockup from a runaway remote */
+			for (i = 0; i < SUN55I_FIFO_MAX; i++) {
+				u32 msg;
+
+				if (!(readl(local_base +
+					    SUNXI_MSGBOX_MSG_STATUS(local_n, p)) & MSG_NUM_MASK))
+					break;
+				msg = readl(local_base + SUNXI_MSGBOX_MSG_FIFO(local_n, p));
+				mbox_chan_received_data(&mbox->controller.chans[chan_idx], &msg);
+			}
+
+			ret = IRQ_HANDLED;
+		}
+	}
+
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_SUN55I_MSGBOX_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sun55i_msgbox_irq);
+#endif
+
+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;
+	u32 msg = 0;
+
+	if (data)
+		memcpy(&msg, data, sizeof(msg));
+
+	sun55i_chan_to_route(n, &local_n, &p, &remote_id, &remote_n);
+
+	writel(msg, mbox->regs[remote_id] + SUNXI_MSGBOX_MSG_FIFO(remote_n, p));
+	return 0;
+}
+
+static int sun55i_msgbox_startup(struct mbox_chan *chan)
+{
+	struct sun55i_msgbox *mbox = to_sun55i_msgbox(chan);
+	int n = chan - mbox->controller.chans;
+	int i, local_n, p, remote_id, remote_n;
+	unsigned long flags;
+	u32 val;
+
+	sun55i_chan_to_route(n, &local_n, &p, &remote_id, &remote_n);
+
+	/* Flush any stale receive data (bounded to FIFO_MAX) */
+	for (i = 0; i < SUN55I_FIFO_MAX; i++) {
+		if (!(readl(mbox->regs[0] + SUNXI_MSGBOX_MSG_STATUS(local_n, p)) & MSG_NUM_MASK))
+			break;
+		readl(mbox->regs[0] + SUNXI_MSGBOX_MSG_FIFO(local_n, p));
+	}
+
+	/* Clear pending status */
+	writel(RD_IRQ_PEND_BIT(p),
+	       mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n));
+
+	/* Enable receive IRQ */
+	spin_lock_irqsave(&mbox->lock, flags);
+	val = readl(mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
+	val |= RD_IRQ_EN_BIT(p);
+	writel(val, mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
+	spin_unlock_irqrestore(&mbox->lock, flags);
+
+	return 0;
+}
+
+static void sun55i_msgbox_shutdown(struct mbox_chan *chan)
+{
+	struct sun55i_msgbox *mbox = to_sun55i_msgbox(chan);
+	int n = chan - mbox->controller.chans;
+	int i, local_n, p, remote_id, remote_n;
+	unsigned long flags;
+	u32 val;
+
+	sun55i_chan_to_route(n, &local_n, &p, &remote_id, &remote_n);
+
+	/* Disable receive IRQ */
+	spin_lock_irqsave(&mbox->lock, flags);
+	val = readl(mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
+	val &= ~RD_IRQ_EN_BIT(p);
+	writel(val, mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
+	spin_unlock_irqrestore(&mbox->lock, flags);
+
+	/* Clear pending status and flush (bounded to FIFO_MAX) */
+	writel(RD_IRQ_PEND_BIT(p),
+	       mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n));
+	for (i = 0; i < SUN55I_FIFO_MAX; i++) {
+		if (!(readl(mbox->regs[0] + SUNXI_MSGBOX_MSG_STATUS(local_n, p)) & MSG_NUM_MASK))
+			break;
+		readl(mbox->regs[0] + SUNXI_MSGBOX_MSG_FIFO(local_n, p));
+	}
+}
+
+static bool sun55i_msgbox_last_tx_done(struct mbox_chan *chan)
+{
+	struct sun55i_msgbox *mbox = to_sun55i_msgbox(chan);
+	int n = chan - mbox->controller.chans;
+	int local_n, p, remote_id, remote_n;
+	u32 count;
+
+	sun55i_chan_to_route(n, &local_n, &p, &remote_id, &remote_n);
+
+	count = readl(mbox->regs[remote_id] + SUNXI_MSGBOX_MSG_STATUS(remote_n, p)) & MSG_NUM_MASK;
+	return count < SUN55I_FIFO_MAX;
+}
+
+static bool sun55i_msgbox_peek_data(struct mbox_chan *chan)
+{
+	struct sun55i_msgbox *mbox = to_sun55i_msgbox(chan);
+	int n = chan - mbox->controller.chans;
+	int local_n, p, remote_id, remote_n;
+	u32 count;
+
+	sun55i_chan_to_route(n, &local_n, &p, &remote_id, &remote_n);
+
+	count = readl(mbox->regs[0] + SUNXI_MSGBOX_MSG_STATUS(local_n, p)) & MSG_NUM_MASK;
+	return count > 0;
+}
+
+const struct mbox_chan_ops sun55i_msgbox_chan_ops = {
+	.send_data    = sun55i_msgbox_send_data,
+	.startup      = sun55i_msgbox_startup,
+	.shutdown     = sun55i_msgbox_shutdown,
+	.last_tx_done = sun55i_msgbox_last_tx_done,
+	.peek_data    = sun55i_msgbox_peek_data,
+};
+
+#if IS_ENABLED(CONFIG_SUN55I_MSGBOX_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sun55i_msgbox_chan_ops);
+#endif
+
+static int sun55i_msgbox_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mbox_chan *chans;
+	struct sun55i_msgbox *mbox;
+	int i, ret, irq_cnt, local_n;
+
+	mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
+	if (!mbox)
+		return -ENOMEM;
+
+	spin_lock_init(&mbox->lock);
+
+	/* Allocate channels early before touching any hardware */
+	chans = devm_kcalloc(dev, SUN55I_NUM_CHANS, sizeof(*chans), GFP_KERNEL);
+	if (!chans)
+		return -ENOMEM;
+
+	for (i = 0; i < SUN55I_NUM_CHANS; i++)
+		chans[i].con_priv = mbox;
+
+	for (i = 0; i < SUN55I_MAX_PROCESSORS; i++) {
+		mbox->regs[i] = devm_platform_ioremap_resource(pdev, i);
+		if (IS_ERR(mbox->regs[i]))
+			return dev_err_probe(dev, PTR_ERR(mbox->regs[i]),
+					     "failed to map resource %d\n", i);
+	}
+
+	mbox->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(mbox->clk))
+		return dev_err_probe(dev, PTR_ERR(mbox->clk), "failed to get clock\n");
+
+	ret = clk_prepare_enable(mbox->clk);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable clock\n");
+
+	mbox->reset = devm_reset_control_get_optional_shared(dev, NULL);
+	if (IS_ERR(mbox->reset)) {
+		ret = PTR_ERR(mbox->reset);
+		goto err_disable_clk;
+	}
+
+	ret = reset_control_deassert(mbox->reset);
+	if (ret)
+		goto err_disable_clk;
+
+	/* Disable all read IRQs and clear status */
+	for (local_n = 0; local_n < SUN55I_NUM_ROUTES; local_n++) {
+		writel(0, mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
+		writel(U32_MAX, mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n));
+	}
+
+	irq_cnt = platform_irq_count(pdev);
+	if (irq_cnt < 0) {
+		ret = irq_cnt;
+		goto err_assert_reset;
+	}
+
+	for (i = 0; i < irq_cnt; i++) {
+		int irq = platform_get_irq(pdev, i);
+
+		if (irq < 0) {
+			ret = irq;
+			goto err_free_irqs;
+		}
+
+		ret = request_irq(irq, sun55i_msgbox_irq,
+				  IRQF_SHARED, dev_name(dev), mbox);
+		if (ret) {
+			dev_err(dev, "failed to request irq %d: %d\n", irq, ret);
+			goto err_free_irqs;
+		}
+		mbox->irqs[i] = irq;
+		mbox->num_irqs = i + 1;
+	}
+
+	mbox->controller.dev           = dev;
+	mbox->controller.ops           = &sun55i_msgbox_chan_ops;
+	mbox->controller.chans         = chans;
+	mbox->controller.num_chans     = SUN55I_NUM_CHANS;
+	mbox->controller.txdone_irq    = false;
+	mbox->controller.txdone_poll   = true;
+	mbox->controller.txpoll_period = 1;
+
+	platform_set_drvdata(pdev, mbox);
+
+	ret = mbox_controller_register(&mbox->controller);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to register controller\n");
+		goto err_free_irqs;
+	}
+
+	return 0;
+
+err_free_irqs:
+	/* Mask all hardware read IRQs and free registered IRQs before cutting clocks */
+	for (local_n = 0; local_n < SUN55I_NUM_ROUTES; local_n++)
+		writel(0, mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
+	for (i = 0; i < mbox->num_irqs; i++)
+		free_irq(mbox->irqs[i], mbox);
+err_assert_reset:
+	reset_control_assert(mbox->reset);
+err_disable_clk:
+	clk_disable_unprepare(mbox->clk);
+	return ret;
+}
+
+static void sun55i_msgbox_remove(struct platform_device *pdev)
+{
+	struct sun55i_msgbox *mbox = platform_get_drvdata(pdev);
+	int local_n, i;
+
+	mbox_controller_unregister(&mbox->controller);
+
+	/* Mask hardware interrupts and free IRQs before asserting reset and disabling clock */
+	for (local_n = 0; local_n < SUN55I_NUM_ROUTES; local_n++)
+		writel(0, mbox->regs[0] + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n));
+
+	for (i = 0; i < mbox->num_irqs; i++)
+		free_irq(mbox->irqs[i], mbox);
+
+	reset_control_assert(mbox->reset);
+	clk_disable_unprepare(mbox->clk);
+}
+
+static const struct of_device_id sun55i_msgbox_of_match[] = {
+	{ .compatible = "allwinner,sun55i-a523-msgbox" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sun55i_msgbox_of_match);
+
+static struct platform_driver sun55i_msgbox_driver = {
+	.driver = {
+		.name = "sun55i-msgbox",
+		.of_match_table = sun55i_msgbox_of_match,
+	},
+	.probe = sun55i_msgbox_probe,
+	.remove = sun55i_msgbox_remove,
+};
+module_platform_driver(sun55i_msgbox_driver);
+
+MODULE_AUTHOR("Tim Michals <tcmichals@gmail.com>");
+MODULE_DESCRIPTION("Allwinner sun55i/sun60i 4-Port Message Box Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mailbox/sun55i-msgbox.h b/drivers/mailbox/sun55i-msgbox.h
new file mode 100644
index 000000000000..2ab79a9149c0
--- /dev/null
+++ b/drivers/mailbox/sun55i-msgbox.h
@@ -0,0 +1,58 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SUN55I_MSGBOX_H_
+#define _SUN55I_MSGBOX_H_
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/mailbox_controller.h>
+#include <linux/spinlock.h>
+
+#define SUN55I_PROC_ARM			0
+#define SUN55I_PROC_DSP			1
+#define SUN55I_PROC_CPUS		2
+#define SUN55I_PROC_RV			3
+
+#define SUN55I_MAX_PROCESSORS		4
+#define SUN55I_CHANS_PER_PROC		4
+#define SUN55I_NUM_ROUTES		(SUN55I_MAX_PROCESSORS - 1)
+#define SUN55I_NUM_CHANS		(SUN55I_NUM_ROUTES * SUN55I_CHANS_PER_PROC)
+#define SUN55I_FIFO_MAX			8
+
+#define SUNXI_MSGBOX_OFFSET(n)			(0x100 * (n))
+#define SUNXI_MSGBOX_READ_IRQ_ENABLE(n)		(0x020 + SUNXI_MSGBOX_OFFSET(n))
+#define SUNXI_MSGBOX_READ_IRQ_STATUS(n)		(0x024 + SUNXI_MSGBOX_OFFSET(n))
+#define SUNXI_MSGBOX_WRITE_IRQ_ENABLE(n)	(0x030 + SUNXI_MSGBOX_OFFSET(n))
+#define SUNXI_MSGBOX_WRITE_IRQ_STATUS(n)	(0x034 + SUNXI_MSGBOX_OFFSET(n))
+#define SUNXI_MSGBOX_FIFO_STATUS(n, p)		(0x050 + SUNXI_MSGBOX_OFFSET(n) + 0x4 * (p))
+#define SUNXI_MSGBOX_MSG_STATUS(n, p)		(0x060 + SUNXI_MSGBOX_OFFSET(n) + 0x4 * (p))
+#define SUNXI_MSGBOX_MSG_FIFO(n, p)		(0x070 + SUNXI_MSGBOX_OFFSET(n) + 0x4 * (p))
+
+#define RD_IRQ_EN_BIT(p)			BIT((p) * 2)
+#define RD_IRQ_PEND_BIT(p)			BIT((p) * 2)
+#define MSG_NUM_MASK				GENMASK(3, 0)
+
+struct sun55i_route {
+	u8 remote_id;
+	u8 remote_n;
+};
+
+struct sun55i_msgbox {
+	struct mbox_controller controller;
+	void __iomem *regs[SUN55I_MAX_PROCESSORS];
+	struct clk *clk;
+	struct reset_control *reset;
+	int irqs[SUN55I_MAX_PROCESSORS];
+	int num_irqs;
+	/* Protects concurrent MMIO register access */
+	spinlock_t lock;
+};
+
+extern const struct sun55i_route sun55i_msgbox_arm_routes[SUN55I_NUM_ROUTES];
+extern const struct mbox_chan_ops sun55i_msgbox_chan_ops;
+
+void sun55i_chan_to_route(int chan_idx, int *local_n, int *p,
+			  int *remote_id, int *remote_n);
+irqreturn_t sun55i_msgbox_irq(int irq, void *dev_id);
+
+#endif /* _SUN55I_MSGBOX_H_ */