Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions zephyr/include/rtos/userspace_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,12 @@ int user_stack_free(void *p_stack);
*/
void module_driver_heap_remove(struct k_heap *mod_drv_heap);

/**
* Add access to mailbox.h interface to a user-space thread.
*
* @param domain memory domain to add the mailbox partitions to
* @param thread_id user-space thread for which access is added
*/
int user_access_to_mailbox(struct k_mem_domain *domain, k_tid_t thread_id);

#endif /* __ZEPHYR_LIB_USERSPACE_HELPER_H__ */
46 changes: 46 additions & 0 deletions zephyr/lib/userspace_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <rtos/userspace_helper.h>
#include <sof/audio/module_adapter/module/generic.h>
#include <sof/audio/module_adapter/library/userspace_proxy.h>
#include <sof/lib/mailbox.h>

#define MODULE_DRIVER_HEAP_CACHED CONFIG_SOF_ZEPHYR_HEAP_CACHED

Expand Down Expand Up @@ -82,6 +83,46 @@ int user_memory_init_shared(k_tid_t thread_id, struct processing_module *mod)
return k_mem_domain_add_thread(comp_dom, thread_id);
}

int user_access_to_mailbox(struct k_mem_domain *domain, k_tid_t thread_id)
{
struct k_mem_partition mem_partition;
int ret;

/*
* Start with mailbox_swregs. This is aligned with mailbox.h
* implementation with uncached addressed used for register I/O.
*/
mem_partition.start =
(uintptr_t)sys_cache_uncached_ptr_get((void __sparse_cache *)MAILBOX_SW_REG_BASE);

BUILD_ASSERT(MAILBOX_SW_REG_SIZE == CONFIG_MMU_PAGE_SIZE);
mem_partition.size = CONFIG_MMU_PAGE_SIZE;
mem_partition.attr = K_MEM_PARTITION_P_RW_U_RW;

ret = k_mem_domain_add_partition(domain, &mem_partition);
if (ret < 0)
return ret;

#ifndef CONFIG_IPC_MAJOR_4
/*
* Next mailbox_stream (not available in IPC4). Stream access is cached,
* so different mapping this time.
*/
mem_partition.start =
(uintptr_t)sys_cache_cached_ptr_get((void *)SRAM_STREAM_BASE);
BUILD_ASSERT(MAILBOX_STREAM_SIZE == CONFIG_MMU_PAGE_SIZE);
/* size and attr the same as for mailbox_swregs */

ret = k_mem_domain_add_partition(domain, &mem_partition);
if (ret < 0)
return ret;
#endif

k_mem_domain_add_thread(domain, thread_id);

return 0;
}

#else /* CONFIG_USERSPACE */

void *user_stack_allocate(size_t stack_size, uint32_t options)
Expand All @@ -102,4 +143,9 @@ int user_stack_free(void *p_stack)
void module_driver_heap_remove(struct k_heap *mod_drv_heap)
{ }

int user_access_to_mailbox(struct k_mem_domain *domain, k_tid_t thread_id)
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: we can make this static inline in the header and it would save a little TEXT.

{
return 0;
}

#endif /* CONFIG_USERSPACE */
4 changes: 4 additions & 0 deletions zephyr/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ if (CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_SOF_USERSPACE_INTERFACE_DMA)
zephyr_library_sources(userspace/test_intel_ssp_dai.c)
endif()
endif()

if (CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_USERSPACE)
zephyr_library_sources(userspace/test_mailbox.c)
endif()
2 changes: 2 additions & 0 deletions zephyr/test/userspace/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Available tests:
- Test Zephyr DAI interface, together with SOF DMA
wrapper from a user thread. Mimics the call flows done in
sof/src/audio/dai-zephyr.c. Use cavstool.py as host runner.
- test_mailbox.c
- Test use of sof/mailbox.h interface from a Zephyr user thread.

Building for Intel Panther Lake:
./scripts/xtensa-build-zephyr.py --cmake-args=-DCONFIG_SOF_BOOT_TEST_STANDALONE=y \
Expand Down
123 changes: 123 additions & 0 deletions zephyr/test/userspace/test_mailbox.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright(c) 2026 Intel Corporation.
*/

/*
* Test case for sof/mailbox.h interface use from a Zephyr user
* thread.
*/

#include <sof/boot_test.h>
#include <sof/lib/mailbox.h>
#include <rtos/userspace_helper.h>

#include <zephyr/kernel.h>
#include <zephyr/ztest.h>
#include <zephyr/logging/log.h>
#include <zephyr/app_memory/app_memdomain.h>

#include <ipc4/fw_reg.h> /* mailbox definitions */

LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG);

#define USER_STACKSIZE 2048

static struct k_thread user_thread;
static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE);

static void mailbox_write_to_pipeline_regs(void)
{
unsigned int offset =
offsetof(struct ipc4_fw_registers, pipeline_regs);
struct ipc4_pipeline_registers pipe_reg;

pipe_reg.stream_start_offset = (uint64_t)-1;
pipe_reg.stream_end_offset = (uint64_t)-1;

LOG_INF("Write to IPC4 pipeline regs at offset %u", offset);

mailbox_sw_regs_write(offset, &pipe_reg, sizeof(pipe_reg));
}

/*
* the "stream" mailbox not available on targets using IPC4
* layout for shared memory between host and DSP
*/
#ifdef CONFIG_IPC_MAJOR_4
static void mailbox_write_to_stream_posn(void)
{
}
#else
static void mailbox_write_to_stream_posn(void)
{
struct sof_ipc_stream_posn posn;
size_t offset = 0; /* first stream position slot */

LOG_INF("Write to IPC4 stream#0 position info offset %u size %u.",
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The log message incorrectly states 'IPC4' when this code path is for non-IPC4 configurations (inside #else block for CONFIG_IPC_MAJOR_4). Should reference the appropriate IPC version or use generic terminology.

Suggested change
LOG_INF("Write to IPC4 stream#0 position info offset %u size %u.",
LOG_INF("Write to stream#0 position info offset %u size %zu.",

Copilot uses AI. Check for mistakes.
offset, sizeof(posn));

mailbox_stream_write(offset, &posn, sizeof(posn));
}
#endif

static void mailbox_test_thread(void *p1, void *p2, void *p3)
{
zassert_true(k_is_user_context(), "isn't user");

LOG_INF("SOF thread %s (%s)",
k_is_user_context() ? "UserSpace!" : "privileged mode.",
CONFIG_BOARD_TARGET);

mailbox_write_to_pipeline_regs();
mailbox_write_to_stream_posn();
}

static void mailbox_test(void)
{
struct k_mem_domain domain;
int ret = k_mem_domain_init(&domain, 0, NULL);

zassert_equal(ret, 0);

k_thread_create(&user_thread, user_stack, USER_STACKSIZE,
mailbox_test_thread, NULL, NULL, NULL,
-1, K_USER, K_FOREVER);

LOG_INF("set up user access to mailbox");

ret = user_access_to_mailbox(&domain, &user_thread);
zassert_equal(ret, 0);

k_thread_start(&user_thread);

LOG_INF("user started, waiting in kernel until test complete");

k_thread_join(&user_thread, K_FOREVER);
}

ZTEST(userspace_mailbox, mailbox_test)
{
/* first test from kernel */
mailbox_write_to_pipeline_regs();
mailbox_write_to_stream_posn();

/* then full test in userspace */
mailbox_test();

ztest_test_pass();
}

ZTEST_SUITE(userspace_mailbox, NULL, NULL, NULL, NULL, NULL);

/**
* SOF main has booted up and IPC handling is stopped.
* Run test suites with ztest_run_all.
*/
static int run_tests(void)
{
ztest_run_test_suite(userspace_mailbox, false, 1, 1, NULL);
return 0;
}

SYS_INIT(run_tests, APPLICATION, 99);