[PATCH v2 2/3] ASoC: qcom: q6apm-dai: add VMID-based SCM assignment for mDSP buffers

Ajay Kumar Nandam <[email protected]>
Newsgroups gmane.linux.drivers.devicetree,gmane.linux.sound,gmane.linux.ports.arm.msm,gmane.linux.kernel
Message-ID <[email protected]>
On platforms such as Qualcomm Shikra, audio is served by the modem DSP
(mDSP) which runs in a stage-2 protected context. Unlike ADSP targets
where SMMU-mapped system RAM is directly accessible, the mDSP cannot
reach the PCM buffers unless they are explicitly SCM-assigned to the
appropriate Virtual Machine IDs (VMIDs). Without this assignment, audio
does not function on these platforms.

Parse the qcom,vmids DT property and, when present, SCM-assign all
memory regions to HLOS (retained as source owner, RW) plus the listed
consumer VMIDs (all RW). The two access models are mutually exclusive:
a node uses the SMMU path (iommus) or the SCM path (qcom,vmids), never
both.

Memory assignment follows the upstream pattern used by rmtfs_mem and
qcom_q6v5_pas: reserved-memory carveouts are SCM-assigned once at
probe() time and restored to HLOS-only ownership via
devm_add_action_or_reset() at device removal. For the non-reserved-mem
path (qcom,vmids without memory-region), the fixed DMA buffer is
assigned at pcm_new() time since its address is only known after
allocation.

When memory-region is present (has_reserved_mem), the data-path carveout
is attached as a DMA pool via of_reserved_mem_device_init_by_idx() and
PCM buffers allocate directly from the carveout instead of system RAM.
Buffer constraints are capped at reserved_buf_size and
snd_pcm_set_managed_buffer_all() replaces snd_pcm_set_fixed_buffer_all().

All new code paths are gated on use_scm_assign (false when qcom,vmids is
absent), ensuring existing ADSP/iommus targets are completely unaffected.

Co-developed-by: Mohit Sharma <[email protected]>
Signed-off-by: Mohit Sharma <[email protected]>
Signed-off-by: Ajay Kumar Nandam <[email protected]>
---
 sound/soc/qcom/Kconfig           |   1 +
 sound/soc/qcom/qdsp6/q6apm-dai.c | 373 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 351 insertions(+), 23 deletions(-)

diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index e6e24f3b9922..82f496e53acb 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -101,6 +101,7 @@ config SND_SOC_QDSP6_ASM_DAI
 
 config SND_SOC_QDSP6_APM_DAI
 	tristate
+	select QCOM_SCM
 	select SND_SOC_COMPRESS
 
 config SND_SOC_QDSP6_APM_LPASS_DAI
diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c
index bf1f872a09f4..19a5c62cc4e1 100644
--- a/sound/soc/qcom/qdsp6/q6apm-dai.c
+++ b/sound/soc/qcom/qdsp6/q6apm-dai.c
@@ -1,20 +1,22 @@
 // SPDX-License-Identifier: GPL-2.0
 // Copyright (c) 2021, Linaro Limited
 
-#include <linux/init.h>
+#include <linux/dma-mapping.h>
 #include <linux/err.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/init.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
-#include <sound/soc.h>
-#include <sound/soc-dapm.h>
 #include <linux/spinlock.h>
-#include <sound/pcm.h>
 #include <asm/div64.h>
 #include <asm/dma.h>
-#include <linux/dma-mapping.h>
+#include <sound/pcm.h>
 #include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
 #include "q6apm.h"
 
 #define DRV_NAME "q6apm-dai"
@@ -34,6 +36,9 @@
 #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		2
+#define Q6APM_MAX_CARVEOUTS	2
+#define Q6APM_POOL_MAX_STREAMS	8
 #define SID_MASK_DEFAULT	0xF
 
 static const struct snd_compr_codec_caps q6apm_compr_caps = {
@@ -57,6 +62,13 @@ enum stream_state {
 	Q6APM_STREAM_RUNNING,
 };
 
+struct q6apm_scm_region {
+	phys_addr_t addr;
+	size_t size;
+	u64 src_perms;
+	bool assigned;
+};
+
 struct q6apm_dai_rtd {
 	struct snd_pcm_substream *substream;
 	struct snd_compr_stream *cstream;
@@ -84,9 +96,123 @@ struct q6apm_dai_rtd {
 };
 
 struct q6apm_dai_data {
+	struct device *dev;
 	long long sid;
+	int num_vmids;
+	u32 vmids[Q6APM_MAX_VMIDS];
+	bool use_scm_assign;
+	bool has_reserved_mem;
+	size_t reserved_buf_size;
+	struct q6apm_scm_region carveout_regions[Q6APM_MAX_CARVEOUTS];
+	int num_carveouts;
 };
 
+static int q6apm_dai_scm_assign(struct q6apm_scm_region *region,
+				const struct q6apm_dai_data *pdata)
+{
+	struct qcom_scm_vmperm *dst;
+	int dst_count = 0;
+	int ret, i;
+
+	if (region->assigned)
+		return 0;
+
+	dst = kcalloc(pdata->num_vmids + 1, sizeof(*dst), GFP_KERNEL);
+	if (!dst)
+		return -ENOMEM;
+
+	dst[dst_count].vmid = QCOM_SCM_VMID_HLOS;
+	dst[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[dst_count].vmid = pdata->vmids[i];
+		dst[dst_count].perm = QCOM_SCM_PERM_RW;
+		dst_count++;
+	}
+
+	if (dst_count == 1) {
+		kfree(dst);
+		return 0;
+	}
+
+	ret = qcom_scm_assign_mem(region->addr, region->size,
+				  &region->src_perms, dst, dst_count);
+	kfree(dst);
+	if (!ret)
+		region->assigned = true;
+
+	return ret;
+}
+
+static void q6apm_dai_scm_unassign(struct q6apm_scm_region *region,
+				   const struct q6apm_dai_data *pdata)
+{
+	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->addr, region->size,
+				  &region->src_perms, &hlos, 1);
+	if (ret) {
+		/*
+		 * A failed reclaim leaves the buffer owned by the DSP VMIDs
+		 * instead of HLOS: it must not be reused. Warn loudly and keep
+		 * it marked assigned so it is never handed back to the pool.
+		 */
+		dev_err(pdata->dev,
+			"SCM unassign failed for %pa (size %zu): %d\n",
+			&region->addr, region->size, ret);
+		return;
+	}
+
+	region->assigned = false;
+	region->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS);
+}
+
+static void q6apm_dai_unassign_all(void *data)
+{
+	struct q6apm_dai_data *pdata = data;
+	int i;
+
+	for (i = 0; i < pdata->num_carveouts; i++)
+		q6apm_dai_scm_unassign(&pdata->carveout_regions[i], pdata);
+}
+
+/*
+ * Reclaim and drop the runtime carveout tracking the buffer at @addr.
+ * Only per-PCM buffers (allocated in pcm_new when there is no reserved
+ * memory pool) are removed this way, so tearing one PCM down never
+ * disturbs regions still owned by other live PCMs.
+ */
+static void q6apm_dai_scm_unassign_addr(struct q6apm_dai_data *pdata,
+					phys_addr_t addr)
+{
+	int i;
+
+	for (i = 0; i < pdata->num_carveouts; i++) {
+		if (pdata->carveout_regions[i].addr != addr)
+			continue;
+
+		q6apm_dai_scm_unassign(&pdata->carveout_regions[i], pdata);
+		if (pdata->carveout_regions[i].assigned)
+			return;	/* reclaim failed: keep tracking, warned already */
+
+		/* swap-remove the (now HLOS-owned) slot */
+		pdata->num_carveouts--;
+		pdata->carveout_regions[i] =
+			pdata->carveout_regions[pdata->num_carveouts];
+		return;
+	}
+}
+
 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 |
@@ -409,8 +535,11 @@ static int q6apm_dai_open(struct snd_soc_component *component,
 	}
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		size_t buf_max = pdata->has_reserved_mem ?
+				 pdata->reserved_buf_size : BUFFER_BYTES_MAX;
+
 		ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
-						   BUFFER_BYTES_MIN, BUFFER_BYTES_MAX);
+						   BUFFER_BYTES_MIN, buf_max);
 		if (ret < 0) {
 			dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret);
 			goto err;
@@ -431,17 +560,18 @@ static int q6apm_dai_open(struct snd_soc_component *component,
 	}
 
 	runtime->private_data = prtd;
-	runtime->dma_bytes = BUFFER_BYTES_MAX;
 	if (pdata->sid < 0)
 		prtd->phys = substream->dma_buffer.addr;
 	else
 		prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32);
 
 	if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+		size_t buf_sz = pdata->has_reserved_mem ?
+				pdata->reserved_buf_size : BUFFER_BYTES_MAX;
 		void *pos_buffer;
 
-		prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX;
-		pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX);
+		prtd->pos_phys = prtd->phys + buf_sz;
+		pos_buffer = (void *)(substream->dma_buffer.area + buf_sz);
 		prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer);
 	}
 
@@ -529,6 +659,9 @@ static int q6apm_dai_hw_params(struct snd_soc_component *component,
 	return 0;
 }
 
+static void q6apm_dai_memory_unmap(struct snd_soc_component *component,
+				   struct snd_pcm_substream *substream);
+
 static int q6apm_dai_memory_map(struct snd_soc_component *component,
 				struct snd_pcm_substream *substream,
 				int graph_id, bool is_push_pull)
@@ -549,15 +682,21 @@ 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);
 
 	if (is_push_pull) {
+		size_t buf_sz = pdata->has_reserved_mem ?
+				pdata->reserved_buf_size : BUFFER_BYTES_MAX;
+
 		if (pdata->sid < 0)
-			phys = substream->dma_buffer.addr + BUFFER_BYTES_MAX;
+			phys = substream->dma_buffer.addr + buf_sz;
 		else
-			phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32);
+			phys = (substream->dma_buffer.addr + buf_sz) | (pdata->sid << 32);
 
 		ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES);
 		if (ret < 0)
@@ -572,6 +711,7 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component,
 static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd)
 {
 	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct q6apm_dai_data *pdata = snd_soc_component_get_drvdata(component);
 	struct snd_pcm *pcm = rtd->pcm;
 	/*
 	 * Allocate one extra page as a workaround for a DSP bug where 32-bit
@@ -583,15 +723,17 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc
 	bool is_push_pull;
 	struct snd_pcm_substream *substream = NULL;
 
+	if (!pdata)
+		return -EINVAL;
+
 	graph_id = cpu_dai->driver->id;
 
 	/* Note: DSP backend dais are uni-directional ONLY(either playback or capture) */
 	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
 		substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
-	else  if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
+	else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
 		substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
 
-
 	if (substream) {
 		is_push_pull = q6apm_is_graph_in_push_pull_mode_from_id(component->dev,
 									graph_id,
@@ -599,13 +741,46 @@ 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);
+		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;
 
 		ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull);
 		if (ret)
 			return ret;
+
+		if (pdata->use_scm_assign && !pdata->has_reserved_mem) {
+			struct q6apm_scm_region *r;
+
+			if (pdata->num_carveouts >= Q6APM_MAX_CARVEOUTS) {
+				dev_err(component->dev,
+					"too many SCM carveouts (max %d)\n",
+					Q6APM_MAX_CARVEOUTS);
+				q6apm_dai_memory_unmap(component, substream);
+				return -ENOSPC;
+			}
+
+			r = &pdata->carveout_regions[pdata->num_carveouts];
+			r->addr = substream->dma_buffer.addr;
+			r->size = ALIGN(size, PAGE_SIZE);
+			r->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS);
+			ret = q6apm_dai_scm_assign(r, pdata);
+			if (ret) {
+				dev_err(component->dev,
+					"SCM assign DMA buffer failed: %d\n", ret);
+				q6apm_dai_memory_unmap(component, substream);
+				return ret;
+			}
+			pdata->num_carveouts++;
+		}
 	}
 
 	return 0;
@@ -635,15 +810,29 @@ static void q6apm_dai_memory_unmap(struct snd_soc_component *component,
 
 static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm)
 {
+	struct q6apm_dai_data *pdata = snd_soc_component_get_drvdata(component);
 	struct snd_pcm_substream *substream;
+	int i;
 
-	substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
-	if (substream)
-		q6apm_dai_memory_unmap(component, substream);
+	if (!pdata)
+		return;
+
+	/*
+	 * Reverse of pcm_new: unmap the buffer from the DSP first, then
+	 * reclaim only this PCM's carveout(s) back to HLOS. Regions owned
+	 * by other live PCMs, and the reserved-memory pool, are untouched.
+	 */
+	for (i = 0; i < SNDRV_PCM_STREAM_LAST + 1; i++) {
+		substream = pcm->streams[i].substream;
+		if (!substream)
+			continue;
 
-	substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
-	if (substream)
 		q6apm_dai_memory_unmap(component, substream);
+
+		if (pdata->use_scm_assign && !pdata->has_reserved_mem)
+			q6apm_dai_scm_unassign_addr(pdata,
+						    substream->dma_buffer.addr);
+	}
 }
 
 static int q6apm_dai_compr_open(struct snd_soc_component *component,
@@ -1017,23 +1206,161 @@ 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;
 	struct device_node *node = dev->of_node;
 	struct q6apm_dai_data *pdata;
 	struct of_phandle_args args;
-	int rc;
+	int rc, vmids, i;
 
 	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
 	if (!pdata)
 		return -ENOMEM;
 
+	pdata->dev = dev;
+
 	rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
-	if (rc < 0)
+	if (rc < 0) {
 		pdata->sid = -1;
-	else
+	} else {
 		pdata->sid = args.args[0] & SID_MASK_DEFAULT;
+		of_node_put(args.np);
+	}
+
+	vmids = of_property_count_u32_elems(node, "qcom,vmids");
+	if (vmids == -EINVAL) {
+		/* no qcom,vmids: SCM assignment not used on this target */
+		pdata->use_scm_assign = false;
+	} else if (vmids < 0) {
+		return vmids;
+	} else if (vmids == 0 || vmids > Q6APM_MAX_VMIDS) {
+		dev_err(dev, "qcom,vmids: invalid count %d (need 1..%d)\n",
+			vmids, Q6APM_MAX_VMIDS);
+		return -EINVAL;
+	} else if (pdata->sid >= 0) {
+		/*
+		 * iommus (SMMU translation) and qcom,vmids (SCM VMID
+		 * assignment) are mutually exclusive buffer-protection
+		 * schemes; the binding forbids both, reject them here too.
+		 */
+		dev_err(dev, "qcom,vmids and iommus are mutually exclusive\n");
+		return -EINVAL;
+	}
+
+	if (vmids > 0) {
+		rc = of_property_read_u32_array(node, "qcom,vmids",
+						pdata->vmids, vmids);
+		if (rc)
+			return rc;
+
+		for (i = 0; i < vmids; i++) {
+			if (pdata->vmids[i] == QCOM_SCM_VMID_HLOS) {
+				dev_err(dev,
+					"qcom,vmids must not include HLOS\n");
+				return -EINVAL;
+			}
+		}
+		pdata->num_vmids = vmids;
+		pdata->use_scm_assign = true;
+	}
+
+	if (pdata->use_scm_assign) {
+		struct device_node *mem_node;
+		struct reserved_mem *rmem;
+
+		if (!qcom_scm_is_available())
+			return -EPROBE_DEFER;
+
+		mem_node = of_parse_phandle(node, "memory-region", 0);
+		if (mem_node) {
+			rmem = of_reserved_mem_lookup(mem_node);
+			of_node_put(mem_node);
+			if (rmem) {
+				struct q6apm_scm_region *r;
+
+				r = &pdata->carveout_regions[pdata->num_carveouts++];
+				r->addr = rmem->base;
+				r->size = ALIGN(rmem->size, PAGE_SIZE);
+				r->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS);
+			}
+		}
+
+		mem_node = of_parse_phandle(node, "memory-region", 1);
+		if (mem_node) {
+			rmem = of_reserved_mem_lookup(mem_node);
+			of_node_put(mem_node);
+			if (rmem) {
+				struct q6apm_scm_region *r;
+				size_t per_stream;
+
+				/*
+				 * The data-path carveout is a shared DMA pool
+				 * from which all PCM substreams pre-allocate.
+				 * Divide evenly so concurrent streams fit,
+				 * subtracting per-stream position-buffer
+				 * overhead. Reject a pool too small to give
+				 * each stream a usable buffer (unsigned
+				 * arithmetic would otherwise wrap).
+				 */
+				per_stream = rmem->size / Q6APM_POOL_MAX_STREAMS;
+				if (per_stream <= POS_BUFFER_BYTES) {
+					dev_err(dev,
+						"reserved-memory pool too small: %llu bytes\n",
+						(u64)rmem->size);
+					return -EINVAL;
+				}
+
+				rc = of_reserved_mem_device_init_by_idx(dev, node, 1);
+				if (rc) {
+					dev_err(dev,
+						"reserved-memory pool init failed: %d\n",
+						rc);
+					return rc;
+				}
+				rc = devm_add_action_or_reset(dev,
+							      q6apm_dai_reserved_mem_release,
+							      dev);
+				if (rc)
+					return rc;
+
+				/* never exceed the per-substream buffer cap */
+				pdata->reserved_buf_size =
+					min_t(size_t,
+					      per_stream - POS_BUFFER_BYTES,
+					      BUFFER_BYTES_MAX);
+				pdata->has_reserved_mem = true;
+
+				r = &pdata->carveout_regions[pdata->num_carveouts++];
+				r->addr = rmem->base;
+				r->size = ALIGN(rmem->size, PAGE_SIZE);
+				r->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS);
+			}
+		}
+
+		if (pdata->num_carveouts) {
+			for (i = 0; i < pdata->num_carveouts; i++) {
+				rc = q6apm_dai_scm_assign(&pdata->carveout_regions[i],
+							  pdata);
+				if (rc) {
+					dev_err(dev,
+						"SCM assign carveout[%d] failed: %d\n",
+						i, rc);
+					return rc;
+				}
+			}
+			rc = devm_add_action_or_reset(dev,
+						      q6apm_dai_unassign_all,
+						      pdata);
+			if (rc)
+				return rc;
+		}
+	}
 
 	dev_set_drvdata(dev, pdata);
 

-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.