[PATCH v3] misc: fastrpc: map ADSP remote heap into remoteproc IOMMU domain

From: Anandu Krishnan E

Date: Mon Oct 26 2026 - 14:54:37 EST


On Qualcomm platforms without Gunyah/QHEE, the kernel runs at EL2
without a separate hypervisor to manage inter-VM memory access
control. In this configuration the remoteproc is assigned its own
IOMMU domain, and any memory carveout the DSP must access requires
an explicit mapping into that domain before the DSP can reach it.

The existing code calls qcom_scm_assign_mem() to transfer ownership
of the ADSP remote heap carveout from HLOS to the DSP VM. This SCM
call is only meaningful when a separate hypervisor (e.g. Gunyah) is
present to enforce inter-VM memory access control. On platforms
without such a hypervisor, the carveout must instead be mapped into
the remoteproc's IOMMU domain via an identity mapping (IOVA == PA)
using iommu_map(). Without this mapping the DSP triggers an SMMU
translation fault when accessing the remote heap during audio PD
static process creation.

Detect whether the remoteproc has an IOMMU by checking for the
"iommus" property on the remoteproc DT node and store the result in
a new use_iommu_map flag in fastrpc_channel_ctx. When the flag is
set, map the carveout into the remoteproc's IOMMU domain instead of
calling qcom_scm_assign_mem(). Introduce fastrpc_remote_heap_map()
and fastrpc_remote_heap_unmap() helpers to encapsulate the IOMMU
domain lookup and map/unmap operations, and a shared
fastrpc_remote_heap_release() helper that tears the mapping (or SCM
assignment) back down, used both from fastrpc_rpmsg_remove() and
from fastrpc_rpmsg_probe()'s error path.

Signed-off-by: Anandu Krishnan E <anandu.e@xxxxxxxxxxxxxxxx>
---
Changes in v3:
- Rebase onto the current fastrpc-for-next tip.
- Reword the commit message's opening sentence: describe the
condition as "Qualcomm platforms without Gunyah/QHEE" rather than
"KVM-based targets".
- Fix #include <linux/iommu.h> ordering to keep the linux/* include
block alphabetically sorted.
- Extract the heap teardown logic into a new shared
fastrpc_remote_heap_release() helper and call it from both
fastrpc_rpmsg_remove() and fastrpc_rpmsg_probe()'s error path, so
that a probe failure on a Gunyah-based (non-IOMMU) target now also
reassigns the carveout back to HLOS via qcom_scm_assign_mem()
instead of leaving it assigned to the DSP VM.
- Link to v2: https://lore.kernel.org/r/20261001-fastrpc-kvm-iommu-v2-v2-1-6c32ad6f662f@xxxxxxxxxxxxxxxx
- Link to v1: https://lore.kernel.org/r/20260618-audio_fix_clean_v3-v1-1-ec1ee66fe455@xxxxxxxxxxxxxxxx
---
drivers/misc/fastrpc.c | 156 ++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 134 insertions(+), 22 deletions(-)

diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c
index 5ac7b3e78ba7..398bcd0cfab4 100644
--- a/drivers/misc/fastrpc.c
+++ b/drivers/misc/fastrpc.c
@@ -8,6 +8,7 @@
#include <linux/dma-mapping.h>
#include <linux/dma-resv.h>
#include <linux/idr.h>
+#include <linux/iommu.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
@@ -317,6 +318,8 @@ struct fastrpc_channel_ctx {
/* Audio PD reserved remote heap region */
phys_addr_t remote_heap_addr;
u64 remote_heap_size;
+ /* set when remoteproc has an IOMMU; use iommu_map instead of hyp_assign */
+ bool use_iommu_map;
u32 dsp_attributes[FASTRPC_MAX_DSP_ATTRIBUTES];
struct fastrpc_device *secure_fdevice;
struct fastrpc_device *fdevice;
@@ -2507,9 +2510,120 @@ static const struct of_device_id fastrpc_poll_supported_machines[] __maybe_unuse
{},
};

+static struct device_node *fastrpc_get_rproc_node(struct device_node *node)
+{
+ struct device_node *parent, *rproc_node;
+
+ parent = of_get_parent(node);
+ if (!parent)
+ return NULL;
+
+ rproc_node = of_get_parent(parent);
+ of_node_put(parent);
+
+ return rproc_node;
+}
+
+static int fastrpc_remote_heap_map(struct device *rdev,
+ struct device_node *rproc_node,
+ phys_addr_t addr, u64 size)
+{
+ struct platform_device *rproc_pdev;
+ struct iommu_domain *domain;
+ int ret;
+
+ rproc_pdev = of_find_device_by_node(rproc_node);
+ if (!rproc_pdev) {
+ dev_err(rdev, "failed to find remoteproc platform device\n");
+ return -ENODEV;
+ }
+
+ domain = iommu_get_domain_for_dev(&rproc_pdev->dev);
+ if (!domain) {
+ put_device(&rproc_pdev->dev);
+ dev_err(rdev, "no IOMMU domain for remoteproc\n");
+ return -ENODEV;
+ }
+
+ ret = iommu_map(domain, addr, addr, size, IOMMU_READ | IOMMU_WRITE, GFP_KERNEL);
+ if (ret)
+ dev_err(rdev, "failed to map remote heap phys=0x%llx size=0x%llx err=%d\n",
+ (u64)addr, size, ret);
+
+ put_device(&rproc_pdev->dev);
+ return ret;
+}
+
+static void fastrpc_remote_heap_unmap(struct rpmsg_device *rpdev,
+ phys_addr_t addr, u64 size)
+{
+ struct device_node *rproc_node;
+ struct platform_device *rproc_pdev;
+ struct iommu_domain *domain;
+ size_t unmapped;
+
+ rproc_node = fastrpc_get_rproc_node(rpdev->dev.of_node);
+ if (!rproc_node) {
+ dev_warn(&rpdev->dev, "failed to find remoteproc node for heap unmap\n");
+ return;
+ }
+
+ rproc_pdev = of_find_device_by_node(rproc_node);
+ of_node_put(rproc_node);
+ if (!rproc_pdev) {
+ dev_warn(&rpdev->dev, "failed to find remoteproc platform device for heap unmap\n");
+ return;
+ }
+
+ domain = iommu_get_domain_for_dev(&rproc_pdev->dev);
+ if (!domain) {
+ dev_warn(&rpdev->dev, "no IOMMU domain for remoteproc during heap unmap\n");
+ put_device(&rproc_pdev->dev);
+ return;
+ }
+
+ unmapped = iommu_unmap(domain, addr, size);
+ if (unmapped != size)
+ dev_warn(&rpdev->dev, "partial/failed heap unmap: requested=0x%llx unmapped=0x%zx\n",
+ size, unmapped);
+
+ put_device(&rproc_pdev->dev);
+}
+
+static void fastrpc_remote_heap_release(struct fastrpc_channel_ctx *cctx,
+ struct rpmsg_device *rpdev)
+{
+ if (!cctx->remote_heap_size)
+ return;
+
+ if (cctx->use_iommu_map) {
+ fastrpc_remote_heap_unmap(rpdev, cctx->remote_heap_addr,
+ cctx->remote_heap_size);
+ } else if (cctx->vmcount) {
+ u64 src_perms = 0;
+ int err, i;
+ struct qcom_scm_vmperm dst_perms;
+
+ for (i = 0; i < cctx->vmcount; i++)
+ src_perms |= BIT(cctx->vmperms[i].vmid);
+
+ dst_perms.vmid = QCOM_SCM_VMID_HLOS;
+ dst_perms.perm = QCOM_SCM_PERM_RWX;
+
+ err = qcom_scm_assign_mem(cctx->remote_heap_addr,
+ cctx->remote_heap_size, &src_perms,
+ &dst_perms, 1);
+ if (err)
+ dev_err(&rpdev->dev,
+ "Failed to assign memory back to HLOS: addr %pa size %#llx err %d\n",
+ &cctx->remote_heap_addr, cctx->remote_heap_size, err);
+ }
+}
+
static int fastrpc_init_reserved_mem(struct fastrpc_channel_ctx *cctx,
struct device *rdev, u32 domain_id)
{
+ struct device_node *rproc_node;
struct resource res;
u64 src_perms;
int err;
@@ -2536,6 +2650,19 @@ static int fastrpc_init_reserved_mem(struct fastrpc_channel_ctx *cctx,
if (domain_id == ADSP_DOMAIN_ID) {
cctx->remote_heap_addr = res.start;
cctx->remote_heap_size = resource_size(&res);
+
+ rproc_node = fastrpc_get_rproc_node(rdev->of_node);
+ if (rproc_node)
+ cctx->use_iommu_map = of_property_present(rproc_node, "iommus");
+
+ if (cctx->use_iommu_map) {
+ err = fastrpc_remote_heap_map(rdev, rproc_node, res.start,
+ resource_size(&res));
+ of_node_put(rproc_node);
+ return err;
+ }
+
+ of_node_put(rproc_node);
}

if (!cctx->vmcount)
@@ -2611,7 +2738,7 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
data->unsigned_support = false;
err = fastrpc_device_register(rdev, data, secure_dsp, domain);
if (err)
- goto err_free_data;
+ goto err_unmap_heap;
break;
case CDSP_DOMAIN_ID:
case GDSP_DOMAIN_ID:
@@ -2619,7 +2746,7 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
/* Create both device nodes so that we can allow both Signed and Unsigned PD */
err = fastrpc_device_register(rdev, data, true, domain);
if (err)
- goto err_free_data;
+ goto err_unmap_heap;

err = fastrpc_device_register(rdev, data, false, domain);
if (err)
@@ -2627,7 +2754,7 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
break;
default:
err = -EINVAL;
- goto err_free_data;
+ goto err_unmap_heap;
}

kref_init(&data->refcount);
@@ -2655,6 +2782,9 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
if (data->secure_fdevice)
misc_deregister(&data->secure_fdevice->miscdev);

+err_unmap_heap:
+ fastrpc_remote_heap_release(data, rpdev);
+
err_free_data:
kfree(data);
return err;
@@ -2695,25 +2825,7 @@ static void fastrpc_rpmsg_remove(struct rpmsg_device *rpdev)
list_for_each_entry_safe(buf, b, &cctx->invoke_interrupted_mmaps, node)
list_del(&buf->node);

- if (cctx->remote_heap_size && cctx->vmcount) {
- u64 src_perms = 0;
- int err, i;
- struct qcom_scm_vmperm dst_perms;
-
- for (i = 0; i < cctx->vmcount; i++)
- src_perms |= BIT(cctx->vmperms[i].vmid);
-
- dst_perms.vmid = QCOM_SCM_VMID_HLOS;
- dst_perms.perm = QCOM_SCM_PERM_RWX;
-
- err = qcom_scm_assign_mem(cctx->remote_heap_addr,
- cctx->remote_heap_size, &src_perms,
- &dst_perms, 1);
- if (err)
- dev_err(&rpdev->dev,
- "Failed to assign memory back to HLOS: addr %pa size %#llx err %d\n",
- &cctx->remote_heap_addr, cctx->remote_heap_size, err);
- }
+ fastrpc_remote_heap_release(cctx, rpdev);

of_platform_depopulate(&rpdev->dev);


---
base-commit: ef071c4906eb45d16b60f09154cf0bc6ec8f5435
change-id: 20261026-fastrpc-kvm-iommu-v3-40edf7bf52ce

Best regards,
--
Anandu Krishnan E <anandu.e@xxxxxxxxxxxxxxxx>