diff --git a/arch/arm64/boot/dts/qcom/milos.dtsi b/arch/arm64/boot/dts/qcom/milos.dtsi index 288e0e658629..0f95a9976de7 100644 --- a/arch/arm64/boot/dts/qcom/milos.dtsi +++ b/arch/arm64/boot/dts/qcom/milos.dtsi @@ -548,6 +548,14 @@ no-map; }; + adsp_rpc_heap_mem: adsp-rpc-heap { + compatible = "shared-dma-pool"; + alloc-ranges = <0x0 0x0 0x0 0xffffffff>; + reusable; + alignment = <0x0 0x400000>; + size = <0x0 0xc00000>; + }; + pvm_fw_mem: pvm-fw-region@824a0000 { reg = <0x0 0x824a0000 0x0 0x100000>; no-map; @@ -1398,6 +1406,9 @@ qcom,glink-channels = "fastrpcglink-apps-dsp"; label = "adsp"; qcom,non-secure-domain; + memory-region = <&adsp_rpc_heap_mem>; + qcom,vmids = ; #address-cells = <1>; #size-cells = <0>; @@ -1422,6 +1433,8 @@ reg = <5>; iommus = <&apps_smmu 0x1005 0x0>, <&apps_smmu 0x1065 0x0>; + /* AUDIO_STATICPD bank, per downstream volcano-dsp.dtsi */ + pd-type = <2>; dma-coherent; }; diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index f3a49384586d..cb29a429af78 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -103,6 +103,13 @@ #define USER_PD (1) #define SENSORS_PD (2) +/* + * Downstream DT "pd-type" context-bank designations (volcano-dsp.dtsi). + * A static PD must be spawned on its designated context bank / SMMU + * stream or the DSP treats it as a generic signed PD. + */ +#define FASTRPC_DT_PD_TYPE_AUDIO_STATICPD 2 + #define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev) struct fastrpc_phy_page { @@ -255,6 +262,8 @@ struct fastrpc_session_ctx { int sid; bool used; bool valid; + /* downstream DT pd-type this context bank is dedicated to (0 = generic) */ + u32 pd_type; }; struct fastrpc_soc_data { @@ -314,6 +323,11 @@ struct fastrpc_user { struct kref refcount; }; +static struct fastrpc_session_ctx *fastrpc_session_alloc_pd_type( + struct fastrpc_user *fl, u32 pd_type); +static void fastrpc_session_free(struct fastrpc_channel_ctx *cctx, + struct fastrpc_session_ctx *session); + /* Extract SMMU PA from consolidated IOVA */ static inline dma_addr_t fastrpc_ipa_to_dma_addr(struct fastrpc_channel_ctx *cctx, dma_addr_t iova) { @@ -1343,7 +1357,7 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, struct fastrpc_phy_page pages[1]; char *name; int err; - bool scm_done = false; + bool scm_done = false, heap_created = false; struct { int client_id; u32 namelen; @@ -1371,11 +1385,35 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, goto err; } + /* + * The audio static PD must live on its designated context bank so the + * DSP sees its memory on the audio SMMU stream; on a generic bank the + * aDSP enforces module signing in the PD (dlopen of unsigned dynamic + * modules fails demanding a testsig). Re-route before anything is + * allocated against the session. Falls back to the already-held + * generic session if no bank is tagged in DT. + */ + if (init.namelen >= strlen("audiopd") && + !memcmp(name, "audiopd", strlen("audiopd"))) { + struct fastrpc_session_ctx *audio_sess; + + audio_sess = fastrpc_session_alloc_pd_type(fl, + FASTRPC_DT_PD_TYPE_AUDIO_STATICPD); + if (audio_sess) { + fastrpc_session_free(fl->cctx, fl->sctx); + fl->sctx = audio_sess; + } + dev_info(&fl->cctx->rpdev->dev, + "audiopd: using session sid %d (%s)\n", fl->sctx->sid, + audio_sess ? "dedicated audio CB" : "no audio CB in DT"); + } + if (!fl->cctx->remote_heap) { err = fastrpc_remote_heap_alloc(fl, fl->sctx->dev, init.memlen, &fl->cctx->remote_heap); if (err) goto err_name; + heap_created = true; /* Map if we have any heap VMIDs associated with this ADSP Static Process. */ if (fl->cctx->vmcount) { @@ -1446,8 +1484,16 @@ err_invoke: &fl->cctx->remote_heap->dma_addr, fl->cctx->remote_heap->size, err); } err_map: - fastrpc_buf_free(fl->cctx->remote_heap); - fl->cctx->remote_heap = NULL; + /* + * A heap reused from an earlier spawn is still SCM-assigned to the + * remote VMIDs; freeing it here would return protected pages to the + * allocator and a later touch crashes with a synchronous external + * abort. Keep it for the next spawn attempt. + */ + if (heap_created) { + fastrpc_buf_free(fl->cctx->remote_heap); + fl->cctx->remote_heap = NULL; + } err_name: kfree(name); err: @@ -1582,7 +1628,9 @@ static struct fastrpc_session_ctx *fastrpc_session_alloc( spin_lock_irqsave(&cctx->lock, flags); for (i = 0; i < cctx->sesscount; i++) { - if (!cctx->session[i].used && cctx->session[i].valid) { + /* dedicated static-PD context banks are assigned by pd_type only */ + if (!cctx->session[i].used && cctx->session[i].valid && + !cctx->session[i].pd_type) { cctx->session[i].used = true; session = &cctx->session[i]; /* any non-zero ID will work, session_idx + 1 is the simplest one */ @@ -1595,6 +1643,29 @@ static struct fastrpc_session_ctx *fastrpc_session_alloc( return session; } +static struct fastrpc_session_ctx *fastrpc_session_alloc_pd_type( + struct fastrpc_user *fl, u32 pd_type) +{ + struct fastrpc_channel_ctx *cctx = fl->cctx; + struct fastrpc_session_ctx *session = NULL; + unsigned long flags; + int i; + + spin_lock_irqsave(&cctx->lock, flags); + for (i = 0; i < cctx->sesscount; i++) { + if (!cctx->session[i].used && cctx->session[i].valid && + cctx->session[i].pd_type == pd_type) { + cctx->session[i].used = true; + session = &cctx->session[i]; + fl->client_id = i + 1; + break; + } + } + spin_unlock_irqrestore(&cctx->lock, flags); + + return session; +} + static void fastrpc_session_free(struct fastrpc_channel_ctx *cctx, struct fastrpc_session_ctx *session) { @@ -2251,6 +2322,9 @@ static int fastrpc_cb_probe(struct platform_device *pdev) if (of_property_read_u32(dev->of_node, "reg", &sess->sid)) dev_info(dev, "FastRPC Session ID not specified in DT\n"); + sess->pd_type = 0; + of_property_read_u32(dev->of_node, "pd-type", &sess->pd_type); + if (sessions > 0) { struct fastrpc_session_ctx *dup_sess; diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index db823177e636..aae337572be7 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -520,6 +520,54 @@ int qrtr_endpoint_post(struct qrtr_endpoint *ep, const void *data, size_t len) qrtr_node_assign(node, le32_to_cpu(pkt->server.node)); } + /* The DSPs are star-connected through this node: forward packets + * destined to another node onto that node's endpoint (e.g. the + * modem's voice stack talking to the ADSP's audio service). Only + * DATA and RESUME_TX transit; control packets keep going to the + * local ns, which does its own mesh-wide redistribution. RESUME_TX + * additionally releases this hop's flow-control token on the + * arrival link: per-hop counters advance in lockstep with the + * end-to-end ones since every packet of the flow transits here. + */ + if (cb->dst_node != qrtr_local_nid && + cb->dst_node != QRTR_NODE_BCAST && + (cb->type == QRTR_TYPE_DATA || cb->type == QRTR_TYPE_RESUME_TX)) { + struct sockaddr_qrtr from = {AF_QIPCRTR, + cb->src_node, cb->src_port}; + struct sockaddr_qrtr to = {AF_QIPCRTR, + cb->dst_node, cb->dst_port}; + struct qrtr_node *dst; + + dst = qrtr_node_lookup(cb->dst_node); + if (!dst || dst == node) { + if (dst) + qrtr_node_release(dst); + goto err; + } + + if (cb->type == QRTR_TYPE_RESUME_TX) { + struct sk_buff *clone; + + clone = skb_clone(skb, GFP_ATOMIC); + if (clone) + qrtr_tx_resume(node, clone); + } + + pr_debug("qrtr: fwd %u:%u -> %u:%u type %d len %zu\n", + cb->src_node, cb->src_port, + cb->dst_node, cb->dst_port, cb->type, size); + + if (skb_cow_head(skb, sizeof(struct qrtr_hdr_v1))) { + qrtr_node_release(dst); + goto err; + } + + qrtr_node_enqueue(dst, skb, cb->type, &from, &to); + qrtr_node_release(dst); + + return 0; + } + if (cb->type == QRTR_TYPE_RESUME_TX) { qrtr_tx_resume(node, skb); } else { diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 67267304e7e9..d650e0b0ba5b 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only snd-q6dsp-common-y := q6dsp-common.o q6dsp-lpass-ports.o q6dsp-lpass-clocks.o -snd-q6apm-y := q6apm.o audioreach.o topology.o +snd-q6apm-y := q6apm.o audioreach.o topology.o q6apm-voice-proto.o obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += snd-q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index a13f753eff98..4fea75b021e8 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -576,10 +576,11 @@ void *audioreach_alloc_graph_pkt(struct q6apm *apm, } EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt); -int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, +int audioreach_send_cmd_sync_timeout(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode) + const struct gpr_pkt *pkt, uint32_t rsp_opcode, + unsigned long timeout_s) { const struct gpr_hdr *hdr = &pkt->hdr; @@ -601,9 +602,9 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, if (rsp_opcode) rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode) || - (result->opcode == rsp_opcode), 5 * HZ); + (result->opcode == rsp_opcode), timeout_s * HZ); else - rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode), 5 * HZ); + rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode), timeout_s * HZ); if (!rc) { dev_err(dev, "CMD timeout for [%x] opcode\n", hdr->opcode); @@ -620,6 +621,16 @@ err: mutex_unlock(cmd_lock); return rc; } +EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync_timeout); + +int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, + struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, + gpr_port_t *port, wait_queue_head_t *cmd_wait, + const struct gpr_pkt *pkt, uint32_t rsp_opcode) +{ + return audioreach_send_cmd_sync_timeout(dev, gdev, result, cmd_lock, + port, cmd_wait, pkt, rsp_opcode, 30); +} EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync); int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 6859770b38a6..e52c4b564cbc 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -58,12 +58,15 @@ struct q6apm_graph; #define APM_CMD_GRAPH_FLUSH 0x01001005 #define APM_CMD_SET_CFG 0x01001006 #define APM_CMD_GET_CFG 0x01001007 +#define APM_CMD_REGISTER_CFG 0x01001008 #define APM_CMD_SHARED_MEM_MAP_REGIONS 0x0100100C #define APM_CMD_SHARED_MEM_UNMAP_REGIONS 0x0100100D #define APM_CMD_RSP_SHARED_MEM_MAP_REGIONS 0x02001001 #define APM_CMD_RSP_GET_CFG 0x02001000 #define APM_CMD_CLOSE_ALL 0x01001013 #define APM_CMD_REGISTER_SHARED_CFG 0x0100100A +#define AMDB_CMD_LOAD_MODULES 0x01001020 +#define AMDB_CMD_RSP_LOAD_MODULES 0x02001008 #define APM_MEMORY_MAP_SHMEM8_4K_POOL 3 @@ -842,6 +845,11 @@ void audioreach_graph_free_buf(struct q6apm_graph *graph); int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, const struct gpr_pkt *pkt, uint32_t rsp_opcode); +int audioreach_send_cmd_sync_timeout(struct device *dev, gpr_device_t *gdev, + struct gpr_ibasic_rsp_result_t *result, + struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, + const struct gpr_pkt *pkt, uint32_t rsp_opcode, + unsigned long timeout_s); int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, uint32_t rsp_opcode); int audioreach_set_media_format(struct q6apm_graph *graph, diff --git a/sound/soc/qcom/qdsp6/q6apm-voice-proto.c b/sound/soc/qcom/qdsp6/q6apm-voice-proto.c new file mode 100644 index 000000000000..32a2dfdd623a --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6apm-voice-proto.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Voice-call bring-up prototype for the Fairphone (Gen. 6): replay + * pre-generated APM command sequences through the q6apm GPR channel. + * + * The blobs reproduce, byte for byte, the GRAPH_OPEN/SET_CFG/PREPARE/START + * sequence the stock Android PAL/AGM/GSL stack sends to set up the + * handset voice-call graphs (mined from the device ACDB; generated by + * utilities/mkvoiceblobs.py in the bring-up repo). The modem's voice + * engine and the ADSP VCPM service wire up the vocoder themselves once + * these graphs are running. + * + * Local prototype only, NOT for upstream. Interface: + * echo > /sys/kernel/debug/q6apm-voice-proto/play + * where is relative to /lib/firmware. Playback aborts on the + * first command the DSP rejects. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audioreach.h" +#include "q6apm.h" + +#define Q6VP_MAGIC 0x50563651 /* "Q6VP" little-endian */ +#define Q6VP_VERSION 1 + +/* + * A record opcode with this bit set is sent out-of-band: the payload is + * placed in an ADSP-mapped shared-memory region and the GPR command carries + * only an apm_cmd_header referencing it. Stock GSL sends every voice + * GRAPH_OPEN this way; it is mandatory because a voice graph must be opened + * atomically as one command (all subgraphs + cross-subgraph module + * connections and control links), and the whole payload (~4.3-4.7 KB) + * exceeds the ~4 KiB in-band GPR/GLINK intent limit. Splitting the open + * per-subgraph to fit in-band makes the cross-referencing TX DevicePP + * subgraph invalid (rejected at open) — see journal/calls.md 2026-07-08. + */ +#define Q6VP_OOB_FLAG 0x80000000 +/* + * A record opcode with this bit set carries the GPR destination port in the + * first payload word (stripped before sending). Lets a blob address static + * services other than APM — e.g. AMDB (port 3) module pre-loading, so the + * dynamic Fluence pp modules can be made resident BEFORE the voice + * GRAPH_OPENs. A first open that has to FastRPC-load them can fail or block + * so long that the client-side retry dance corrupts VCPM's per-VSID voice + * session (TX cal EBADPARAM / TX START EFAILED, journal/calls.md + * 2026-07-10). In-band records only. + */ +#define Q6VP_DSTPORT_FLAG 0x40000000 +#define Q6VP_GRAPH_ID 0xf000 /* private mem-map handle owner */ +#define Q6VP_BUF_SZ 0x800000 /* 8 MiB shared region */ +#define Q6VP_SCRATCH_SZ 0x10000 /* [0, 64K): transient commands */ +#define Q6VP_SID_MASK 0xf + +struct q6vp_hdr { + __le32 magic; + __le32 version; + __le32 num_records; +} __packed; + +struct q6vp_rec { + __le32 opcode; + __le32 size; +} __packed; + +static DEFINE_MUTEX(q6vp_play_lock); +static struct q6apm *q6vp_apm; +static struct dentry *q6vp_dir; +static struct device *q6vp_dais_dev; +static struct audioreach_graph_info *q6vp_info; +static void *q6vp_buf; +static dma_addr_t q6vp_buf_iova; +static phys_addr_t q6vp_buf_dsp_addr; + +static int q6vp_send(uint32_t opcode, const void *payload, uint32_t size) +{ + uint32_t dest_port = APM_MODULE_INSTANCE_ID; + uint32_t rsp_opcode = 0; + struct gpr_pkt *pkt; + int ret; + + if (opcode & Q6VP_DSTPORT_FLAG) { + if (size < sizeof(__le32)) + return -EINVAL; + dest_port = le32_to_cpup(payload); + payload += sizeof(__le32); + size -= sizeof(__le32); + opcode &= ~Q6VP_DSTPORT_FLAG; + } + + /* AMDB load responds with its own opcode, not an ibasic result */ + if (opcode == AMDB_CMD_LOAD_MODULES) + rsp_opcode = AMDB_CMD_RSP_LOAD_MODULES; + + pkt = audioreach_alloc_cmd_pkt(size, opcode, 0, GPR_APM_MODULE_IID, + dest_port); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + memcpy((void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE, payload, size); + + /* + * Voice blobs need the long wait: a cold TX GRAPH_OPEN that has to + * FastRPC-load the Fluence pp modules takes >120 s. Regular audio + * commands (and especially the probe-time GET_SPF_STATE ready poll) + * must keep the short default or they hold the shared command mutex + * hostage at boot: a 240 s stall in apm_probe delays module loading + * past udev's worker timeout and the sound card never assembles. + */ + ret = q6apm_send_cmd_sync_timeout(q6vp_apm, pkt, rsp_opcode, 240); + kfree(pkt); + + return ret; +} + +/* + * Lazily set up the shared-memory region used for OOB commands: allocate a + * DMA buffer against the q6apm-dais child device (it carries the iommus + * mapping the ADSP expects, same as the PCM data path), register a private + * graph_info so the APM_CMD_RSP_SHARED_MEM_MAP_REGIONS callback has a slot + * to store the handle in, and map the region with the DSP. The mapping is + * kept until the module goes away. + */ +static int q6vp_oob_init(void) +{ + struct device *dev = q6vp_apm->dev; + struct of_phandle_args args; + struct platform_device *pdev; + struct device_node *np; + u64 sid = 0; + u32 graph_id = Q6VP_GRAPH_ID; + int ret; + + if (q6vp_info && q6vp_info->mem_map_handle) + return 0; + + if (!q6vp_dais_dev) { + np = of_get_compatible_child(dev->of_node, "qcom,q6apm-dais"); + if (!np) { + dev_err(dev, "voice-proto: no q6apm-dais node\n"); + return -ENODEV; + } + if (!of_parse_phandle_with_fixed_args(np, "iommus", 1, 0, + &args)) { + sid = args.args[0] & Q6VP_SID_MASK; + of_node_put(args.np); + } + pdev = of_find_device_by_node(np); + of_node_put(np); + if (!pdev) { + dev_err(dev, "voice-proto: no q6apm-dais device\n"); + return -ENODEV; + } + q6vp_dais_dev = &pdev->dev; + + q6vp_buf = dma_alloc_coherent(q6vp_dais_dev, Q6VP_BUF_SZ, + &q6vp_buf_iova, GFP_KERNEL); + if (!q6vp_buf) + return -ENOMEM; + q6vp_buf_dsp_addr = q6vp_buf_iova | (sid << 32); + } + + if (!q6vp_info) { + q6vp_info = kzalloc(sizeof(*q6vp_info), GFP_KERNEL); + if (!q6vp_info) + return -ENOMEM; + INIT_LIST_HEAD(&q6vp_info->sg_list); + q6vp_info->id = Q6VP_GRAPH_ID; + ret = idr_alloc_u32(&q6vp_apm->graph_info_idr, q6vp_info, + &graph_id, graph_id, GFP_KERNEL); + if (ret < 0) { + kfree(q6vp_info); + q6vp_info = NULL; + return ret; + } + } + + ret = q6apm_map_memory_fixed_region(q6vp_dais_dev, Q6VP_GRAPH_ID, + q6vp_buf_dsp_addr, Q6VP_BUF_SZ); + if (ret) { + dev_err(dev, "voice-proto: mem map failed (%d)\n", ret); + return ret; + } + + dev_info(dev, "voice-proto: OOB region mapped, handle 0x%x\n", + q6vp_info->mem_map_handle); + return 0; +} + +/* + * Persistent-slice bump offset: APM_CMD_REGISTER_CFG registers the payload + * with the DSP, which keeps reading that memory for as long as the config + * stays registered — those payloads must never be overwritten. They get + * bump-allocated slices above Q6VP_SCRATCH_SZ that live until reboot; + * transient commands (opens etc.) reuse the scratch slot at offset 0. + */ +static u32 q6vp_persist_off = Q6VP_SCRATCH_SZ; + +static int q6vp_send_oob(uint32_t opcode, const void *payload, uint32_t size) +{ + struct apm_cmd_header *cmd_header; + struct gpr_pkt *pkt; + u32 off = 0; + int ret; + + ret = q6vp_oob_init(); + if (ret) + return ret; + + if (opcode == APM_CMD_REGISTER_CFG) { + if (q6vp_persist_off + size > Q6VP_BUF_SZ) + return -EFBIG; + off = q6vp_persist_off; + q6vp_persist_off += ALIGN(size, SZ_4K); + } else if (size > Q6VP_SCRATCH_SZ) { + return -EFBIG; + } + + memcpy(q6vp_buf + off, payload, size); + + pkt = audioreach_alloc_apm_cmd_pkt(0, opcode, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + /* + * The region is mapped in absolute-address mode (mainline's + * q6apm_map_memory_fixed_region passes property_flag 0, unlike GSL + * which maps with IS_OFFSET_MODE and then references offset 0), so + * the header carries the full DSP-visible address of the buffer. + */ + cmd_header = (void *)pkt + GPR_HDR_SIZE; + cmd_header->payload_address_lsw = lower_32_bits(q6vp_buf_dsp_addr + off); + cmd_header->payload_address_msw = upper_32_bits(q6vp_buf_dsp_addr + off); + cmd_header->mem_map_handle = q6vp_info->mem_map_handle; + cmd_header->payload_size = size; + + ret = q6apm_send_cmd_sync_timeout(q6vp_apm, pkt, 0, 240); + kfree(pkt); + + return ret; +} + +static int q6vp_play(const char *name) +{ + const struct firmware *fw; + const struct q6vp_hdr *hdr; + const struct q6vp_rec *rec; + struct device *dev = q6vp_apm->dev; + size_t off; + uint32_t i, num, opcode, size; + int ret, err = 0; + + ret = request_firmware(&fw, name, dev); + if (ret) { + dev_err(dev, "voice-proto: cannot load %s (%d)\n", name, ret); + return ret; + } + + hdr = (const struct q6vp_hdr *)fw->data; + if (fw->size < sizeof(*hdr) || + le32_to_cpu(hdr->magic) != Q6VP_MAGIC || + le32_to_cpu(hdr->version) != Q6VP_VERSION) { + dev_err(dev, "voice-proto: %s: bad header\n", name); + ret = -EINVAL; + goto out; + } + + num = le32_to_cpu(hdr->num_records); + off = sizeof(*hdr); + for (i = 0; i < num; i++) { + if (off + sizeof(*rec) > fw->size) { + ret = -EINVAL; + goto out; + } + rec = (const struct q6vp_rec *)(fw->data + off); + opcode = le32_to_cpu(rec->opcode); + size = le32_to_cpu(rec->size); + off += sizeof(*rec); + if (off + size > fw->size) { + ret = -EINVAL; + goto out; + } + + if (opcode & Q6VP_OOB_FLAG) + ret = (opcode & Q6VP_DSTPORT_FLAG) ? -EINVAL : + q6vp_send_oob(opcode & ~Q6VP_OOB_FLAG, + fw->data + off, size); + else + ret = q6vp_send(opcode, fw->data + off, size); + dev_info(dev, "voice-proto: %s[%u] opcode 0x%08x size %u -> %d\n", + name, i, + opcode & ~(Q6VP_OOB_FLAG | Q6VP_DSTPORT_FLAG), + size, ret); + /* keep going on DSP rejections; each result is logged */ + if (ret) + err = ret; + + off += ALIGN(size, 4); + } + +out: + release_firmware(fw); + return ret ? ret : err; +} + +static ssize_t q6vp_play_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + char name[128]; + int ret; + + if (!q6vp_apm) + return -ENODEV; + if (count >= sizeof(name)) + return -EINVAL; + if (copy_from_user(name, ubuf, count)) + return -EFAULT; + name[count] = '\0'; + strim(name); + + /* + * Serialize blob playback: concurrent debugfs writers (e.g. a + * dropped ssh session whose write is still stuck in a DSP wait) + * would interleave their records on the GPR channel and corrupt + * both sequences (seen 2026-07-10). + */ + ret = mutex_lock_interruptible(&q6vp_play_lock); + if (ret) + return ret; + ret = q6vp_play(name); + mutex_unlock(&q6vp_play_lock); + + return ret ? ret : count; +} + +static const struct file_operations q6vp_play_fops = { + .open = simple_open, + .write = q6vp_play_write, + .llseek = default_llseek, +}; + +void q6apm_voice_proto_init(struct q6apm *apm) +{ + q6vp_apm = apm; + q6vp_dir = debugfs_create_dir("q6apm-voice-proto", NULL); + debugfs_create_file("play", 0200, q6vp_dir, NULL, &q6vp_play_fops); +} + +void q6apm_voice_proto_exit(void) +{ + debugfs_remove_recursive(q6vp_dir); + q6vp_dir = NULL; + + if (q6vp_info) { + if (q6vp_info->mem_map_handle) + q6apm_unmap_memory_fixed_region(q6vp_dais_dev, + Q6VP_GRAPH_ID); + idr_remove(&q6vp_apm->graph_info_idr, Q6VP_GRAPH_ID); + kfree(q6vp_info); + q6vp_info = NULL; + } + if (q6vp_buf) { + dma_free_coherent(q6vp_dais_dev, Q6VP_BUF_SZ, q6vp_buf, + q6vp_buf_iova); + q6vp_buf = NULL; + } + q6vp_dais_dev = NULL; + q6vp_apm = NULL; +} diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 2ab378fb5032..6e0562e600dd 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -38,6 +38,16 @@ int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, NULL, &apm->wait, pkt, rsp_opcode); } +int q6apm_send_cmd_sync_timeout(struct q6apm *apm, const struct gpr_pkt *pkt, + uint32_t rsp_opcode, unsigned long timeout_s) +{ + gpr_device_t *gdev = apm->gdev; + + return audioreach_send_cmd_sync_timeout(&gdev->dev, gdev, &apm->result, + &apm->lock, NULL, &apm->wait, + pkt, rsp_opcode, timeout_s); +} + static struct audioreach_graph *q6apm_get_audioreach_graph(struct q6apm *apm, uint32_t graph_id) { struct audioreach_graph_info *info; @@ -764,6 +774,8 @@ static int apm_probe(gpr_device_t *gdev) g_apm = apm; + q6apm_voice_proto_init(apm); + q6apm_get_apm_state(apm); ret = snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0); @@ -781,6 +793,7 @@ static int apm_probe(gpr_device_t *gdev) static void apm_remove(gpr_device_t *gdev) { + q6apm_voice_proto_exit(); of_platform_depopulate(&gdev->dev); snd_soc_unregister_component(&gdev->dev); } @@ -817,6 +830,7 @@ static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op) case GPR_BASIC_RSP_RESULT: switch (result->opcode) { case APM_CMD_SHARED_MEM_MAP_REGIONS: + case APM_CMD_REGISTER_CFG: case APM_CMD_GRAPH_START: case APM_CMD_GRAPH_OPEN: case APM_CMD_GRAPH_PREPARE: @@ -849,6 +863,23 @@ static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op) break; } break; + case AMDB_CMD_RSP_LOAD_MODULES: { + /* amdb_module_load_unload_t: proc_domain, error_code, + * num_modules, {module_id, handle_lsw, handle_msw}[]. + * A zero handle = module not registered with the AMDB. + */ + const u32 *amdb = data->payload; + u32 i, n = amdb[2]; + + for (i = 0; i < n; i++) + dev_info(dev, "AMDB load: module 0x%08x handle 0x%08x%08x\n", + amdb[3 + 3 * i], amdb[5 + 3 * i], + amdb[4 + 3 * i]); + apm->result.opcode = hdr->opcode; + apm->result.status = amdb[1]; + wake_up(&apm->wait); + break; + } case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS: apm->result.opcode = hdr->opcode; apm->result.status = 0; diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 376a36700c53..f8a66ff5f29f 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -144,6 +144,8 @@ int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id); /* Helpers */ int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, uint32_t rsp_opcode); +int q6apm_send_cmd_sync_timeout(struct q6apm *apm, const struct gpr_pkt *pkt, + uint32_t rsp_opcode, unsigned long timeout_s); /* Callback for graph specific */ struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, @@ -157,4 +159,8 @@ int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples); int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, uint32_t codec_id); int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir); + +/* FP6 voice-call bring-up prototype (q6apm-voice-proto.c) */ +void q6apm_voice_proto_init(struct q6apm *apm); +void q6apm_voice_proto_exit(void); #endif /* __APM_GRAPH_ */