[PATCH v2] ASoC: qcom: q6apm-dai: add carveout SCM assignment for mDSP buffers
From: Ajay Kumar Nandam
Date: Wed Jun 24 2026 - 08:38:16 EST
On targets like Shikra where audio processing runs on the mDSP (modem
DSP) instead of the ADSP, the DSP operates with stage-2 secured memory.
Unlike ADSP platforms where SMMU-mapped system RAM is directly
accessible, the mDSP cannot access buffers unless they are explicitly
SCM-assigned to the appropriate VMIDs. This requires allocating PCM DMA
buffers from reserved carveout regions and performing SCM assignment to
grant both HLOS and the mDSP shared RW access.
Add support for reserved-memory carveout regions listed in the
memory-region DT property of the q6apm-dais node. When qcom,vmid is
configured, the control-path carveout (memory-region index 0) is
SCM-assigned to HLOS + the configured destination VMIDs (RW) at
pcm_new() time and restored to HLOS-only at pcm_free().
For the data-path region (memory-region index 1), the device DMA pool
is attached via of_reserved_mem_device_init_by_idx() so that PCM buffers
are allocated directly from the carveout. The per-substream buffer size
is derived from the region (capped at BUFFER_BYTES_MAX) and the same
size is used consistently for the buffer-bytes constraint, the DSP
memory map, runtime->dma_bytes, and the per-substream SCM window so all
layers see a single coherent window. Only index 0 is SCM-assigned via
the carveout list; the data-path region is assigned per-substream, so
the carveout walk stops after index 0 to avoid double-assigning the
same physical region (which TZ rejects with -EINVAL).
Use a typed reserved-memory release callback for
devm_add_action_or_reset() instead of casting
of_reserved_mem_device_release(), so the function-pointer prototype
matches and stays CFI-clean.
All paths are gated on use_scm_assign and has_reserved_mem so existing
platforms without memory-region or qcom,vmid in DT are unaffected.
Depends-on: https://lore.kernel.org/all/20260609064038.492641-1-ajay.nandam@xxxxxxxxxxxxxxxx/
Signed-off-by: Ajay Kumar Nandam <ajay.nandam@xxxxxxxxxxxxxxxx>
---
v1: https://lore.kernel.org/all/20260618112810.2009847-1-ajay.nandam@xxxxxxxxxxxxxxxx/
Changes since v1:
- Fix double SCM assignment of the data-path region: the carveout walk
now stops after index 0, since index 1+ are assigned per-substream.
Assigning the same physical region twice was rejected by TZ (-EINVAL).
- Size the per-substream buffer from the carveout (region / 4, capped at
BUFFER_BYTES_MAX) and use that same size consistently for the
buffer-bytes constraint, DSP memory map, runtime->dma_bytes and the
SCM window (v1 mixed BUFFER_BYTES_MAX with the full region size).
- Use a typed reserved-memory release callback for
devm_add_action_or_reset() instead of casting
of_reserved_mem_device_release() (CFI-clean).
- Drop a debug print that triggered a -Wformat warning (%zu vs
phys_addr_t).
sound/soc/qcom/qdsp6/q6apm-dai.c | 238 +++++++++++++++++++++++++++++--
1 file changed, 230 insertions(+), 8 deletions(-)
diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c
index f3dac2932afe..44c5ad97efae 100644
--- a/sound/soc/qcom/qdsp6/q6apm-dai.c
+++ b/sound/soc/qcom/qdsp6/q6apm-dai.c
@@ -15,6 +15,7 @@
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/of_reserved_mem.h>
#include <sound/pcm_params.h>
#include "q6apm.h"
@@ -35,7 +36,8 @@
#define COMPR_PLAYBACK_MAX_NUM_FRAGMENTS (16 * 4)
#define COMPR_PLAYBACK_MIN_FRAGMENT_SIZE (8 * 1024)
#define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4)
-#define Q6APM_MAX_VMIDS 8
+#define Q6APM_MAX_VMIDS 8
+#define Q6APM_MAX_CARVEOUTS 8
#define Q6APM_SCM_MAX_VMID 63
#define SID_MASK_DEFAULT 0xF
@@ -99,6 +101,16 @@ struct q6apm_dai_data {
int num_vmids;
u32 vmids[Q6APM_MAX_VMIDS];
bool use_scm_assign;
+ /*
+ * carveout regions from memory-region DT property
+ * (index 0: control path, index 1+: data path)
+ */
+ struct q6apm_scm_region carveout_regions[Q6APM_MAX_CARVEOUTS];
+ int num_carveouts;
+ /* true when memory-region DT property is present and DMA pool attached */
+ bool has_reserved_mem;
+ /* size of the data-path reserved region, capped at BUFFER_BYTES_MAX */
+ size_t reserved_buf_size;
};
static int q6apm_dai_assign_memory(struct q6apm_dai_rtd *prtd,
@@ -188,6 +200,102 @@ static int q6apm_dai_unassign_memory(struct snd_soc_component *component,
return ret;
}
+static int q6apm_dai_assign_one_region(struct q6apm_scm_region *region,
+ struct q6apm_dai_data *pdata)
+{
+ struct qcom_scm_vmperm *dst_vmids;
+ int dst_count = 0;
+ int ret, i;
+
+ if (region->assigned)
+ return 0;
+
+ dst_vmids = kcalloc(pdata->num_vmids + 1, sizeof(*dst_vmids),
+ GFP_KERNEL);
+ if (!dst_vmids)
+ return -ENOMEM;
+
+ /* Always keep HLOS RW so CPU can continue carveout access. */
+ dst_vmids[dst_count].vmid = QCOM_SCM_VMID_HLOS;
+ dst_vmids[dst_count].perm = QCOM_SCM_PERM_RW;
+ dst_count++;
+
+ for (i = 0; i < pdata->num_vmids; i++) {
+ if (WARN_ON_ONCE(pdata->vmids[i] == QCOM_SCM_VMID_HLOS))
+ continue;
+ dst_vmids[dst_count].vmid = pdata->vmids[i];
+ dst_vmids[dst_count].perm = QCOM_SCM_PERM_RW;
+ dst_count++;
+ }
+
+ if (dst_count == 1) {
+ /* Nothing to assign beyond HLOS access. */
+ kfree(dst_vmids);
+ return 0;
+ }
+
+ ret = qcom_scm_assign_mem(region->dma_addr, region->size,
+ ®ion->src_perms, dst_vmids, dst_count);
+ kfree(dst_vmids);
+ if (!ret)
+ region->assigned = true;
+ return ret;
+}
+
+static int q6apm_dai_assign_carveout(struct q6apm_dai_data *pdata)
+{
+ int i, ret;
+
+ if (!pdata->use_scm_assign || !pdata->num_carveouts)
+ return 0;
+
+ for (i = 0; i < pdata->num_carveouts; i++) {
+ ret = q6apm_dai_assign_one_region(&pdata->carveout_regions[i],
+ pdata);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static void q6apm_dai_unassign_one_region(struct snd_soc_component *component,
+ struct q6apm_scm_region *region)
+{
+ struct device *dev = component->dev;
+ struct qcom_scm_vmperm hlos = {
+ .vmid = QCOM_SCM_VMID_HLOS,
+ .perm = QCOM_SCM_PERM_RW,
+ };
+ int ret;
+
+ if (!region->assigned)
+ return;
+
+ ret = qcom_scm_assign_mem(region->dma_addr, region->size,
+ ®ion->src_perms, &hlos, 1);
+ if (!ret) {
+ region->assigned = false;
+ region->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS);
+ } else {
+ dev_err(dev,
+ "Failed to unassign carveout %pa from VMIDs: %d\n",
+ ®ion->dma_addr, ret);
+ }
+}
+
+static void q6apm_dai_unassign_carveout(struct snd_soc_component *component,
+ struct q6apm_dai_data *pdata)
+{
+ int i;
+
+ if (!pdata->use_scm_assign || !pdata->num_carveouts)
+ return;
+
+ for (i = 0; i < pdata->num_carveouts; i++)
+ q6apm_dai_unassign_one_region(component,
+ &pdata->carveout_regions[i]);
+}
+
static const struct snd_pcm_hardware q6apm_dai_hardware_capture = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED |
@@ -469,7 +577,8 @@ static int q6apm_dai_open(struct snd_soc_component *component,
struct device *dev = component->dev;
struct q6apm_dai_data *pdata;
struct q6apm_dai_rtd *prtd;
- unsigned int assign_size = BUFFER_BYTES_MAX + PAGE_SIZE;
+ unsigned int buf_size;
+ unsigned int assign_size;
int graph_id, ret;
graph_id = cpu_dai->driver->id;
@@ -480,6 +589,9 @@ static int q6apm_dai_open(struct snd_soc_component *component,
return -EINVAL;
}
+ buf_size = pdata->has_reserved_mem ? pdata->reserved_buf_size : BUFFER_BYTES_MAX;
+ assign_size = buf_size + PAGE_SIZE;
+
prtd = kzalloc_obj(*prtd);
if (prtd == NULL)
return -ENOMEM;
@@ -507,7 +619,7 @@ static int q6apm_dai_open(struct snd_soc_component *component,
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
- BUFFER_BYTES_MIN, BUFFER_BYTES_MAX);
+ BUFFER_BYTES_MIN, buf_size);
if (ret < 0) {
dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret);
goto err;
@@ -528,7 +640,7 @@ static int q6apm_dai_open(struct snd_soc_component *component,
}
runtime->private_data = prtd;
- runtime->dma_bytes = BUFFER_BYTES_MAX;
+ runtime->dma_bytes = buf_size;
if (pdata->sid < 0)
prtd->phys = substream->dma_buffer.addr;
else
@@ -538,8 +650,8 @@ static int q6apm_dai_open(struct snd_soc_component *component,
void *pos_buffer;
assign_size += POS_BUFFER_BYTES;
- prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX;
- pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX);
+ prtd->pos_phys = prtd->phys + buf_size;
+ pos_buffer = (void *)(substream->dma_buffer.area + buf_size);
prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer);
}
@@ -660,7 +772,9 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component,
else
phys = substream->dma_buffer.addr | (pdata->sid << 32);
- ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, BUFFER_BYTES_MAX);
+ ret = q6apm_map_memory_fixed_region(dev, graph_id, phys,
+ pdata->has_reserved_mem ?
+ pdata->reserved_buf_size : BUFFER_BYTES_MAX);
if (ret < 0)
dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
@@ -716,7 +830,19 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc
if (is_push_pull)
size += POS_BUFFER_BYTES;
- ret = snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, component->dev, size);
+ /*
+ * When a reserved DMA pool is attached (memory-region in DT), allocate
+ * PCM buffers from it so the DSP accesses the carveout address directly.
+ * Fall back to the standard fixed system-RAM buffer on other platforms.
+ */
+ if (pdata->has_reserved_mem)
+ ret = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ component->dev,
+ pdata->reserved_buf_size,
+ pdata->reserved_buf_size);
+ else
+ ret = snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ component->dev, size);
if (ret)
return ret;
@@ -725,6 +851,12 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc
return ret;
}
+ if (pdata->use_scm_assign && pdata->num_carveouts) {
+ ret = q6apm_dai_assign_carveout(pdata);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
@@ -758,6 +890,9 @@ static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_p
if (!pdata)
return;
+ if (pdata->use_scm_assign && pdata->num_carveouts)
+ q6apm_dai_unassign_carveout(component, pdata);
+
substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
if (substream)
q6apm_dai_memory_unmap(component, substream);
@@ -1157,6 +1292,11 @@ static const struct snd_soc_component_driver q6apm_fe_dai_component = {
.remove_order = SND_SOC_COMP_ORDER_EARLY,
};
+static void q6apm_dai_reserved_mem_release(void *data)
+{
+ of_reserved_mem_device_release(data);
+}
+
static int q6apm_dai_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1214,6 +1354,88 @@ static int q6apm_dai_probe(struct platform_device *pdev)
pdata->use_scm_assign = true;
}
+ /*
+ * Attach the data-path reserved memory region (index 1 in
+ * memory-region, e.g. audio_mdsp_carveout_mem on shikra) as a DMA
+ * pool so that snd_pcm_set_managed_buffer_all() allocates PCM
+ * buffers from the carveout instead of system RAM. The size is read
+ * from the DT node and capped at BUFFER_BYTES_MAX.
+ * Index 0 is the control-path carveout (SCM-assigned separately).
+ * Platforms without memory-region are completely unaffected.
+ */
+ if (of_property_present(node, "memory-region")) {
+ struct device_node *rmem_node;
+ struct reserved_mem *rmem = NULL;
+
+ /* index 1 = data path (PCM DMA buffer pool) */
+ rmem_node = of_parse_phandle(node, "memory-region", 1);
+ if (rmem_node) {
+ rmem = of_reserved_mem_lookup(rmem_node);
+ of_node_put(rmem_node);
+ }
+
+ if (rmem) {
+ rc = of_reserved_mem_device_init_by_idx(dev, node, 1);
+ if (rc) {
+ dev_err(dev,
+ "failed to attach reserved memory pool: %d\n",
+ rc);
+ return rc;
+ }
+ rc = devm_add_action_or_reset(dev,
+ q6apm_dai_reserved_mem_release,
+ dev);
+ if (rc)
+ return rc;
+ pdata->reserved_buf_size = min_t(size_t, rmem->size / 4,
+ BUFFER_BYTES_MAX);
+ pdata->has_reserved_mem = true;
+ } else {
+ dev_warn(dev,
+ "memory-region index 1 not found, using system RAM\n");
+ }
+ }
+
+ if (pdata->use_scm_assign) {
+ struct device_node *mem_node;
+ int idx = 0;
+
+ while ((mem_node = of_parse_phandle(node, "memory-region",
+ idx++))) {
+ struct reserved_mem *rmem;
+ struct q6apm_scm_region *r;
+
+ /*
+ * Only index 0 (control-path carveout) is SCM-assigned
+ * via carveout_regions[]. Index 1+ are data-path DMA
+ * pools handled per-substream by q6apm_dai_assign_memory()
+ * in open(). Including them here causes a double-assignment
+ * of the same physical region which TZ rejects with -EINVAL.
+ */
+ if (idx > 1) {
+ of_node_put(mem_node);
+ break;
+ }
+
+ if (pdata->num_carveouts >= Q6APM_MAX_CARVEOUTS) {
+ dev_warn(dev,
+ "memory-region: too many entries, ignoring rest\n");
+ of_node_put(mem_node);
+ break;
+ }
+
+ rmem = of_reserved_mem_lookup(mem_node);
+ of_node_put(mem_node);
+ if (!rmem)
+ continue;
+
+ r = &pdata->carveout_regions[pdata->num_carveouts++];
+ r->dma_addr = rmem->base;
+ r->size = ALIGN(rmem->size, PAGE_SIZE);
+ r->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS);
+ }
+ }
+
if (pdata->use_scm_assign && !qcom_scm_is_available())
return -EPROBE_DEFER;
--
2.34.1