* [PATCH 1/2] dt-bindings: dma: qcom,bam-dma: Add optional qcom,vmid property
2026-07-14 5:00 [PATCH 0/2] dmaengine: qcom: bam_dma: SCM-assign descriptor FIFOs to a remote VMID Vishnu Santhosh
@ 2026-07-14 5:00 ` Vishnu Santhosh
2026-07-14 5:00 ` [PATCH 2/2] dmaengine: qcom: bam_dma: SCM-assign descriptor FIFOs to a remote VMID Vishnu Santhosh
1 sibling, 0 replies; 4+ messages in thread
From: Vishnu Santhosh @ 2026-07-14 5:00 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andy Gross, Bjorn Andersson
Cc: linux-arm-msm, dmaengine, devicetree, linux-kernel,
Vishnu Santhosh, chris.lew, Deepak Kumar Singh
A SoC can have multiple BAM DMA instances. Some of these BAMs are
powered by a remote processor that enforces XPU (eXternal Protection
Unit) access control and reads the per-channel descriptor FIFOs as an
AXI master under that remote processor's execution environment, so
their FIFOs must be accessible to the remote processor's VMID; other
BAM instances on the same SoC are not behind such a remote processor
and must not have this property set.
Add an optional qcom,vmid property listing the destination VMID(s)
that the affected BAM instance's descriptor FIFOs must be accessible
to. HLOS is always the source owner and must not be listed.
Co-developed-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
Signed-off-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
Signed-off-by: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
---
.../devicetree/bindings/dma/qcom,bam-dma.yaml | 26 ++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml
index 0923fb189ada9ee435144e8490c64ecb81edc57d..d256340d3f32e81eaa8c1e275c2a99aac888aa90 100644
--- a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml
+++ b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml
@@ -12,6 +12,15 @@ maintainers:
allOf:
- $ref: dma-controller.yaml#
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: qcom,shikra-bam-dma
+ then:
+ properties:
+ qcom,vmid: false
properties:
compatible:
@@ -29,6 +38,8 @@ properties:
- enum:
# SDM845, SM6115, SM8150, SM8250 and QCM2290
- qcom,bam-v1.7.4
+ # Shikra
+ - qcom,shikra-bam-dma
- const: qcom,bam-v1.7.0
clocks:
@@ -81,6 +92,21 @@ properties:
Indicates that the bam is powered up by a remote processor but must be
initialized by the local processor.
+ qcom,vmid:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 1
+ maxItems: 8
+ items:
+ minimum: 1
+ maximum: 63
+ description:
+ Destination VMIDs of the remote processor(s) that read the per-channel
+ descriptor FIFOs as an AXI master. When present, the driver SCM-assigns
+ each FIFO to these VMIDs so the remote access does not trigger an XPU
+ violation. HLOS is always retained as the source owner and must not be
+ listed. Optional even when the qcom,shikra-bam-dma compatible is
+ present; not valid on any other compatible in this schema.
+
reg:
maxItems: 1
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 2/2] dmaengine: qcom: bam_dma: SCM-assign descriptor FIFOs to a remote VMID
2026-07-14 5:00 [PATCH 0/2] dmaengine: qcom: bam_dma: SCM-assign descriptor FIFOs to a remote VMID Vishnu Santhosh
2026-07-14 5:00 ` [PATCH 1/2] dt-bindings: dma: qcom,bam-dma: Add optional qcom,vmid property Vishnu Santhosh
@ 2026-07-14 5:00 ` Vishnu Santhosh
2026-07-14 5:16 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Vishnu Santhosh @ 2026-07-14 5:00 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andy Gross, Bjorn Andersson
Cc: linux-arm-msm, dmaengine, devicetree, linux-kernel,
Vishnu Santhosh, chris.lew, Deepak Kumar Singh
On Qualcomm SoCs where the BAM is powered by a remote processor that
enforces XPU access control (e.g. the mDSP on Shikra, VMID 43 / NAV),
the BAM reads the descriptor FIFO as an AXI master under the remote
execution environment. Without an SCM grant for the remote VMID, the
first descriptor enqueue faults with an XPU violation.
Parse the optional qcom,vmid DT property as a list of destination
VMIDs. When present, SCM-assign each channel's descriptor FIFO to HLOS
plus the listed VMIDs; num_vmids being non-zero is derived purely from
qcom,vmid, a board-integration property, not from the per-SoC IP data,
and is distinct from qcom,powered-remotely.
A BAM with configured VMIDs has two properties that shape the channel
lifecycle:
1. The remote firmware owns the BAM's power and reset. It may remove
power during error recovery before the driver releases its
channels, so any pipe/block register access at teardown can raise
a synchronous external abort, and a local reset is redundant as
the remote re-initialises the hardware on the next power-on.
2. TZ does not revoke the SCM grant when the remote powers down. A
FIFO assigned once stays assigned across every power cycle, and
re-assigning or reclaiming it while the remote is mid-teardown is
rejected by TZ with -EINVAL.
Handle both by keeping the descriptor FIFO as a persistent resource on
such BAMs: allocate and SCM-assign it once on the first
bam_alloc_chan(), keep it (and its grant) across power cycles, and
reclaim it to HLOS and free it only once in bam_dma_remove(). If the
final reclaim fails the remote still has access, so the buffer is
leaked rather than returned to the page allocator; the source-VMID
bitmask stored by qcom_scm_assign_mem() drives that reclaim.
bam_free_chan() on such a BAM therefore only drops local channel state
(clear ->initialized, decrement active_channels) with no MMIO and no
SCM call, so the block and pipe are re-initialised on the next
power-on while power is present. The bam_chan_init_hw() pipe reset in
bam_dma_terminate_all() is likewise skipped. reclaiming the FIFO is an
SCM call, not a register access, so bam_dma_remove() stays safe.
Platforms that do not set qcom,vmid keep num_vmids 0, make no SCM
call, and leave the alloc/free and register-access paths unchanged.
Co-developed-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
Signed-off-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
Signed-off-by: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
---
drivers/dma/qcom/Kconfig | 1 +
drivers/dma/qcom/bam_dma.c | 197 +++++++++++++++++++++++++++++++++++++++++----
2 files changed, 182 insertions(+), 16 deletions(-)
diff --git a/drivers/dma/qcom/Kconfig b/drivers/dma/qcom/Kconfig
index c71b0b5d536bdd5242df645454d8dc673a2ea659..8f20b33bce84de5fc48caa7411258c69b5b7e79f 100644
--- a/drivers/dma/qcom/Kconfig
+++ b/drivers/dma/qcom/Kconfig
@@ -15,6 +15,7 @@ config QCOM_BAM_DMA
depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM)
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
+ select QCOM_SCM
help
Enable support for the QCOM BAM DMA controller. This controller
provides DMA capabilities for a variety of on-chip devices.
diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c
index 1bb26af0405f3a16f97e0d4b86c945c252d97f57..389ca1a9d70e814b722bcc51f0433ce0214a2042 100644
--- a/drivers/dma/qcom/bam_dma.c
+++ b/drivers/dma/qcom/bam_dma.c
@@ -42,6 +42,7 @@
#include <linux/pm_runtime.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
+#include <linux/firmware/qcom/qcom_scm.h>
#include "../dmaengine.h"
#include "../virt-dma.h"
@@ -389,6 +390,9 @@ struct bam_chan {
struct bam_desc_hw *fifo_virt;
dma_addr_t fifo_phys;
+ /* SCM source-VMID bitmask of the FIFO, 0 if not SCM-assigned */
+ u64 fifo_src_perms;
+
/* fifo markers */
unsigned short head; /* start of active descriptor entries */
unsigned short tail; /* end of active descriptor entries */
@@ -423,6 +427,10 @@ struct bam_device {
const struct reg_offset_data *layout;
+ /* destination VMIDs for SCM assignment of descriptor FIFOs */
+ u32 *vmids;
+ int num_vmids;
+
struct clk *bamclk;
int irq;
@@ -559,6 +567,126 @@ static void bam_chan_init_hw(struct bam_chan *bchan,
bchan->tail = 0;
}
+/**
+ * bam_parse_vmids - Parse the optional qcom,vmid property
+ * @bdev: bam device
+ *
+ * Reads the list of destination VMIDs from qcom,vmid, if present. HLOS is
+ * always the source owner and must not be listed. Absent property leaves
+ * num_vmids 0.
+ */
+static int bam_parse_vmids(struct bam_device *bdev)
+{
+ struct device *dev = bdev->dev;
+ int n, i, ret;
+
+ n = of_property_count_u32_elems(dev->of_node, "qcom,vmid");
+ if (n == -EINVAL)
+ return 0;
+
+ if (n < 0)
+ return n;
+
+ if (!qcom_scm_is_available())
+ return -EPROBE_DEFER;
+
+ bdev->vmids = devm_kcalloc(dev, n, sizeof(*bdev->vmids), GFP_KERNEL);
+ if (!bdev->vmids)
+ return -ENOMEM;
+
+ ret = of_property_read_u32_array(dev->of_node, "qcom,vmid",
+ bdev->vmids, n);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < n; i++) {
+ if (bdev->vmids[i] == QCOM_SCM_VMID_HLOS) {
+ dev_err(dev, "qcom,vmid must not include HLOS (%u)\n",
+ QCOM_SCM_VMID_HLOS);
+ return -EINVAL;
+ }
+ }
+
+ bdev->num_vmids = n;
+
+ return 0;
+}
+
+/**
+ * bam_assign_fifo - SCM-assign a channel's descriptor FIFO to the remote VMIDs
+ * @bdev: bam device
+ * @bchan: bam channel
+ *
+ * Grants HLOS plus the configured qcom,vmid VMIDs access to the FIFO, so
+ * the remote EE can read it. The updated source-VMID bitmask is stored in
+ * bchan->fifo_src_perms for bam_fifo_can_free() to reverse.
+ */
+static int bam_assign_fifo(struct bam_device *bdev, struct bam_chan *bchan)
+{
+ struct qcom_scm_vmperm *dst __free(kfree) = NULL;
+ u64 src = BIT_ULL(QCOM_SCM_VMID_HLOS);
+ int i, ret;
+
+ if (!bdev->num_vmids)
+ return 0;
+
+ dst = kcalloc(bdev->num_vmids + 1, sizeof(*dst), GFP_KERNEL);
+ if (!dst)
+ return -ENOMEM;
+
+ dst[0].vmid = QCOM_SCM_VMID_HLOS;
+ dst[0].perm = QCOM_SCM_PERM_RW;
+
+ for (i = 0; i < bdev->num_vmids; i++) {
+ dst[i + 1].vmid = bdev->vmids[i];
+ dst[i + 1].perm = QCOM_SCM_PERM_RW;
+ }
+
+ ret = qcom_scm_assign_mem(bchan->fifo_phys, BAM_DESC_FIFO_SIZE,
+ &src, dst, bdev->num_vmids + 1);
+ if (ret) {
+ dev_err(bdev->dev, "SCM assign fifo chan %u failed: %d\n",
+ bchan->id, ret);
+ return ret;
+ }
+
+ bchan->fifo_src_perms = src;
+
+ return 0;
+}
+
+/**
+ * bam_fifo_can_free - Reclaim a channel's descriptor FIFO to HLOS
+ * @bdev: bam device
+ * @bchan: bam channel
+ *
+ * Returns true if the FIFO may now be freed. On SCM failure the remote VMID
+ * still has access, so the caller must leak the buffer instead of freeing it.
+ */
+static bool bam_fifo_can_free(struct bam_device *bdev, struct bam_chan *bchan)
+{
+ struct qcom_scm_vmperm hlos = {
+ .vmid = QCOM_SCM_VMID_HLOS,
+ .perm = QCOM_SCM_PERM_RW,
+ };
+ int ret;
+
+ if (!bchan->fifo_src_perms)
+ return true;
+
+ ret = qcom_scm_assign_mem(bchan->fifo_phys, BAM_DESC_FIFO_SIZE,
+ &bchan->fifo_src_perms, &hlos, 1);
+ if (ret) {
+ dev_err(bdev->dev, "SCM reclaim fifo chan %u failed: %d; leaking\n",
+ bchan->id, ret);
+ return false;
+ }
+
+ bchan->fifo_src_perms = 0;
+
+ return true;
+}
+
/**
* bam_alloc_chan - Allocate channel resources for DMA channel.
* @chan: specified channel
@@ -570,16 +698,29 @@ static int bam_alloc_chan(struct dma_chan *chan)
struct bam_chan *bchan = to_bam_chan(chan);
struct bam_device *bdev = bchan->bdev;
- if (bchan->fifo_virt)
- return 0;
-
- /* allocate FIFO descriptor space, but only if necessary */
- bchan->fifo_virt = dma_alloc_wc(bdev->dev, BAM_DESC_FIFO_SIZE,
- &bchan->fifo_phys, GFP_KERNEL);
-
+ /*
+ * Remote-owned BAMs keep the FIFO allocated and SCM-assigned to the
+ * remote VMID across power cycles (see bam_free_chan), so allocate and
+ * assign it only once; the block and pipe are still re-initialised on
+ * every power-on below.
+ */
if (!bchan->fifo_virt) {
- dev_err(bdev->dev, "Failed to allocate desc fifo\n");
- return -ENOMEM;
+ /* allocate FIFO descriptor space, but only if necessary */
+ bchan->fifo_virt = dma_alloc_wc(bdev->dev, BAM_DESC_FIFO_SIZE,
+ &bchan->fifo_phys, GFP_KERNEL);
+ if (!bchan->fifo_virt) {
+ dev_err(bdev->dev, "Failed to allocate desc fifo\n");
+ return -ENOMEM;
+ }
+
+ if (bam_assign_fifo(bdev, bchan)) {
+ dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE,
+ bchan->fifo_virt, bchan->fifo_phys);
+ bchan->fifo_virt = NULL;
+ return -EIO;
+ }
+ } else if (!bdev->num_vmids) {
+ return 0;
}
if (bdev->active_channels++ == 0 && bdev->powered_remotely)
@@ -613,12 +754,29 @@ static void bam_free_chan(struct dma_chan *chan)
goto err;
}
+ /*
+ * Remote-owned BAMs (qcom,vmid) keep the descriptor FIFO allocated and
+ * SCM-assigned across power cycles: the remote may already have cut
+ * power, so pipe-register access would fault, and TZ still holds the
+ * grant for the next restart (the FIFO is reclaimed and freed once in
+ * bam_dma_remove). Only drop local channel state here so the block and
+ * pipe are re-initialised on the next power-on; skip all MMIO.
+ */
+ if (bdev->num_vmids) {
+ scoped_guard(spinlock_irqsave, &bchan->vc.lock)
+ bchan->initialized = 0;
+ bdev->active_channels--;
+ goto err;
+ }
+
scoped_guard(spinlock_irqsave, &bchan->vc.lock)
bam_reset_channel(bchan);
- dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, bchan->fifo_virt,
- bchan->fifo_phys);
+ if (bam_fifo_can_free(bdev, bchan))
+ dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE,
+ bchan->fifo_virt, bchan->fifo_phys);
bchan->fifo_virt = NULL;
+ bdev->active_channels--;
/* mask irq for pipe/channel */
val = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE));
@@ -628,7 +786,7 @@ static void bam_free_chan(struct dma_chan *chan)
/* disable irq */
writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_IRQ_EN));
- if (--bdev->active_channels == 0 && bdev->powered_remotely) {
+ if (bdev->active_channels == 0 && bdev->powered_remotely) {
/* s/w reset bam */
val = readl_relaxed(bam_addr(bdev, 0, BAM_CTRL));
val |= BAM_SW_RST;
@@ -767,7 +925,9 @@ static int bam_dma_terminate_all(struct dma_chan *chan)
if (!list_empty(&bchan->desc_list)) {
async_desc = list_first_entry(&bchan->desc_list,
struct bam_async_desc, desc_node);
- bam_chan_init_hw(bchan, async_desc->dir);
+ /* Remote-owned BAM: pipe reset may fault, skip it. */
+ if (!bchan->bdev->num_vmids)
+ bam_chan_init_hw(bchan, async_desc->dir);
}
list_for_each_entry_safe(async_desc, tmp,
@@ -1282,6 +1442,10 @@ static int bam_dma_probe(struct platform_device *pdev)
bdev->powered_remotely = of_property_read_bool(pdev->dev.of_node,
"qcom,powered-remotely");
+ ret = bam_parse_vmids(bdev);
+ if (ret)
+ return ret;
+
if (bdev->controlled_remotely || bdev->powered_remotely)
bdev->bamclk = devm_clk_get_optional(bdev->dev, "bam_clk");
else
@@ -1418,9 +1582,10 @@ static void bam_dma_remove(struct platform_device *pdev)
if (!bdev->channels[i].fifo_virt)
continue;
- dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE,
- bdev->channels[i].fifo_virt,
- bdev->channels[i].fifo_phys);
+ if (bam_fifo_can_free(bdev, &bdev->channels[i]))
+ dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE,
+ bdev->channels[i].fifo_virt,
+ bdev->channels[i].fifo_phys);
}
tasklet_kill(&bdev->task);
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread