Skip to content

zephyr: idc: align the IDC payload to the cache line - #11334

Open
lgirdwood wants to merge 1 commit into
thesofproject:mainfrom
lgirdwood:topic/idc-remote-fixes
Open

lgirdwood wants to merge 1 commit into
thesofproject:mainfrom
lgirdwood:topic/idc-remote-fixes

Conversation

@lgirdwood

@lgirdwood lgirdwood commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Summary

struct idc_payload lives in coherent (SHARED_DATA) memory and is shared across cores, but it is a uint8_t array with no explicit alignment, allowing the linker to place it on an unaligned address. Handlers that read or write the payload as a wider type (such as the cross-core get_attribute path, which accesses a uint32_t attribute field) take an EXCCAUSE=9 (LoadStoreAlignmentCause) fault when a component runs on a secondary core on ACE 3.0 / PTL.

Align struct idc_payload to DCACHE_LINE_SIZE so that every per-core IDC payload slot is cache-line and word aligned.

Verification

Tested on Panther Lake (AAEON UP Xtreme PTL / ACE 3.0) built with LLVM + HiFi (xtensa-intel_ace30_ptl-zephyr-elf) with ffmpeg_dec running as a DP module on secondary core 1:

  • Cross-core MOD_BIND / comp_get_attribute_remote() succeeds without alignment faults.
  • Cross-core comp_prepare_remote() completes synchronously within the 10 ms IDC timeout (~0.35 ms with lazy decoder open on the DP thread).
  • End-to-end compressed playback (cplay), drain EOS, comp_reset_remote(), MOD_UNBIND, and DELETE_PIPELINE complete cleanly.

Copilot AI balanced review requested due to automatic review settings October 22, 2026 13:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

5 open issues
What changed in this PR

Fixes cross-core IDC and remote component lifecycle issues on secondary cores by addressing alignment faults, uncached/coherent memcpy faults, and blocking behavior that can stall or time out during heavy DP cold-start/reset.

Changes:

  • Aligns struct idc_payload to DCACHE_LINE_SIZE to prevent Xtensa alignment faults when reading payload fields as wider types.
  • Introduces IDC_BLOCKING_NO_TIMEOUT and implements a K_FOREVER wait path for reset completion.
  • Reworks remote component prepare to be non-blocking and adds 32-bit word copy helper for coherent (uncached) attribute payload transfers.
File Description
zephyr/​include/​rtos/​idc.h Adds IDC_BLOCKING_NO_TIMEOUT and aligns IDC payload to cache line to avoid alignment faults.
src/​include/​sof/​audio/​component_ext.h Makes remote prepare non-blocking; adds coherent 32-bit copy helper; uses it for IPC4 get_attribute.
src/​idc/​zephyr_idc.c Implements IDC_BLOCKING_NO_TIMEOUT behavior using k_p4wq_wait(..., K_FOREVER).
src/​idc/​idc.c Updates remote get_attribute handler to use local cacheable struct + 32-bit stores into coherent buffer.
posix/​include/​rtos/​idc.h Defines IDC_BLOCKING_NO_TIMEOUT for parity with Zephyr header.

Review effort: Lite

Comment thread src/idc/idc.c Outdated
Comment on lines +135 to +149
/*
* Let the module fill a local (cacheable) copy, then publish it to the
* caller's coherent buffer with aligned 32-bit stores. Passing the
* coherent buffer straight to the module makes it memcpy() a whole
* struct into the uncached alias, which faults (EXCCAUSE 9) on Xtensa.
* Only COMP_ATTR_BASE_CONFIG is supported remotely (enforced by the
* sender), so local_cfg is always the right size.
*/
ret = comp_get_attribute(ipc_dev->cd, get_attr_payload->type, &local_cfg);
if (ret < 0)
return ret;

ipc4_coherent_copy32(get_attr_payload->value, &local_cfg, sizeof(local_cfg));

return ret;
Comment thread src/idc/zephyr_idc.c Outdated
Comment on lines +192 to +198
case IDC_BLOCKING_NO_TIMEOUT:
/* wait for completion; the handler is bounded but may exceed
* CONFIG_IDC_TIMEOUT_US (see IDC_BLOCKING_NO_TIMEOUT) */
ret = k_p4wq_wait(work, K_FOREVER);
if (!ret)
ret = idc_msg_status_get(msg->core);
break;
Comment thread src/include/sof/audio/component_ext.h Outdated
Comment on lines +246 to +259
* Non-blocking: a remote component's prepare may be expensive (e.g. a DP
* decoder's one-time codec open building large trig tables -- measured
* ~580 ms for mp3 on a secondary core). Waiting for it here stalls the
* initiator core's IPC/EDF thread for that whole time, and with the
* default CONFIG_IDC_TIMEOUT_US the k_p4wq_wait() would instead time out
* mid-prepare (-EAGAIN) and race the pipeline teardown into a crash.
*
* Ordering is preserved without the wait: each core drains its IDC work
* through a single in-order p4wq worker, so this prepare completes before
* the subsequent cross-core trigger (START) that is queued behind it. For
* a compress stream the START IPC also arrives only after the app's first
* write, giving the remote prepare time to finish off the critical path.
*/
return idc_send_msg(&msg, IDC_NON_BLOCKING);
Comment thread src/idc/idc.c Outdated
if (ret < 0)
return ret;

ipc4_coherent_copy32(get_attr_payload->value, &local_cfg, sizeof(local_cfg));
Comment thread src/include/sof/audio/component_ext.h Outdated
Comment on lines +304 to +313
static inline void ipc4_coherent_copy32(void *dst, const void *src, size_t bytes)
{
volatile uint32_t *d = dst;
const volatile uint32_t *s = src;
size_t words = bytes / sizeof(uint32_t);
size_t i;

for (i = 0; i < words; i++)
d[i] = s[i];
}

@lgirdwood lgirdwood left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisit IDC update afer single core is upstream. Needs to be generic.

Comment thread src/include/sof/audio/component_ext.h Outdated
size_t words = bytes / sizeof(uint32_t);
size_t i;

for (i = 0; i < words; i++)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use a vectorized version here, there is a global call that can be reused.

Comment thread src/idc/idc.c Outdated
@@ -129,7 +132,21 @@ static int idc_get_attribute(uint32_t comp_id)
idc_payload = idc_payload_get(*idc_get(), cpu_get_id());
get_attr_payload = (struct get_attribute_remote_payload *)idc_payload;

return comp_get_attribute(ipc_dev->cd, get_attr_payload->type, get_attr_payload->value);
/*
* Let the module fill a local (cacheable) copy, then publish it to the

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect the copy is fixing a race we have somewhere else - revisit after ffmpeg only parts are merged.

Comment thread src/include/sof/audio/component_ext.h Outdated
@@ -242,7 +242,21 @@ static inline int comp_prepare_remote(struct comp_dev *dev)
struct idc_msg msg = { IDC_MSG_PREPARE,
IDC_MSG_PREPARE_EXT(dev->ipc_config.id), dev->ipc_config.core, };

return idc_send_msg(&msg, IDC_BLOCKING);
/*

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revisit after ffmpeg merge

@lgirdwood
lgirdwood marked this pull request as draft October 22, 2026 13:36
@intel-sofci

intel-sofci commented Oct 22, 2026 •

Copy link
Copy Markdown

PR 11334: test results

Run date: 2026-11-03 16:42 UTC

Tested commit: 141d71515cb4171bc6e4b5ef415d268213c1016f

mtl pass rate lnl pass rate ptl pass rate wcl pass rate nvl pass rate

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/include/sof/audio/component_ext.h Outdated
Comment on lines +255 to +257
* the subsequent cross-core trigger (START) that is queued behind it. For
* a compress stream the START IPC also arrives only after the app's first
* write, giving the remote prepare time to finish off the critical path.
Comment thread src/include/sof/audio/component_ext.h Outdated
* a compress stream the START IPC also arrives only after the app's first
* write, giving the remote prepare time to finish off the critical path.
*/
return idc_send_msg(&msg, IDC_NON_BLOCKING);
Comment thread src/include/sof/audio/component_ext.h Outdated
* a compress stream the START IPC also arrives only after the app's first
* write, giving the remote prepare time to finish off the critical path.
*/
return idc_send_msg(&msg, IDC_NON_BLOCKING);
Comment thread src/include/sof/audio/component_ext.h Outdated
* a compress stream the START IPC also arrives only after the app's first
* write, giving the remote prepare time to finish off the critical path.
*/
return idc_send_msg(&msg, IDC_NON_BLOCKING);
Comment thread zephyr/include/rtos/idc.h Outdated
Comment on lines +155 to +161
* Placed in coherent (SHARED_DATA) memory and shared across cores. The struct
* is only a byte array, so without an explicit alignment the linker may drop
* it (and every per-core slot) on an odd address. Handlers that read the
* payload as wider types -- e.g. the cross-core get_attribute path reads a
* uint32 'type' field -- then take an EXCCAUSE 9 (load/store alignment) fault.
* Align to the cache line, which is the size this payload is already built
* around (IDC_MAX_PAYLOAD_SIZE == DCACHE_LINE_SIZE * 2).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimize this to just the 1st sentence in the comment.

Comment thread src/idc/idc.c Outdated
Comment on lines +135 to +149
/*
* Let the module fill a local (cacheable) copy, then publish it to the
* caller's coherent buffer with aligned 32-bit stores. Passing the
* coherent buffer straight to the module makes it memcpy() a whole
* struct into the uncached alias, which faults (EXCCAUSE 9) on Xtensa.
* Only COMP_ATTR_BASE_CONFIG is supported remotely (enforced by the
* sender), so local_cfg is always the right size.
*/
ret = comp_get_attribute(ipc_dev->cd, get_attr_payload->type, &local_cfg);
if (ret < 0)
return ret;

ipc4_coherent_copy32(get_attr_payload->value, &local_cfg, sizeof(local_cfg));

return ret;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think this is needed, maybe a side effect of an earlier bug, lets re-test with this reverted.

Comment thread src/include/sof/audio/component_ext.h Outdated
Comment on lines +245 to +259
/*
* Non-blocking: a remote component's prepare may be expensive (e.g. a DP
* decoder's one-time codec open building large trig tables -- measured
* ~580 ms for mp3 on a secondary core). Waiting for it here stalls the
* initiator core's IPC/EDF thread for that whole time, and with the
* default CONFIG_IDC_TIMEOUT_US the k_p4wq_wait() would instead time out
* mid-prepare (-EAGAIN) and race the pipeline teardown into a crash.
*
* Ordering is preserved without the wait: each core drains its IDC work
* through a single in-order p4wq worker, so this prepare completes before
* the subsequent cross-core trigger (START) that is queued behind it. For
* a compress stream the START IPC also arrives only after the app's first
* write, giving the remote prepare time to finish off the critical path.
*/
return idc_send_msg(&msg, IDC_NON_BLOCKING);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed after optimization of mp3 init ? lets revert and retest.

Comment thread src/idc/zephyr_idc.c Outdated
/* message was sent and executed successfully, get status code */
ret = idc_msg_status_get(msg->core);
break;
case IDC_BLOCKING_NO_TIMEOUT:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if needed this patch should probably be squashed with teh previous patch.

struct idc_payload lives in coherent (SHARED_DATA) memory and is shared
across cores, but it is only a byte array with no explicit alignment, so
the linker was free to place it (and every per-core slot) on an odd
address. Handlers that read the payload as a wider type -- e.g. the
cross-core get_attribute path, which reads a uint32 type field -- then
take an EXCCAUSE 9 (load/store alignment) fault when a component runs on
a secondary core (ptl/ace30).

Align the payload to the cache line. That is the granularity the payload
is already built around (IDC_MAX_PAYLOAD_SIZE == DCACHE_LINE_SIZE * 2)
and it guarantees every per-core slot starts word-aligned.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
@lgirdwood
lgirdwood force-pushed the topic/idc-remote-fixes branch from 7411f40 to 141d715 Compare November 3, 2026 14:01
@lgirdwood lgirdwood changed the title idc: fix cross-core component attribute access, prepare, and reset zephyr: idc: align the IDC payload to the cache line Nov 3, 2026
@lgirdwood

Copy link
Copy Markdown
Member Author

Updated and retested on PTL hardware (UP Xtreme PTL / ACE 3.0, built with LLVM + HiFi and ffmpeg_dec pinned to secondary Core 1):

  1. zephyr/include/rtos/idc.h: Shortened the comment above struct idc_payload to the first sentence as requested.
  2. src/idc/idc.c (idc_Copy32): Dropped — confirmed on PTL hardware that cache-line aligning struct idc_payload is sufficient and memcpy_s() works cleanly across cores.
  3. src/include/sof/audio/component_ext.h (non-blocking comp_prepare_remote): Dropped — with avcodec_open2() deferred to the DP thread on the first process() cycle, ffmpeg_dec_prepare() on Core 1 completes synchronously in ~0.35 ms (well within IDC_TIMEOUT).
  4. src/idc/zephyr_idc.c (blocking comp_reset_remote): Dropped — verified full stream playback, drain EOS, comp_reset_remote(), MOD_UNBIND, and DELETE_PIPELINE complete cleanly without blocking reset.

@kv2019i kv2019i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. A bit puzzled by the described test sequence as this specific sequence is currently not working with DP modules assigned to non-zero cores unless you disable the os_linux_overlay.conf. But not really related to this specific PR.

Comment thread zephyr/include/rtos/idc.h
struct idc_payload {
uint8_t data[IDC_MAX_PAYLOAD_SIZE];
};
} __aligned(DCACHE_LINE_SIZE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit funky to have this in in the type definition, but seems good otherwise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still WIP btw, using latest clang with hifi.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clang linker would optimize on a byte boundary for size/perf reasons. We will get this on the userspace tests and linux configs next.

@lgirdwood
lgirdwood marked this pull request as ready for review November 3, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants