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 <jorijnvdgraaf@catcrafts.net>
This commit is contained in:
Jorijn van der Graaf 2026-07-10 23:50:20 +02:00
commit cfc2b6a276
4 changed files with 67 additions and 7 deletions

View file

@ -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);

View file

@ -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

View file

@ -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;
}

View file

@ -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;