Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] remoteproc: sunxi: unbalanced disable_irq via crash_irq_enabled race
- [High] remoteproc: sunxi: race on kick_msg and immediate txdone corrupts mailbox
- [High] remoteproc: sunxi: double mapping of DT regions with conflicting attributes
- [High] remoteproc: sunxi: premature core execution due to broken reset fallback logic
- [High] remoteproc: sunxi: UAF of virtqueues due to late mailbox interrupts
- [High] remoteproc: sunxi: UAF of priv in probe error path due to workqueue teardown
- [High] remoteproc: sunxi: UAF of rproc in remove due to crash_irq_enabled data race
- [Medium] remoteproc: sunxi: da_to_va translates unmatched ATT addresses as host PAs
- [Medium] remoteproc: sunxi: missing teardown of crash IRQ on start failure leaks state
- [Medium] remoteproc: sunxi: missing write flush of boot address causes execution race
--
commit e007f2bf04fe297a7d71605516a9a9ee48e727ef
Author: Tim Michals <tcmichals@gmail.com>
remoteproc: sunxi: add Allwinner XuanTie RISC-V remoteproc driver
Add remoteproc driver for the auxiliary Alibaba T-Head XuanTie E907
RISC-V co-processor found on Allwinner A523, A527, and T527 SoCs.
> diff --git a/drivers/remoteproc/sunxi_rproc.c b/drivers/remoteproc/sunxi_rproc.c
> index 0000000000000..fbb0ee8a91105
> --- /dev/null
> +++ b/drivers/remoteproc/sunxi_rproc.c
[ ... ]
> +static irqreturn_t sunxi_rproc_crash_handler(int irq, void *data)
> +{
> + struct sunxi_rproc *priv = data;
> + struct rproc *rproc = priv->rproc;
> +
> + dev_err(priv->dev, "Hardware crash event received from %s core!\n",
> + priv->cfg ? priv->cfg->name : "remote");
> + if (priv->crash_irq_enabled) {
> + disable_irq_nosync(irq);
> + priv->crash_irq_enabled = false;
> + }
> + rproc_report_crash(rproc, RPROC_FATAL_ERROR);
> +
> + return IRQ_HANDLED;
> +}
[Severity: High]
Could this lockless check of priv->crash_irq_enabled race with
sunxi_rproc_stop() or sunxi_rproc_remove()?
If a hardware crash occurs concurrently with the core being stopped or removed,
both threads can evaluate priv->crash_irq_enabled as true. Both paths will
independently disable the IRQ, incrementing the disable depth to 2.
When the remote core is restarted via sunxi_rproc_start(), enable_irq() is
only called once, which leaves the crash IRQ permanently masked at the
hardware level.
Also, could this race result in a Use-After-Free during removal?
If the interrupt handler clears the flag before sunxi_rproc_remove() evaluates
it, the remove path skips disable_irq(). It then continues to rproc_del() and
frees the rproc structure while the interrupt handler is concurrently executing
rproc_report_crash(rproc, RPROC_FATAL_ERROR).
[ ... ]
> +int sunxi_rproc_prepare(struct rproc *rproc)
> +{
> + struct sunxi_rproc *priv = rproc->priv;
> + const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
> + int ret;
> +
> + /* 1. Deassert configuration & SRAM bus resets */
> + if (priv->rst_cfg) {
> + ret = reset_control_deassert(priv->rst_cfg);
[Severity: High]
Does this cause the core to start executing prematurely if rst_cfg acts as the
execution reset?
In sunxi_rproc_prepare(), rst_cfg is unconditionally deasserted to allow
register access. Later in sunxi_rproc_start(), if the optional rst_core is
missing, the code falls back to deasserting rst_cfg again:
int sunxi_rproc_start(struct rproc *rproc)
{
...
if (priv->rst_core) {
ret = reset_control_deassert(priv->rst_core);
...
} else if (priv->rst_cfg) {
ret = reset_control_deassert(priv->rst_cfg);
Because rst_cfg was already deasserted in prepare(), the remote core actually
starts executing before the firmware segments are fully loaded and before
the boot address is programmed.
[ ... ]
> +int sunxi_rproc_start(struct rproc *rproc)
> +{
[ ... ]
> + /*
> + * Program boot vector while the core execution reset is held.
> + * The CFG block bus was un-gated during prepare() via rst_cfg.
> + */
> + if (priv->cfg_va) {
> + writel((u32)rproc->bootaddr, priv->cfg_va + cfg->boot_reg_offset);
> + dev_dbg(priv->dev, "STA_ADD set to 0x%08x\n", (u32)rproc->bootaddr);
> + }
> +
> + /* Release core execution reset so the core begins execution at bootaddr */
> + if (priv->rst_core) {
> + ret = reset_control_deassert(priv->rst_core);
> + if (ret) {
> + dev_err(priv->dev, "failed to release core reset: %d\n", ret);
> + return ret;
> + }
[Severity: Medium]
Is a read-back of the CFG register necessary to explicitly flush the posted
boot address write?
Without flushing the writel() before deasserting the core reset, the SoC
interconnect might deliver the reset deassertion before the boot address
reaches the CFG block. This could cause the core to begin execution from a
stale or zero address.
[Severity: Medium]
If reset_control_deassert() fails, does this error path leave the crash IRQ
enabled for an offline core?
Returning early bypasses the required IRQ cleanup, leaving the IRQ active
and the system vulnerable to spurious interrupts during retry attempts.
[ ... ]
> +void sunxi_rproc_kick(struct rproc *rproc, int vqid)
> +{
> + struct sunxi_rproc *priv = rproc->priv;
> + int ret;
> +
> + if (!priv->tx_chan)
> + return;
> +
> + /*
> + * Use priv->kick_msg rather than a stack-local variable. The mailbox
> + * controller runs with tx_block=false, so mbox_send_message() may
> + * queue the pointer and return before the hardware reads the message.
> + * A stack-local vqid would be a use-after-return at that point.
> + */
> + priv->kick_msg = (u32)vqid;
> + ret = mbox_send_message(priv->tx_chan, &priv->kick_msg);
> + if (ret < 0)
> + dev_err_ratelimited(priv->dev, "failed to send mailbox kick: %d\n", ret);
> +
> + mbox_client_txdone(priv->tx_chan, 0);
> +}
[Severity: High]
Does this create a data race on priv->kick_msg during concurrent kicks?
Different virtqueues on multiple CPUs can execute sunxi_rproc_kick()
concurrently. Since they both assign to the single shared priv->kick_msg
variable without a lock, a concurrent kick will overwrite it before the
hardware processes the queue, causing dropped notifications.
Also, does calling mbox_client_txdone() immediately after a non-blocking send
break the mailbox queue pacing?
By falsely signaling TX completion, the framework will submit the next
message immediately, potentially clobbering the controller's TX registers
while the first message is still physically transmitting.
[ ... ]
> +void *sunxi_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
> +{
> + struct sunxi_rproc *priv = rproc->priv;
> + u64 sys;
[ ... ]
> + /*
> + * 1. Translate core-local device addresses (DA) to system bus
> + * addresses (Host PA) using the SoC address translation table (ATT).
> + */
> + if (sunxi_rproc_da_to_sys(priv, da, len, &sys, is_iomem) == 0) {
> + if (priv->r_sram_va && sys >= priv->r_sram_phys &&
> + (sys + len) <= (priv->r_sram_phys + priv->r_sram_size))
> + return (__force void *)(priv->r_sram_va + (sys - priv->r_sram_phys));
[ ... ]
> + }
> +
> + /*
> + * 2. Device Tree Memory Regions (Trace buffer, DRAM carveout, or
> + * dynamically-assigned SRAM regions whose host PA is supplied via DT).
> + */
> + if (priv->trace_va && da >= priv->trace_phys &&
[Severity: Medium]
Should there be an early return here if the ATT translation succeeds but fails
to match a valid mapped memory window?
If sunxi_rproc_da_to_sys() returns 0 but the resulting sys address isn't found
in any mapped window, the code falls through to the Device Tree fallback
checks.
In the fallback block, it mistakenly compares the original device address (da)
against the host physical addresses (e.g. priv->trace_phys). If a da happens
to overlap numerically with a host PA, it will improperly translate the
address and return an invalid virtual pointer.
[ ... ]
> +static int sunxi_rproc_parse_memory_regions(struct rproc *rproc)
> +{
[ ... ]
> + if (name && (strstr(name, "trace") || of_node_name_eq(rmem_np, "trace"))) {
> + priv->trace_phys = res.start;
> + priv->trace_size = resource_size(&res);
> + priv->trace_va = devm_memremap(dev, res.start, resource_size(&res),
> + MEMREMAP_WB);
[ ... ]
> + } else if (name && (strstr(name, "dram") || strstr(name, "vram"))) {
> + priv->dram_phys = res.start;
> + priv->dram_size = resource_size(&res);
> + priv->dram_va = devm_memremap(dev, res.start, resource_size(&res),
> + MEMREMAP_WB);
[ ... ]
> + }
> +
> + /* Reuse existing SRAM mapping if region overlaps, else ioremap */
> + if (priv->r_sram1_va && res.start == priv->r_sram1_phys)
> + va = (__force void *)priv->r_sram1_va;
> + else if (priv->r_sram_va && res.start == priv->r_sram_phys)
> + va = (__force void *)priv->r_sram_va;
> + else
> + va = (__force void *)devm_ioremap_wc(dev, res.start, resource_size(&res));
[Severity: High]
Does this establish conflicting memory mappings for the trace and dram regions?
After creating the initial mapping using devm_memremap() with Write-Back
cacheability (MEMREMAP_WB), the loop lacks a continue statement or an else
branch.
Execution falls through to the fallback block where it maps the exact same
physical memory again using devm_ioremap_wc() (Write-Combining). Mapping the
same physical address space concurrently with mismatched caching attributes
is undefined behavior on ARM64 and can cause silent cache corruption.
[ ... ]
> +static int sunxi_rproc_probe(struct platform_device *pdev)
> +{
[ ... ]
> +err_mbox_release:
> + cancel_work_sync(&priv->vq_work);
> + /*
> + * mbox_request_channel_byname() can return ERR_PTR on failure.
> + * Guard with IS_ERR() to avoid calling mbox_free_channel() with
> + * an invalid pointer, which would panic on the first dereference.
> + */
> + if (priv->rx_chan && !IS_ERR(priv->rx_chan))
> + mbox_free_channel(priv->rx_chan);
[Severity: High]
Does cancelling the workqueue before shutting down the mailbox channel
introduce a Use-After-Free risk?
Because mbox_free_channel() happens after cancel_work_sync(), the mailbox
remains active. If an RX interrupt fires immediately after the cancellation,
the callback can re-queue priv->vq_work.
When the probe function returns an error, devres will free the priv
structure. The workqueue would then execute the dangling work item and
dereference the freed pointer.
[ ... ]
> +static void sunxi_rproc_remove(struct platform_device *pdev)
> +{
> + struct rproc *rproc = platform_get_drvdata(pdev);
> + struct sunxi_rproc *priv = rproc->priv;
[ ... ]
> + rproc_del(rproc);
> +
> + if (priv->rx_chan) {
> + mbox_free_channel(priv->rx_chan);
> + priv->rx_chan = NULL;
> + }
[Severity: High]
Does calling rproc_del() before tearing down the mailbox expose a window for
virtqueue Use-After-Free?
rproc_del() stops the remote processor and destroys all virtio
resources and virtqueues. However, the mailbox channel remains active.
If a late interrupt is received, the mailbox IRQ callback will queue vq_work,
which unconditionally calls rproc_vq_interrupt(). Executing this against the
already destroyed virtqueues will result in a crash.
@@ -388,6 +388,14 @@ config XLNX_R5_REMOTEPROC
It's safe to say N if not interested in using RPU r5f cores.
+config SUNXI_REMOTEPROC
+ tristate "Allwinner XuanTie RISC-V remoteproc support"
+ depends on (ARCH_SUNXI || COMPILE_TEST) && MAILBOX
+ help
+ Say y or m here to support the Allwinner XuanTie E906/E907 RISC-V
+ co-processor on Allwinner A523, A527, and T527 SoCs via the
+ remote processor framework.
+
endif # REMOTEPROC
endmenu
@@ -41,3 +41,4 @@ obj-$(CONFIG_TI_K3_DSP_REMOTEPROC) += ti_k3_dsp_remoteproc.o ti_k3_common.o
obj-$(CONFIG_TI_K3_M4_REMOTEPROC) += ti_k3_m4_remoteproc.o ti_k3_common.o
obj-$(CONFIG_TI_K3_R5_REMOTEPROC) += ti_k3_r5_remoteproc.o ti_k3_common.o
obj-$(CONFIG_XLNX_R5_REMOTEPROC) += xlnx_r5_remoteproc.o
+obj-$(CONFIG_SUNXI_REMOTEPROC) += sunxi_rproc.o
new file mode 100644
@@ -0,0 +1,922 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Allwinner XuanTie E906/E907 RISC-V Remote Processor Driver
+ *
+ * Copyright (C) 2024-2026 Allwinner Technology Co., Ltd.
+ * Copyright (C) 2026 Tim Michals <tcmichals@gmail.com>
+ *
+ * CCU-integrated remoteproc driver for XuanTie RISC-V co-processors on
+ * Allwinner SoCs (T527/A527/A523: E906/E907) supporting TCM, SRAM, and DRAM.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+#include <linux/reset.h>
+
+#include "remoteproc_internal.h"
+#include "sunxi_rproc.h"
+
+#define DRIVER_NAME "sunxi-rproc"
+
+static const struct sunxi_rproc_att sun55i_rproc_att[] = {
+ /* dev addr (remote) , sys addr (host PA) , size , flags */
+ /* Space 0 Core Aliases -> Space 0 Host PA */
+ { E907_SRAM_SPACE0_DA, SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SIZE, ATT_IOMEM },
+ { E907_SRAM_SPACE0_DA_ALT, SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SIZE, ATT_IOMEM },
+ { E907_SRAM_C_DA, SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SIZE, ATT_IOMEM },
+ { SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SIZE, ATT_IOMEM },
+
+ /* Space 1 Core Aliases -> Space 1 Host PA */
+ { E907_SRAM_SPACE1_DA, SUN55I_SRAM_SPACE1_SYS, SUN55I_SRAM_SPACE1_SIZE, ATT_IOMEM },
+ { E907_SRAM_SPACE1_DA_ALT, SUN55I_SRAM_SPACE1_SYS, SUN55I_SRAM_SPACE1_SIZE, ATT_IOMEM },
+ { SUN55I_SRAM_SPACE1_SYS, SUN55I_SRAM_SPACE1_SYS, SUN55I_SRAM_SPACE1_SIZE, ATT_IOMEM },
+};
+
+const struct sunxi_rproc_cfg sun55i_riscv_cfg = {
+ .name = "XuanTie E907 RISC-V",
+ .att = sun55i_rproc_att,
+ .att_size = ARRAY_SIZE(sun55i_rproc_att),
+ .has_remap_reg = true,
+ .boot_reg_offset = E906_STA_ADD_REG,
+};
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sun55i_riscv_cfg);
+#endif
+
+static void sunxi_rproc_vq_work(struct work_struct *work)
+{
+ struct sunxi_rproc *priv = container_of(work, struct sunxi_rproc, vq_work);
+
+ rproc_vq_interrupt(priv->rproc, 0);
+ rproc_vq_interrupt(priv->rproc, 1);
+}
+
+static void sunxi_rproc_mb_rx_callback(struct mbox_client *cl, void *data)
+{
+ struct sunxi_rproc *priv = container_of(cl, struct sunxi_rproc, cl);
+
+ schedule_work(&priv->vq_work);
+}
+
+static irqreturn_t sunxi_rproc_crash_handler(int irq, void *data)
+{
+ struct sunxi_rproc *priv = data;
+ struct rproc *rproc = priv->rproc;
+
+ dev_err(priv->dev, "Hardware crash event received from %s core!\n",
+ priv->cfg ? priv->cfg->name : "remote");
+ if (priv->crash_irq_enabled) {
+ disable_irq_nosync(irq);
+ priv->crash_irq_enabled = false;
+ }
+ rproc_report_crash(rproc, RPROC_FATAL_ERROR);
+
+ return IRQ_HANDLED;
+}
+
+int sunxi_rproc_prepare(struct rproc *rproc)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+ int ret;
+
+ /* 1. Deassert configuration & SRAM bus resets */
+ if (priv->rst_cfg) {
+ ret = reset_control_deassert(priv->rst_cfg);
+ if (ret) {
+ dev_err(priv->dev, "failed to deassert cfg reset: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (priv->rst_sram) {
+ ret = reset_control_deassert(priv->rst_sram);
+ if (ret) {
+ dev_err(priv->dev, "failed to deassert sram reset: %d\n", ret);
+ goto err_assert_cfg;
+ }
+ }
+
+ if (priv->rst_msgbox) {
+ ret = reset_control_deassert(priv->rst_msgbox);
+ if (ret) {
+ dev_err(priv->dev, "failed to deassert msgbox reset: %d\n", ret);
+ goto err_assert_sram;
+ }
+ }
+
+ /* 2. Enable parent clock (PLL source) */
+ if (priv->clk_parent) {
+ ret = clk_prepare_enable(priv->clk_parent);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable parent clock: %d\n", ret);
+ goto err_assert_msgbox;
+ }
+ }
+
+ /* 3. Enable interconnect bus and SRAM clocks */
+ if (priv->clk_bus) {
+ ret = clk_prepare_enable(priv->clk_bus);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable bus clock: %d\n", ret);
+ goto err_disable_parent;
+ }
+ }
+
+ if (priv->clk_sram) {
+ ret = clk_prepare_enable(priv->clk_sram);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable sram clock: %d\n", ret);
+ goto err_disable_bus;
+ }
+ }
+
+ if (priv->clk_msgbox) {
+ ret = clk_prepare_enable(priv->clk_msgbox);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable msgbox clock: %d\n", ret);
+ goto err_disable_sram_clk;
+ }
+ }
+
+ /* 4. Enable core clock */
+ if (priv->clk_core) {
+ ret = clk_prepare_enable(priv->clk_core);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable core clock: %d\n", ret);
+ goto err_disable_msgbox_clk;
+ }
+ }
+
+ /*
+ * 4b. Enable SRAMA3_2 for MCU_SYS (RISC-V) via REMAP_CTRL_REG bit 1.
+ */
+ if (cfg->has_remap_reg && priv->remap_va) {
+ u32 remap_val = readl(priv->remap_va);
+
+ remap_val |= SUNXI_REMAP_SRAMA3_2_BIT;
+ writel(remap_val, priv->remap_va);
+ dev_dbg(priv->dev, "REMAP_CTRL_REG set to 0x%08x (SRAMA3_2 enabled)\n",
+ readl(priv->remap_va));
+ }
+
+ /*
+ * 5. Cleanly clear Dedicated Local SRAM and Switchable SRAM.
+ * Only clears regions that are actually mapped from Device Tree.
+ * Matches upstream patterns (e.g., imx_rproc / ti_k3_r5_remoteproc) to:
+ * - Prevent ECC/parity noise on uninitialized memory banks.
+ * - Ensure NOBITS / .bss sections start strictly at zero.
+ * - Clear stale trace0 logs/telemetry from previous runs.
+ */
+ if (priv->r_sram_va && priv->r_sram_size)
+ memset_io(priv->r_sram_va, 0, priv->r_sram_size);
+
+ if (priv->r_sram1_va && priv->r_sram1_size)
+ memset_io(priv->r_sram1_va, 0, priv->r_sram1_size);
+
+ return 0;
+
+err_disable_msgbox_clk:
+ if (priv->clk_msgbox)
+ clk_disable_unprepare(priv->clk_msgbox);
+err_disable_sram_clk:
+ if (priv->clk_sram)
+ clk_disable_unprepare(priv->clk_sram);
+err_disable_bus:
+ if (priv->clk_bus)
+ clk_disable_unprepare(priv->clk_bus);
+err_disable_parent:
+ if (priv->clk_parent)
+ clk_disable_unprepare(priv->clk_parent);
+err_assert_msgbox:
+ if (priv->rst_msgbox)
+ reset_control_assert(priv->rst_msgbox);
+err_assert_sram:
+ if (priv->rst_sram)
+ reset_control_assert(priv->rst_sram);
+err_assert_cfg:
+ if (priv->rst_cfg)
+ reset_control_assert(priv->rst_cfg);
+ return ret;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_prepare);
+#endif
+
+int sunxi_rproc_unprepare(struct rproc *rproc)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+
+ /* Symmetrical CCU unwinding */
+ if (cfg->has_remap_reg && priv->remap_va) {
+ u32 remap_val = readl(priv->remap_va);
+
+ remap_val &= ~SUNXI_REMAP_SRAMA3_2_BIT;
+ writel(remap_val, priv->remap_va);
+ }
+
+ if (priv->clk_core)
+ clk_disable_unprepare(priv->clk_core);
+
+ if (priv->clk_msgbox)
+ clk_disable_unprepare(priv->clk_msgbox);
+
+ if (priv->clk_sram)
+ clk_disable_unprepare(priv->clk_sram);
+
+ if (priv->clk_bus)
+ clk_disable_unprepare(priv->clk_bus);
+
+ if (priv->clk_parent)
+ clk_disable_unprepare(priv->clk_parent);
+
+ if (priv->rst_msgbox)
+ reset_control_assert(priv->rst_msgbox);
+
+ if (priv->rst_sram)
+ reset_control_assert(priv->rst_sram);
+
+ if (priv->rst_cfg)
+ reset_control_assert(priv->rst_cfg);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_unprepare);
+#endif
+
+int sunxi_rproc_start(struct rproc *rproc)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+ int ret;
+
+ dev_info(priv->dev, "Starting %s core at entry 0x%llx\n",
+ cfg->name ? cfg->name : "remote", (u64)rproc->bootaddr);
+
+ if (rproc->bootaddr > U32_MAX)
+ return -EINVAL;
+
+ /* Enable crash IRQ now that core is executing */
+ if (priv->crash_irq > 0 && !priv->crash_irq_enabled) {
+ enable_irq(priv->crash_irq);
+ priv->crash_irq_enabled = true;
+ }
+
+ /*
+ * Program boot vector while the core execution reset is held.
+ * The CFG block bus was un-gated during prepare() via rst_cfg.
+ */
+ if (priv->cfg_va) {
+ writel((u32)rproc->bootaddr, priv->cfg_va + cfg->boot_reg_offset);
+ dev_dbg(priv->dev, "STA_ADD set to 0x%08x\n", (u32)rproc->bootaddr);
+ }
+
+ /* Release core execution reset so the core begins execution at bootaddr */
+ if (priv->rst_core) {
+ ret = reset_control_deassert(priv->rst_core);
+ if (ret) {
+ dev_err(priv->dev, "failed to release core reset: %d\n", ret);
+ return ret;
+ }
+ } else if (priv->rst_cfg) {
+ ret = reset_control_deassert(priv->rst_cfg);
+ if (ret) {
+ dev_err(priv->dev, "failed to release cfg reset: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_start);
+#endif
+
+int sunxi_rproc_stop(struct rproc *rproc)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+
+ dev_info(priv->dev, "Halting %s core...\n",
+ cfg->name ? cfg->name : "remote");
+
+ /*
+ * Assert reset first so the core stops generating mailbox interrupts,
+ * then drain any work already queued. Reversing this order leaves a
+ * window where a late IRQ re-queues vq_work after cancel_work_sync()
+ * returns, executing on freed resources.
+ */
+ if (priv->rst_core)
+ reset_control_assert(priv->rst_core);
+ else if (priv->rst_cfg)
+ reset_control_assert(priv->rst_cfg);
+
+ /* Disable crash IRQ while core is stopped */
+ if (priv->crash_irq > 0 && priv->crash_irq_enabled) {
+ disable_irq(priv->crash_irq);
+ priv->crash_irq_enabled = false;
+ }
+
+ cancel_work_sync(&priv->vq_work);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_stop);
+#endif
+
+void sunxi_rproc_kick(struct rproc *rproc, int vqid)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ int ret;
+
+ if (!priv->tx_chan)
+ return;
+
+ /*
+ * Use priv->kick_msg rather than a stack-local variable. The mailbox
+ * controller runs with tx_block=false, so mbox_send_message() may
+ * queue the pointer and return before the hardware reads the message.
+ * A stack-local vqid would be a use-after-return at that point.
+ */
+ priv->kick_msg = (u32)vqid;
+ ret = mbox_send_message(priv->tx_chan, &priv->kick_msg);
+ if (ret < 0)
+ dev_err_ratelimited(priv->dev, "failed to send mailbox kick: %d\n", ret);
+
+ mbox_client_txdone(priv->tx_chan, 0);
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_kick);
+#endif
+
+static int sunxi_rproc_da_to_sys(struct sunxi_rproc *priv, u64 da,
+ size_t len, u64 *sys, bool *is_iomem)
+{
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+ size_t i;
+
+ if (len == 0 || da > U64_MAX - len)
+ return -EINVAL;
+
+ if (cfg->att) {
+ for (i = 0; i < cfg->att_size; i++) {
+ const struct sunxi_rproc_att *att = &cfg->att[i];
+
+ if (da >= att->da && (da + len) <= (att->da + att->size)) {
+ *sys = att->sa + (da - att->da);
+ if (is_iomem)
+ *is_iomem = !!(att->flags & ATT_IOMEM);
+ return 0;
+ }
+ }
+ }
+
+ return -ENOENT;
+}
+
+void *sunxi_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ u64 sys;
+
+ /*
+ * Reject zero length and any da+len combination that overflows u64.
+ * A crafted ELF with da near U64_MAX could wrap da+len to a small value,
+ * bypassing upper-bound checks and allowing arbitrary kernel memory
+ * to be mapped during firmware loading.
+ */
+ if (len == 0 || da > U64_MAX - len)
+ return NULL;
+
+ /*
+ * 1. Translate core-local device addresses (DA) to system bus
+ * addresses (Host PA) using the SoC address translation table (ATT).
+ */
+ if (sunxi_rproc_da_to_sys(priv, da, len, &sys, is_iomem) == 0) {
+ if (priv->r_sram_va && sys >= priv->r_sram_phys &&
+ (sys + len) <= (priv->r_sram_phys + priv->r_sram_size))
+ return (__force void *)(priv->r_sram_va + (sys - priv->r_sram_phys));
+
+ if (priv->r_sram1_va && sys >= priv->r_sram1_phys &&
+ (sys + len) <= (priv->r_sram1_phys + priv->r_sram1_size))
+ return (__force void *)(priv->r_sram1_va + (sys - priv->r_sram1_phys));
+
+ if (priv->dram_va && sys >= priv->dram_phys &&
+ (sys + len) <= (priv->dram_phys + priv->dram_size))
+ return (__force void *)(priv->dram_va + (sys - priv->dram_phys));
+
+ if (priv->trace_va && sys >= priv->trace_phys &&
+ (sys + len) <= (priv->trace_phys + priv->trace_size))
+ return (__force void *)(priv->trace_va + (sys - priv->trace_phys));
+ }
+
+ /*
+ * 2. Device Tree Memory Regions (Trace buffer, DRAM carveout, or
+ * dynamically-assigned SRAM regions whose host PA is supplied via DT).
+ */
+ if (priv->trace_va && da >= priv->trace_phys &&
+ (da + len) <= (priv->trace_phys + priv->trace_size)) {
+ if (is_iomem)
+ *is_iomem = false;
+ return (__force void *)(priv->trace_va + (da - priv->trace_phys));
+ }
+
+ if (priv->dram_va && da >= priv->dram_phys &&
+ (da + len) <= (priv->dram_phys + priv->dram_size)) {
+ if (is_iomem)
+ *is_iomem = false;
+ return (__force void *)(priv->dram_va + (da - priv->dram_phys));
+ }
+
+ if (priv->r_sram_va && da >= priv->r_sram_phys &&
+ (da + len) <= (priv->r_sram_phys + priv->r_sram_size)) {
+ if (is_iomem)
+ *is_iomem = true;
+ return (__force void *)(priv->r_sram_va + (da - priv->r_sram_phys));
+ }
+
+ if (priv->r_sram1_va && da >= priv->r_sram1_phys &&
+ (da + len) <= (priv->r_sram1_phys + priv->r_sram1_size)) {
+ if (is_iomem)
+ *is_iomem = true;
+ return (__force void *)(priv->r_sram1_va + (da - priv->r_sram1_phys));
+ }
+
+ /*
+ * Return NULL to delegate all DRAM carveouts (vrings, buffers,
+ * code/data placed in DDR) directly to the remoteproc core's
+ * internal carveout table.
+ */
+ return NULL;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_da_to_va);
+#endif
+
+static int sunxi_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
+{
+ int ret;
+
+ ret = rproc_elf_load_rsc_table(rproc, fw);
+ if (ret == -EINVAL) {
+ dev_dbg(rproc->dev.parent, "no resource table found in ELF\n");
+ return 0;
+ }
+ return ret;
+}
+
+const struct rproc_ops sunxi_rproc_ops = {
+ .prepare = sunxi_rproc_prepare,
+ .unprepare = sunxi_rproc_unprepare,
+ .start = sunxi_rproc_start,
+ .stop = sunxi_rproc_stop,
+ .kick = sunxi_rproc_kick,
+ .da_to_va = sunxi_rproc_da_to_va,
+ .get_boot_addr = rproc_elf_get_boot_addr,
+ .load = rproc_elf_load_segments,
+ .parse_fw = sunxi_rproc_parse_fw,
+ .find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
+ .sanity_check = rproc_elf_sanity_check,
+ .coredump = rproc_coredump,
+};
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_ops);
+#endif
+
+static int sunxi_rproc_register_mem(struct platform_device *pdev, struct rproc *rproc)
+{
+ struct device *dev = &pdev->dev;
+ struct sunxi_rproc *priv = rproc->priv;
+ struct resource *res;
+
+ /* 1. Map Optional RISC-V CFG Block ("cfg") */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg");
+ if (res) {
+ priv->cfg_phys = res->start;
+ priv->cfg_va = devm_ioremap(dev, res->start, resource_size(res));
+ if (!priv->cfg_va)
+ dev_warn(dev, "failed to map 'cfg' registers\n");
+ }
+
+ /* 2. Map Dedicated RISC-V Local SRAM Space 0 ("r_sram" or "sram") */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "r_sram");
+ if (!res)
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+ if (res) {
+ priv->r_sram_phys = res->start;
+ priv->r_sram_size = resource_size(res);
+ priv->r_sram_va = devm_ioremap_wc(dev, res->start, resource_size(res));
+ if (!priv->r_sram_va)
+ return -ENOMEM;
+ }
+
+ /* 3. Map Switchable RISC-V Local SRAM Space 1 ("r_sram1" / SRAMA3_2) - Optional */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "r_sram1");
+ if (res) {
+ priv->r_sram1_phys = res->start;
+ priv->r_sram1_size = resource_size(res);
+ priv->r_sram1_va = devm_ioremap_wc(dev, res->start, resource_size(res));
+ if (!priv->r_sram1_va)
+ dev_warn(dev, "failed to map 'r_sram1' resource\n");
+ }
+
+ /* 4. Map Remap Control Register ("remap" or "sram-for-cpux") - Optional */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "remap");
+ if (!res)
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram-for-cpux");
+ if (res) {
+ priv->remap_phys = res->start;
+ if (resource_size(res) > SUNXI_REMAP_CTRL_OFFSET &&
+ IS_ALIGNED(res->start, PAGE_SIZE)) {
+ void __iomem *base = devm_ioremap(dev, res->start, resource_size(res));
+
+ if (base)
+ priv->remap_va = base + SUNXI_REMAP_CTRL_OFFSET;
+ } else {
+ priv->remap_va = devm_ioremap(dev, res->start, resource_size(res));
+ }
+ if (!priv->remap_va)
+ dev_warn(dev, "failed to map 'remap' register\n");
+ }
+
+ /* 4b. Map Boot DRAM Carveout (Resource "dram" if defined in reg) */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dram");
+ if (res) {
+ priv->dram_phys = res->start;
+ priv->dram_size = resource_size(res);
+ priv->dram_va = devm_memremap(dev, res->start, resource_size(res), MEMREMAP_WB);
+ if (!priv->dram_va)
+ priv->dram_va = (__force void *)devm_ioremap_wc(dev, res->start,
+ resource_size(res));
+ if (!priv->dram_va)
+ dev_warn(dev, "failed to map 'dram' resource\n");
+ }
+
+ dev_info(dev, "Memory resources: r_sram=%s, r_sram1=%s, remap=%s, cfg=%s, dram=%s\n",
+ priv->r_sram_va ? "yes" : "no",
+ priv->r_sram1_va ? "yes" : "no",
+ priv->remap_va ? "yes" : "no",
+ priv->cfg_va ? "yes" : "no",
+ priv->dram_va ? "yes" : "no");
+
+ /* 5. Map Trace Buffer (Resource "trace" if defined in reg) */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "trace");
+ if (res) {
+ priv->trace_phys = res->start;
+ priv->trace_size = resource_size(res);
+ priv->trace_va = devm_memremap(dev, res->start, resource_size(res), MEMREMAP_WB);
+ if (!priv->trace_va)
+ priv->trace_va = (__force void *)devm_ioremap_wc(dev, res->start,
+ resource_size(res));
+ dev_info(dev, "mapped 'trace' mmio resource %pa+%zu\n",
+ &priv->trace_phys, priv->trace_size);
+ }
+
+ return 0;
+}
+
+static int sunxi_rproc_parse_memory_regions(struct rproc *rproc)
+{
+ struct device *dev = rproc->dev.parent;
+ struct device_node *np = dev->of_node;
+ struct sunxi_rproc *priv = rproc->priv;
+ int num_rmems;
+ int i;
+
+ if (!np)
+ return 0;
+
+ num_rmems = of_count_phandle_with_args(np, "memory-region", NULL);
+ if (num_rmems <= 0)
+ return 0;
+
+ /* Bind default DMA pool for dynamic allocations (e.g. vdev0 vrings/buffers) */
+ if (of_reserved_mem_device_init(dev) == 0)
+ priv->has_reserved_mem = true;
+ else
+ dev_dbg(dev, "no dedicated DMA pool assigned from reserved-memory\n");
+
+ /* Register all reserved-memory regions as formal remoteproc carveouts */
+ for (i = 0; i < num_rmems; i++) {
+ struct device_node *rmem_np;
+ struct resource res;
+ const char *name = NULL;
+ struct rproc_mem_entry *mem;
+ void *va;
+
+ rmem_np = of_parse_phandle(np, "memory-region", i);
+ if (!rmem_np)
+ continue;
+
+ if (of_address_to_resource(rmem_np, 0, &res)) {
+ of_node_put(rmem_np);
+ continue;
+ }
+
+ of_property_read_string_index(np, "memory-region-names", i, &name);
+ if (!name)
+ name = rmem_np->name;
+
+ if (name && (strstr(name, "trace") || of_node_name_eq(rmem_np, "trace"))) {
+ priv->trace_phys = res.start;
+ priv->trace_size = resource_size(&res);
+ priv->trace_va = devm_memremap(dev, res.start, resource_size(&res),
+ MEMREMAP_WB);
+ if (!priv->trace_va)
+ priv->trace_va = (__force void *)
+ devm_ioremap_wc(dev, res.start, resource_size(&res));
+ dev_info(dev, "registered trace carveout %pa+%zu (%s)\n",
+ &priv->trace_phys, priv->trace_size, name);
+ } else if (name && (strstr(name, "dram") || strstr(name, "vram"))) {
+ priv->dram_phys = res.start;
+ priv->dram_size = resource_size(&res);
+ priv->dram_va = devm_memremap(dev, res.start, resource_size(&res),
+ MEMREMAP_WB);
+ if (!priv->dram_va)
+ priv->dram_va = (__force void *)
+ devm_ioremap_wc(dev, res.start, resource_size(&res));
+ dev_info(dev, "registered dram carveout %pa+%zu (%s)\n",
+ &priv->dram_phys, priv->dram_size, name);
+ }
+
+ /* Reuse existing SRAM mapping if region overlaps, else ioremap */
+ if (priv->r_sram1_va && res.start == priv->r_sram1_phys)
+ va = (__force void *)priv->r_sram1_va;
+ else if (priv->r_sram_va && res.start == priv->r_sram_phys)
+ va = (__force void *)priv->r_sram_va;
+ else
+ va = (__force void *)devm_ioremap_wc(dev, res.start, resource_size(&res));
+
+ if (va) {
+ mem = rproc_mem_entry_init(dev, va, (dma_addr_t)res.start,
+ resource_size(&res), (u32)res.start,
+ NULL, NULL, "%s", name);
+ if (mem) {
+ mem->is_iomem = true;
+ rproc_add_carveout(rproc, mem);
+ }
+ }
+ of_node_put(rmem_np);
+ }
+
+ return 0;
+}
+
+static int sunxi_rproc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const char *fw_name = "riscv-firmware.elf";
+ struct sunxi_rproc *priv;
+ struct rproc *rproc;
+ int crash_irq;
+ int ret;
+
+ of_property_read_string(dev->of_node, "firmware-name", &fw_name);
+
+ rproc = devm_rproc_alloc(dev, dev_name(dev), &sunxi_rproc_ops,
+ fw_name, sizeof(*priv));
+ if (!rproc) {
+ dev_err(dev, "failed to allocate rproc context\n");
+ return -ENOMEM;
+ }
+
+ ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(dev, "failed to set 32-bit DMA coherent mask: %d\n", ret);
+ return ret;
+ }
+
+ priv = rproc->priv;
+ priv->rproc = rproc;
+ priv->dev = dev;
+ priv->cfg = of_device_get_match_data(dev);
+ if (!priv->cfg)
+ priv->cfg = &sun55i_riscv_cfg;
+
+ /* 1. Common Clock Framework (CCF) Clocks */
+ priv->clk_parent = devm_clk_get_optional(dev, "parent");
+ if (IS_ERR(priv->clk_parent))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_parent),
+ "failed to get 'parent' clock\n");
+
+ priv->clk_bus = devm_clk_get_optional(dev, "bus");
+ if (IS_ERR(priv->clk_bus))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_bus), "failed to get 'bus' clock\n");
+
+ priv->clk_core = devm_clk_get_optional(dev, "core");
+ if (IS_ERR(priv->clk_core))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_core), "failed to get 'core' clock\n");
+
+ priv->clk_sram = devm_clk_get_optional(dev, "sram");
+ if (IS_ERR(priv->clk_sram))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_sram), "failed to get 'sram' clock\n");
+
+ priv->clk_msgbox = devm_clk_get_optional(dev, "msgbox");
+ if (IS_ERR(priv->clk_msgbox))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_msgbox),
+ "failed to get 'msgbox' clock\n");
+
+ /* 2. Resets */
+ priv->rst_core = devm_reset_control_get_optional_exclusive(dev, "core");
+ if (IS_ERR(priv->rst_core))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_core), "failed to get 'core' reset\n");
+
+ priv->rst_cfg = devm_reset_control_get_optional_exclusive(dev, "cfg");
+ if (IS_ERR(priv->rst_cfg))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_cfg), "failed to get 'cfg' reset\n");
+
+ priv->rst_sram = devm_reset_control_get_optional_exclusive(dev, "sram");
+ if (IS_ERR(priv->rst_sram))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_sram), "failed to get 'sram' reset\n");
+
+ priv->rst_msgbox = devm_reset_control_get_optional_exclusive(dev, "msgbox");
+ if (IS_ERR(priv->rst_msgbox))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_msgbox),
+ "failed to get 'msgbox' reset\n");
+
+ /* 3. Memory Windows (TCM, SRAM, CFG) */
+ ret = sunxi_rproc_register_mem(pdev, rproc);
+ if (ret)
+ return ret;
+
+ /* 4. Dynamic Reserved Memory Carveouts & DMA Pools from Device Tree */
+ ret = sunxi_rproc_parse_memory_regions(rproc);
+ if (ret)
+ return ret;
+
+ /* 5. Optional Hardware Crash Notification IRQ */
+ crash_irq = platform_get_irq_byname_optional(pdev, "crash");
+ if (crash_irq > 0) {
+ ret = devm_request_threaded_irq(dev, crash_irq, NULL,
+ sunxi_rproc_crash_handler,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "sunxi-rproc-crash",
+ priv);
+ if (ret) {
+ dev_warn(dev, "failed to request crash IRQ %d: %d\n", crash_irq, ret);
+ } else {
+ priv->crash_irq = crash_irq;
+ priv->crash_irq_enabled = false;
+ }
+ }
+
+ /* 6. Mailbox IPC Client */
+ INIT_WORK(&priv->vq_work, sunxi_rproc_vq_work);
+
+ priv->cl.dev = dev;
+ priv->cl.rx_callback = sunxi_rproc_mb_rx_callback;
+ priv->cl.tx_block = false;
+ priv->cl.knows_txdone = true;
+
+ /*
+ * If the hardware mailbox is assigned to userspace (generic-uio) or
+ * lacks #mbox-cells, run RemoteProc in standalone mode.
+ */
+ if (dev->of_node) {
+ struct device_node *mb_node = of_parse_phandle(dev->of_node, "mboxes", 0);
+
+ if (mb_node) {
+ if (of_device_is_compatible(mb_node, "generic-uio") ||
+ !of_property_read_bool(mb_node, "#mbox-cells")) {
+ dev_info(dev, "Mailbox assigned to UIO; running standalone mode\n");
+ of_node_put(mb_node);
+ goto skip_mbox;
+ }
+ of_node_put(mb_node);
+ }
+ }
+
+ priv->tx_chan = mbox_request_channel_byname(&priv->cl, "tx");
+ if (IS_ERR(priv->tx_chan)) {
+ if (PTR_ERR(priv->tx_chan) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto err_mem_release;
+ }
+ dev_info(dev, "no tx mailbox channel configured; running standalone mode\n");
+ priv->tx_chan = NULL;
+ }
+
+ if (priv->tx_chan) {
+ priv->rx_chan = mbox_request_channel_byname(&priv->cl, "rx");
+ if (IS_ERR(priv->rx_chan)) {
+ if (PTR_ERR(priv->rx_chan) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto err_mbox_release;
+ }
+ dev_info(dev, "no rx mailbox channel configured\n");
+ priv->rx_chan = NULL;
+ }
+ }
+
+skip_mbox:
+ platform_set_drvdata(pdev, rproc);
+
+ ret = rproc_add(rproc);
+ if (ret) {
+ dev_err(dev, "failed to register rproc device: %d\n", ret);
+ goto err_mbox_release;
+ }
+
+ dev_info(dev, "Allwinner %s remoteproc registered (%s)\n",
+ priv->cfg ? priv->cfg->name : "remote", fw_name);
+ return 0;
+
+err_mbox_release:
+ cancel_work_sync(&priv->vq_work);
+ /*
+ * mbox_request_channel_byname() can return ERR_PTR on failure.
+ * Guard with IS_ERR() to avoid calling mbox_free_channel() with
+ * an invalid pointer, which would panic on the first dereference.
+ */
+ if (priv->rx_chan && !IS_ERR(priv->rx_chan))
+ mbox_free_channel(priv->rx_chan);
+ if (priv->tx_chan && !IS_ERR(priv->tx_chan))
+ mbox_free_channel(priv->tx_chan);
+err_mem_release:
+ if (priv->has_reserved_mem)
+ of_reserved_mem_device_release(dev);
+ return ret;
+}
+
+static void sunxi_rproc_remove(struct platform_device *pdev)
+{
+ struct rproc *rproc = platform_get_drvdata(pdev);
+ struct sunxi_rproc *priv = rproc->priv;
+
+ /*
+ * Teardown order is critical:
+ * 1. Disable crash IRQ first so late hardware crash alerts cannot
+ * race against rproc_del() or report crashes on a deleted device.
+ * 2. rproc_del() stops the remote core and tears down VirtIO/vring,
+ * which stops the hardware from generating further mailbox IRQs.
+ * 3. cancel_work_sync() drains any in-flight vq_work. Calling this
+ * before rproc_del() risks a late RX IRQ re-queuing work after
+ * cancel_work_sync() returns, executing on freed priv->rx_chan.
+ * 4. Free mailbox channels only after the workqueue is fully drained.
+ */
+ if (priv->crash_irq > 0 && priv->crash_irq_enabled) {
+ disable_irq(priv->crash_irq);
+ priv->crash_irq_enabled = false;
+ }
+
+ rproc_del(rproc);
+
+ if (priv->rx_chan) {
+ mbox_free_channel(priv->rx_chan);
+ priv->rx_chan = NULL;
+ }
+ if (priv->tx_chan) {
+ mbox_free_channel(priv->tx_chan);
+ priv->tx_chan = NULL;
+ }
+
+ cancel_work_sync(&priv->vq_work);
+
+ if (priv->has_reserved_mem)
+ of_reserved_mem_device_release(&pdev->dev);
+}
+
+static const struct of_device_id sunxi_rproc_of_match[] = {
+ /*
+ * A523, A527, and T527 are the same silicon die (sun55i family).
+ * Use a single compatible string per upstream DT binding policy.
+ */
+ { .compatible = "allwinner,sun55i-a523-rproc", .data = &sun55i_riscv_cfg },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sunxi_rproc_of_match);
+
+static struct platform_driver sunxi_rproc_driver = {
+ .probe = sunxi_rproc_probe,
+ .remove = sunxi_rproc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = sunxi_rproc_of_match,
+ },
+};
+module_platform_driver(sunxi_rproc_driver);
+
+MODULE_AUTHOR("Allwinner Technology Co., Ltd.");
+MODULE_AUTHOR("Tim Michals <tcmichals@gmail.com>");
+MODULE_DESCRIPTION("Allwinner XuanTie E906/E907 RISC-V Remoteproc Driver");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _SUNXI_RPROC_H_
+#define _SUNXI_RPROC_H_
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/remoteproc.h>
+#include <linux/reset.h>
+
+/* XuanTie E906/E907 core-local view of dedicated SRAM Spaces (Allwinner A523/A527/T527) */
+#define E907_SRAM_C_DA 0x00020000UL
+#define E907_SRAM_SPACE0_DA 0x3ff80000UL
+#define E907_SRAM_SPACE0_DA_ALT 0x3ffc0000UL
+#define E907_SRAM_SPACE1_DA 0x40000000UL
+#define E907_SRAM_SPACE1_DA_ALT 0x40040000UL
+
+/* Allwinner A523/A527/T527 System Bus (Host Physical) Addresses & Window Sizes */
+#define SUN55I_SRAM_SPACE0_SYS 0x07280000UL
+#define SUN55I_SRAM_SPACE0_SIZE 0x00040000UL /* 256 KB */
+#define SUN55I_SRAM_SPACE1_SYS 0x072c0000UL
+#define SUN55I_SRAM_SPACE1_SIZE 0x00040000UL /* 256 KB */
+
+/* Address Translation Table flags */
+#define ATT_IOMEM BIT(30)
+
+struct sunxi_rproc_att {
+ u64 da;
+ u64 sa;
+ size_t size;
+ int flags;
+};
+
+/* XuanTie CFG Block Register Offsets */
+#define E906_CTRL_REG 0x0000
+#define E906_STA_ADD_REG 0x0204
+
+/* Remap Control Register (offset 0x364 in PRCM_R_CCU / MCU_CCU) */
+#define SUNXI_REMAP_CTRL_OFFSET 0x0364
+/* Bit 0: 0 = local RAM for MCU; 1 = share for system */
+#define SUNXI_REMAP_MCU_RAM_BIT BIT(0)
+/* Bit 1: 0 = SRAMA3_2 not shared; 1 = share for MCU_SYS */
+#define SUNXI_REMAP_SRAMA3_2_BIT BIT(1)
+
+struct sunxi_rproc_cfg {
+ const char *name;
+ const struct sunxi_rproc_att *att;
+ size_t att_size;
+ bool has_remap_reg;
+ u32 boot_reg_offset;
+};
+
+extern const struct sunxi_rproc_cfg sun55i_riscv_cfg;
+
+struct sunxi_rproc {
+ struct rproc *rproc;
+ struct device *dev;
+ const struct sunxi_rproc_cfg *cfg;
+
+ /* CCU Clocks & Resets */
+ struct clk *clk_parent;
+ struct clk *clk_bus;
+ struct clk *clk_core;
+ struct clk *clk_sram;
+ struct clk *clk_msgbox;
+ struct reset_control *rst_cfg;
+ struct reset_control *rst_core;
+ struct reset_control *rst_sram;
+ struct reset_control *rst_msgbox;
+
+ /* Hardware Memory Windows (Dedicated SRAM, Switchable SRAM, Remap) */
+ void __iomem *cfg_va;
+ phys_addr_t cfg_phys;
+
+ void __iomem *remap_va;
+ phys_addr_t remap_phys;
+
+ void __iomem *r_sram_va;
+ phys_addr_t r_sram_phys;
+ size_t r_sram_size;
+
+ void __iomem *r_sram1_va;
+ phys_addr_t r_sram1_phys;
+ size_t r_sram1_size;
+
+ void *dram_va;
+ phys_addr_t dram_phys;
+ size_t dram_size;
+
+ /* Trace / DDR Reserved Memory Window */
+ void *trace_va;
+ phys_addr_t trace_phys;
+ size_t trace_size;
+
+ /* Reserved Memory & Mailbox State */
+ bool has_reserved_mem;
+ int crash_irq;
+ bool crash_irq_enabled;
+ struct mbox_client cl;
+ struct mbox_chan *tx_chan;
+ struct mbox_chan *rx_chan;
+ struct work_struct vq_work;
+ u32 kick_msg;
+};
+
+extern const struct rproc_ops sunxi_rproc_ops;
+
+int sunxi_rproc_prepare(struct rproc *rproc);
+int sunxi_rproc_unprepare(struct rproc *rproc);
+int sunxi_rproc_start(struct rproc *rproc);
+int sunxi_rproc_stop(struct rproc *rproc);
+void sunxi_rproc_kick(struct rproc *rproc, int vqid);
+void *sunxi_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem);
+
+#endif /* _SUNXI_RPROC_H_ */