Skip to content

ipc4: logging: make the mtrace hook safe in any context - #11332

Open
serhiy-katsyuba-intel wants to merge 1 commit into
thesofproject:mainfrom
serhiy-katsyuba-intel:mtrace_log_hook_spinlock
Open

serhiy-katsyuba-intel wants to merge 1 commit into
thesofproject:mainfrom
serhiy-katsyuba-intel:mtrace_log_hook_spinlock

Conversation

@serhiy-katsyuba-intel

Copy link
Copy Markdown
Contributor

The mtrace log backend calls the hook with its own spinlock held, so the hook must not sleep, but a k_mutex may sleep under contention. Replace the mutex with a spinlock. Nothing done under the lock sleeps: the IPC notification and the delayed log work are only queued.

On a fatal error the hook is also called from the exception handler: coredump() calls LOG_PANIC() to flush the pending logs, and it does so before k_sys_fatal_error_handler() runs, so a flag set there would be too late. Two problems showed up in this context:

  • k_mutex_lock() asserts in ISR context, so every DSP fatal error was hidden behind a secondary assertion.
  • Queueing the IPC notification or the log work takes the work queue lock, which the faulting code may hold, e.g. on an assertion in k_work_reschedule_for_queue(). The DSP then deadlocked after the first log line.

Return early from the hook when k_is_in_isr() is true. The log data is already in the buffer, and the host reads the final write pointer when it receives the EXCEPTION_CAUGHT notification.

The mtrace log backend calls the hook with its own spinlock held, so
the hook must not sleep, but a k_mutex may sleep under contention.
Replace the mutex with a spinlock. Nothing done under the lock sleeps:
the IPC notification and the delayed log work are only queued.

On a fatal error the hook is also called from the exception handler:
coredump() calls LOG_PANIC() to flush the pending logs, and it does so
before k_sys_fatal_error_handler() runs, so a flag set there would be
too late. Two problems showed up in this context:

- k_mutex_lock() asserts in ISR context, so every DSP fatal error was
  hidden behind a secondary assertion.
- Queueing the IPC notification or the log work takes the work queue
  lock, which the faulting code may hold, e.g. on an assertion in
  k_work_reschedule_for_queue(). The DSP then deadlocked after the
  first log line.

Return early from the hook when k_is_in_isr() is true. The log data is
already in the buffer, and the host reads the final write pointer when
it receives the EXCEPTION_CAUGHT notification.

Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 12:00

@lgirdwood lgirdwood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good find, hopefully we should be able to remove more Zephyr API wrapping soon with more native API usage.
@kv2019i good for userspace, I do remember some locking updates ?

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

2 open issues
What changed in this PR

Makes the IPC4 mtrace log hook safe when invoked under the mtrace backend spinlock and during fatal exception log flushing, avoiding sleeping locks and deadlocks.

Changes:

  • Replace the hook’s k_mutex with a k_spinlock to avoid sleeping under contention.
  • Bail out early from the hook when running in ISR context to avoid deadlocking on work/IPC queue locks during fault handling.
  • Use the same spinlock in the delayed work handler for consistent serialization.
File Description
src/​ipc/​ipc4/​logging.c Switches log hook serialization from mutex to spinlock and adds ISR early-return to prevent fault-path deadlocks.

Review effort: Lite

Comment thread src/ipc/ipc4/logging.c
Comment on lines +103 to +107
key = k_spin_lock(&log_lock);

mtrace_log_hook_unlocked(written, space_left);

k_mutex_unlock(&log_mutex);
k_spin_unlock(&log_lock, key);
Comment thread src/ipc/ipc4/logging.c
Comment on lines 95 to 108
static void mtrace_log_hook(size_t written, size_t space_left)
{
k_mutex_lock(&log_mutex, K_FOREVER);
k_spinlock_key_t key;

/* fatal exception log flush: IPC and work queue won't run anyway, their locks may be held */
if (k_is_in_isr())
return;

key = k_spin_lock(&log_lock);

mtrace_log_hook_unlocked(written, space_left);

k_mutex_unlock(&log_mutex);
k_spin_unlock(&log_lock, key);
}
@intel-sofci

Copy link
Copy Markdown

PR 11332: test results

Run date: 2026-10-22 12:33 UTC

Tested commit: c2511a9b2fe73dad008d85746faba293937c1920

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

@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, very interesting. It seems the mutex is fairly recent (2025), see commit 6225be5 . This could explain issues with panic dumps (and mtrace). This is not run in user-space context, so using spinlocks is ok.

Comment thread src/ipc/ipc4/logging.c

/* fatal exception log flush: IPC and work queue won't run anyway, their locks may be held */
if (k_is_in_isr())
return;

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.

Oh, can you @serhiy-katsyuba-intel see how this is handled in other backends (in Zephyr), and whether this is safe for immediate mode logging? IOW, are we really, really sure this is called in ISR only in panic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry, used AI to answer. Hope that's OK :) The answer is lengthy, but it seems a pity to cut it.

In SOF's deferred logging mode, the hook only runs in ISR context during a panic. In immediate mode it also runs from ordinary ISRs. That case is still safe: the log data is written either way, and only the BUFFER_STATUS notification for those bytes is postponed. That postponement already happens today for logs written on cores 1–3. Before this PR, immediate mode with ISR logging hit the "mutexes cannot be used inside ISRs" assertion, so the PR makes that case strictly better. Zephyr backends normally handle this differently, through the .panic() callback; details below.

When is the hook called in ISR context?

The hook is invoked from the mtrace backend's char_out(), i.e. wherever log_process() / msg_process() runs:

Mode Who processes messages Hook in ISR?
Deferred, normal operation (SOF default, app/prj.conf) log thread (pinned to core 0) no
Deferred, after LOG_PANIC() inline in the caller (z_log_msg_post_finalize()) yes, only after panic
Immediate (CONFIG_LOG_MODE_IMMEDIATE) inline in the caller (z_log_msg_commit() → msg_process()) yes, any ISR on any core

In SOF builds, LOG_PANIC() is reached only on the fatal path: z_fatal_error() runs coredump() first, and the coredump logging backend calls LOG_PANIC(), then k_sys_fatal_error_handler() calls it again. (ztest also calls it at the end of a run, from thread context.) So in deferred mode, yes, the hook runs in ISR context only in panic.

No in-tree SOF config combines immediate mode with mtrace (ACP boards don't use mtrace; the PTL/WCL sim configs set LOG_BACKEND_ADSP_MTRACE=n), but it is a common local debug setting, so it is worth covering.

Is it safe in immediate mode?

Yes. With the k_is_in_isr() early return, a log from a regular ISR still goes to the slot: mtrace_out() copies the data and advances dsp_ptr before the hook is called. Only the BUFFER_STATUS accounting and notification are skipped. The host reads dsp_ptr from the slot, so those bytes become visible with the next notification: the next core-0 thread log, the aging work, or EXCEPTION_CAUGHT. This is exactly what already happens for logs from secondary cores (the existing TODO in mtrace_log_hook_unlocked()). The only cost is notification latency for long ISR-only log bursts.

Before this PR, the same case (immediate mode + log from ISR) hit the "mutexes cannot be used inside ISRs" assertion, so this is strictly an improvement.

Notifying from a non-panic ISR would also mostly work: spinlocks and k_work_schedule_for_queue() are ISR-safe. The problem is that the hook has no way to tell a panic from a regular ISR. In panic, the backend forces space_left = 0, but that value also means a full buffer.

How other Zephyr backends handle this

None of them use k_is_in_isr(); they all rely on the .panic() callback:

Backend Panic handling
uart sets in_panic, switches from async uart_tx() to uart_poll_out()
rtt panic_mode; sync mode (immediate or panic) busy-waits instead of k_msleep()
adsp (winstream), SOF probe log_backend_std_panic(), lock-free synchronous output
net, ws, ble panic_mode → process() returns early (the stack can't run anymore)
fs deactivates the backend

The mtrace backend's .panic() does the opposite: it sets mtrace_panic_mode and keeps calling the hook with space_left forced to 0 to get an immediate notification. It assumes the hook is panic-safe but doesn't tell the hook it is in panic. On Xtensa every fatal path enters through the exception vector, which increments nested, so k_is_in_isr() is a reliable signal for that.

Possible follow-up

A cleaner long-term fix would be in Zephyr's log_backend_adsp_mtrace.c: either pass a panic flag to the hook, or skip the hook in panic mode. SOF doesn't need the backend's panic-time notification, because the host takes the final write pointer on EXCEPTION_CAUGHT (ipc4_mtrace_fw_crashed()). The hook could then check panic explicitly instead of using k_is_in_isr() as a proxy.

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