From cfc2b6a2765e43cad60f7fce9afe47faf4aa004d Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Fri, 10 Jul 2026 23:50:20 +0200 Subject: [PATCH] voice-proto: AMDB module pre-loading, longer cmd timeout Three findings from the 2026-07-10 warm-capture session (journal/calls.md): the stock voice sequence is byte-identical warm vs cold, so our replay bytes were never the problem; VCPM rejects TX-side cal (EBADPARAM) while accepting byte-identical RX cal through the same transport; and a voice GRAPH_OPEN that has to FastRPC-load the Fluence pp modules takes >120 s cold, so the client-side timeout/verify dance leaves failed opens in VCPM's session history. - apm_callback: handle AMDB_CMD_RSP_LOAD_MODULES (AMDB load responds with its own opcode, not an ibasic result); log per-module handles (handle 0 = not registered - e.g. 0x70010a3, which only appears after fluence_nn_module_fvxiii.so.1 is parsed). - voice-proto: Q6VP_DSTPORT_FLAG record flag - first payload word carries the GPR destination port, so blobs can address static services other than APM (AMDB port 3 module pre-loading via mkamdbload.py). - Raise the shared command timeout to 300 s so a cold TX voice GRAPH_OPEN (~125 s through the Fluence loads) completes inside ONE command with no retry dance. Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf --- sound/soc/qcom/qdsp6/audioreach.c | 4 +- sound/soc/qcom/qdsp6/audioreach.h | 2 + sound/soc/qcom/qdsp6/q6apm-voice-proto.c | 51 +++++++++++++++++++++--- sound/soc/qcom/qdsp6/q6apm.c | 17 ++++++++ 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index 7cb81ec586a2..12aa04e9504c 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -601,9 +601,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), 30 * HZ); + (result->opcode == rsp_opcode), 240 * HZ); else - rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode), 30 * HZ); + rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode), 240 * HZ); if (!rc) { dev_err(dev, "CMD timeout for [%x] opcode\n", hdr->opcode); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 19011c02572e..fad5f700dc86 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -65,6 +65,8 @@ struct q6apm_graph; #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 diff --git a/sound/soc/qcom/qdsp6/q6apm-voice-proto.c b/sound/soc/qcom/qdsp6/q6apm-voice-proto.c index b10e6e5214bd..0ce26f5d8f0a 100644 --- a/sound/soc/qcom/qdsp6/q6apm-voice-proto.c +++ b/sound/soc/qcom/qdsp6/q6apm-voice-proto.c @@ -44,6 +44,17 @@ * 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 */ @@ -60,6 +71,7 @@ struct q6vp_rec { __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; @@ -70,16 +82,32 @@ 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; - pkt = audioreach_alloc_apm_cmd_pkt(size, opcode, 0); + 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); - ret = q6apm_send_cmd_sync(q6vp_apm, pkt, 0); + ret = q6apm_send_cmd_sync(q6vp_apm, pkt, rsp_opcode); kfree(pkt); return ret; @@ -254,12 +282,15 @@ static int q6vp_play(const char *name) } if (opcode & Q6VP_OOB_FLAG) - ret = q6vp_send_oob(opcode & ~Q6VP_OOB_FLAG, - fw->data + off, size); + 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, size, ret); + name, i, + opcode & ~(Q6VP_OOB_FLAG | Q6VP_DSTPORT_FLAG), + size, ret); /* keep going on DSP rejections; each result is logged */ if (ret) err = ret; @@ -287,7 +318,17 @@ static ssize_t q6vp_play_write(struct file *file, const char __user *ubuf, 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; } diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 95472e6984bb..56c4c1d817e4 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -853,6 +853,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;