diff --git a/tests/subsys/ipc/ipc_sessions/CMakeLists.txt b/tests/subsys/ipc/ipc_sessions/CMakeLists.txt new file mode 100644 index 000000000000..75f2766cf7b9 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright 2021 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ipc_service) + +zephyr_include_directories(./common) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/ipc/ipc_sessions/Kconfig b/tests/subsys/ipc/ipc_sessions/Kconfig new file mode 100644 index 000000000000..bd2e472a0b47 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/Kconfig @@ -0,0 +1,16 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "Zephyr" +source "Kconfig.zephyr" +endmenu + +config IPC_TEST_MSG_HEAP_SIZE + int "The heap to copy processed messages" + default 512 + help + Internal heap where all the message data would be copied to be processed + linearry in tests. diff --git a/tests/subsys/ipc/ipc_sessions/Kconfig.sysbuild b/tests/subsys/ipc/ipc_sessions/Kconfig.sysbuild new file mode 100644 index 000000000000..99680714eded --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/Kconfig.sysbuild @@ -0,0 +1,13 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +source "${ZEPHYR_BASE}/share/sysbuild/Kconfig" + +config REMOTE_BOARD + string "The board used for remote target" + default "nrf5340dk/nrf5340/cpunet" if BOARD_NRF5340DK_NRF5340_CPUAPP + default "nrf5340dk/nrf5340/cpunet" if BOARD_NRF5340DK_NRF5340_CPUAPP_NS + default "nrf54h20dk/nrf54h20/cpuppr" if BOARD_NRF54H20DK_NRF54H20_CPUAPP diff --git a/tests/subsys/ipc/ipc_sessions/boards/nrf5340dk_nrf5340_cpuapp.conf b/tests/subsys/ipc/ipc_sessions/boards/nrf5340dk_nrf5340_cpuapp.conf new file mode 100644 index 000000000000..8cca3a9ce654 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_SOC_NRF53_CPUNET_ENABLE=y diff --git a/tests/subsys/ipc/ipc_sessions/boards/nrf5340dk_nrf5340_cpuapp.overlay b/tests/subsys/ipc/ipc_sessions/boards/nrf5340dk_nrf5340_cpuapp.overlay new file mode 100644 index 000000000000..7ff99435fa6f --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/delete-node/ &ipc0; + +/ { + chosen { + /delete-property/ zephyr,ipc_shm; + /delete-property/ zephyr,bt-hci; + }; + + reserved-memory { + /delete-node/ memory@20070000; + + sram_tx: memory@20070000 { + reg = <0x20070000 0x8000>; + }; + + sram_rx: memory@20078000 { + reg = <0x20078000 0x8000>; + }; + }; + + ipc0: ipc0 { + compatible = "zephyr,ipc-icmsg"; + tx-region = <&sram_tx>; + rx-region = <&sram_rx>; + mboxes = <&mbox 0>, <&mbox 1>; + mbox-names = "tx", "rx"; + dcache-alignment = <8>; + unbound = "detect"; + status = "okay"; + }; +}; diff --git a/tests/subsys/ipc/ipc_sessions/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/tests/subsys/ipc/ipc_sessions/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 000000000000..43b336c31f82 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* Replace default ipc0 instance */ +/delete-node/ &ipc0; + +ipc0: &cpuapp_cpuppr_ipc { + status = "okay"; + unbound = "detect"; +}; + +&cpuppr_vevif { + status = "okay"; +}; + +&cpuapp_bellboard { + status = "okay"; +}; + +/ { + chosen { + /delete-property/ zephyr,bt-hci; + }; +}; diff --git a/tests/subsys/ipc/ipc_sessions/common/test_commands.h b/tests/subsys/ipc/ipc_sessions/common/test_commands.h new file mode 100644 index 000000000000..d3b9928cd84a --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/common/test_commands.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef TEST_COMMANDS_H +#include + +/** + * @brief Test commands executable by remote + */ +enum ipc_test_commands { + IPC_TEST_CMD_NONE, /**< Command to be ingored */ + IPC_TEST_CMD_PING, /**< Respond with the @ref IPC_TEST_CMD_PONG message */ + IPC_TEST_CMD_PONG, /**< Expected response to IPC_TEST_CMD_PING */ + IPC_TEST_CMD_ECHO, /**< Respond with the same data */ + IPC_TEST_CMD_ECHO_RSP, /**< Echo respond */ + IPC_TEST_CMD_REBOND, /**< Unbond and rebond back whole interface */ + IPC_TEST_CMD_REBOOT, /**< Restart remote CPU after a given delay */ +}; + +/** + * @brief Base command structure + */ +struct ipc_test_cmd { + uint32_t cmd; /**< The command of @ref ipc_test_command type */ + uint8_t data[]; /**< Command data depending on the command itself */ +}; + +/** + * @brief Rebond command structure + */ +struct ipc_test_cmd_rebond { + struct ipc_test_cmd base; + uint32_t timeout_ms; +}; + +/** + * @brief Reboot command structure + */ +struct ipc_test_cmd_reboot { + struct ipc_test_cmd base; + uint32_t timeout_ms; +}; + +#endif /* TEST_COMMANDS_H */ diff --git a/tests/subsys/ipc/ipc_sessions/prj.conf b/tests/subsys/ipc/ipc_sessions/prj.conf new file mode 100644 index 000000000000..0f1ad696b294 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/prj.conf @@ -0,0 +1,7 @@ +# Copyright 2021 Carlo Caione +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ZTEST=y +CONFIG_MMU=y +CONFIG_IPC_SERVICE=y +CONFIG_MBOX=y diff --git a/tests/subsys/ipc/ipc_sessions/remote/CMakeLists.txt b/tests/subsys/ipc/ipc_sessions/remote/CMakeLists.txt new file mode 100644 index 000000000000..8ef3d24d697d --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/CMakeLists.txt @@ -0,0 +1,15 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(remote_icmsg) + +zephyr_include_directories(../common) + +FILE(GLOB remote_sources src/*.c) +target_sources(app PRIVATE ${remote_sources}) diff --git a/tests/subsys/ipc/ipc_sessions/remote/Kconfig b/tests/subsys/ipc/ipc_sessions/remote/Kconfig new file mode 100644 index 000000000000..2cb445215094 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/Kconfig @@ -0,0 +1,7 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +source "Kconfig.zephyr" diff --git a/tests/subsys/ipc/ipc_sessions/remote/boards/nrf5340dk_nrf5340_cpunet.overlay b/tests/subsys/ipc/ipc_sessions/remote/boards/nrf5340dk_nrf5340_cpunet.overlay new file mode 100644 index 000000000000..8e3109156ed4 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/boards/nrf5340dk_nrf5340_cpunet.overlay @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/delete-node/ &ipc0; + +/ { + chosen { + /delete-property/ zephyr,ipc_shm; + }; + + reserved-memory { + /delete-node/ memory@20070000; + + sram_rx: memory@20070000 { + reg = <0x20070000 0x8000>; + }; + + sram_tx: memory@20078000 { + reg = <0x20078000 0x8000>; + }; + }; + + ipc0: ipc0 { + compatible = "zephyr,ipc-icmsg"; + tx-region = <&sram_tx>; + rx-region = <&sram_rx>; + mboxes = <&mbox 0>, <&mbox 1>; + mbox-names = "rx", "tx"; + dcache-alignment = <8>; + unbound = "detect"; + status = "okay"; + }; +}; diff --git a/tests/subsys/ipc/ipc_sessions/remote/boards/nrf54h20dk_nrf54h20_cpuppr.conf b/tests/subsys/ipc/ipc_sessions/remote/boards/nrf54h20dk_nrf54h20_cpuppr.conf new file mode 100644 index 000000000000..80e211dcedd2 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/boards/nrf54h20dk_nrf54h20_cpuppr.conf @@ -0,0 +1 @@ +CONFIG_WATCHDOG=y diff --git a/tests/subsys/ipc/ipc_sessions/remote/boards/nrf54h20dk_nrf54h20_cpuppr.overlay b/tests/subsys/ipc/ipc_sessions/remote/boards/nrf54h20dk_nrf54h20_cpuppr.overlay new file mode 100644 index 000000000000..fb14d195b782 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/boards/nrf54h20dk_nrf54h20_cpuppr.overlay @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +ipc0: &cpuapp_cpuppr_ipc { + status = "okay"; + unbound = "detect"; +}; + +&cpuppr_vevif { + status = "okay"; +}; + +&cpuapp_bellboard { + status = "okay"; +}; + +&wdt131 { + status = "okay"; +}; + +/ { + chosen { + /delete-property/ zephyr,bt-hci; + }; + + aliases { + watchdog0 = &wdt131; + }; +}; diff --git a/tests/subsys/ipc/ipc_sessions/remote/prj.conf b/tests/subsys/ipc/ipc_sessions/remote/prj.conf new file mode 100644 index 000000000000..6006669b0b8a --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/prj.conf @@ -0,0 +1,20 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +CONFIG_PRINTK=y +CONFIG_EVENTS=y + +CONFIG_LOG=y +CONFIG_LOG_ALWAYS_RUNTIME=y +CONFIG_LOG_MODE_MINIMAL=y +#CONFIG_LOG_PROCESS_THREAD_PRIORITY=-15 +#CONFIG_LOG_PROCESS_THREAD_CUSTOM_PRIORITY=y + +CONFIG_HEAP_MEM_POOL_SIZE=2048 + +CONFIG_IPC_SERVICE=y +CONFIG_IPC_SERVICE_LOG_LEVEL_INF=y + +CONFIG_MBOX=y + +CONFIG_REBOOT=y diff --git a/tests/subsys/ipc/ipc_sessions/remote/src/remote.c b/tests/subsys/ipc/ipc_sessions/remote/src/remote.c new file mode 100644 index 000000000000..62164d63c900 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/src/remote.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(remote, LOG_LEVEL_INF); + +#define IPC_TEST_EV_REBOND 0x01 +#define IPC_TEST_EV_BOND 0x02 + +static const struct device *ipc0_instance = DEVICE_DT_GET(DT_NODELABEL(ipc0)); +static volatile bool ipc0_bounded; +K_SEM_DEFINE(bound_sem, 0, 1); +K_EVENT_DEFINE(ipc_ev_req); + +static struct k_timer timer_reboot; +static struct k_timer timer_rebond; + +static void ep_bound(void *priv); +static void ep_unbound(void *priv); +static void ep_recv(const void *data, size_t len, void *priv); +static void ep_error(const char *message, void *priv); + +static struct ipc_ept_cfg ep_cfg = { + .cb = { + .bound = ep_bound, + .unbound = ep_unbound, + .received = ep_recv, + .error = ep_error + }, +}; + +/** + * @brief Trying to reset by WDT + * + * @note If this function return, it means it fails + */ +static int reboot_by_wdt(void) +{ + int err; + static const struct device *const wdt = + COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(DT_ALIAS(watchdog0)), + (DEVICE_DT_GET(DT_ALIAS(watchdog0))), (NULL)); + static const struct wdt_timeout_cfg m_cfg_wdt = { + .callback = NULL, + .flags = WDT_FLAG_RESET_SOC, + .window.max = 10, + }; + static const uint8_t wdt_options[] = { + WDT_OPT_PAUSE_HALTED_BY_DBG | WDT_OPT_PAUSE_IN_SLEEP, + WDT_OPT_PAUSE_IN_SLEEP, + 0 + }; + + if (!wdt) { + return -ENOTSUP; + } + + if (!device_is_ready(wdt)) { + LOG_ERR("WDT device is not ready"); + return -EIO; + } + + err = wdt_install_timeout(wdt, &m_cfg_wdt); + if (err < 0) { + LOG_ERR("WDT install error"); + return -EIO; + } + + for (size_t i = 0; i < ARRAY_SIZE(wdt_options); ++i) { + err = wdt_setup(wdt, wdt_options[i]); + if (err < 0) { + LOG_ERR("Failed WDT setup with options = %u", wdt_options[i]); + } else { + /* We are ok with the configuration: + * just wait for the WDT to trigger + */ + for (;;) { + k_cpu_idle(); + } + } + } + + return -EIO; +} + +/** + * @brief Just force to reboot, anyway you find possible + */ +FUNC_NORETURN static void reboot_anyway(void) +{ + reboot_by_wdt(); + /* If WDT restart fails - try another way */ + sys_reboot(SYS_REBOOT_COLD); +} + +static void ep_bound(void *priv) +{ + ipc0_bounded = true; + k_sem_give(&bound_sem); + + LOG_INF("Endpoint bounded"); +} + +static void ep_unbound(void *priv) +{ + ipc0_bounded = false; + k_sem_give(&bound_sem); + + LOG_INF("Endpoint unbounded"); + + /* Try to restore the connection */ + k_event_set(&ipc_ev_req, IPC_TEST_EV_BOND); +} + +static void ep_recv(const void *data, size_t len, void *priv) +{ + int ret; + const struct ipc_test_cmd *cmd = data; + struct ipc_ept *ep = priv; + + if (len < sizeof(struct ipc_test_cmd)) { + LOG_ERR("The unexpected size of received data: %u < %u", len, sizeof(struct ipc_test_cmd)); + /* Dropping further processing */ + return; + } + + LOG_INF("Command received: %u", cmd->cmd); + + switch (cmd->cmd) { + case IPC_TEST_CMD_NONE: + LOG_INF("Command processing: NONE"); + /* Ignore */ + break; + case IPC_TEST_CMD_PING: { + LOG_INF("Command processing: PING"); + + static const struct ipc_test_cmd cmd_pong = {IPC_TEST_CMD_PONG}; + + ret = ipc_service_send(ep, &cmd_pong, sizeof(cmd_pong)); + if (ret < 0) { + LOG_ERR("PONG response failed: %d", ret); + } + break; + } + case IPC_TEST_CMD_ECHO: { + LOG_INF("Command processing: ECHO"); + + struct ipc_test_cmd *cmd_rsp = k_malloc(len); + + if (!cmd_rsp) { + LOG_ERR("ECHO response failed: memory allocation"); + break; + } + + cmd_rsp->cmd = IPC_TEST_CMD_ECHO_RSP; + memcpy(cmd_rsp->data, cmd->data, len - sizeof(struct ipc_test_cmd)); + ret = ipc_service_send(ep, cmd_rsp, len); + k_free(cmd_rsp); + if (ret < 0) { + LOG_ERR("ECHO response failed: %d", ret); + } + break; + } + case IPC_TEST_CMD_REBOND: { + LOG_INF("Command processing: REBOOT"); + + struct ipc_test_cmd_rebond *cmd_rebond = (struct ipc_test_cmd_rebond *)cmd; + + k_timer_start(&timer_rebond, K_MSEC(cmd_rebond->timeout_ms), K_FOREVER); + break; + } + case IPC_TEST_CMD_REBOOT: { + LOG_INF("Command processing: REBOOT"); + + struct ipc_test_cmd_reboot *cmd_reboot = (struct ipc_test_cmd_reboot *)cmd; + + k_timer_start(&timer_reboot, K_MSEC(cmd_reboot->timeout_ms), K_FOREVER); + break; + } + default: + LOG_ERR("Unhandled command: %u", cmd->cmd); + break; + } +} + +static void ep_error(const char *message, void *priv) +{ + LOG_ERR("EP error: \"%s\"", message); +} + +static int init_ipc(void) +{ + int ret; + static struct ipc_ept ep; + + /* Store the pointer to the endpoint */ + ep_cfg.priv = &ep; + + LOG_INF("IPC-sessions test remote started"); + + ret = ipc_service_open_instance(ipc0_instance); + if ((ret < 0) && (ret != -EALREADY)) { + LOG_ERR("ipc_service_open_instance() failure: %d", ret); + return ret; + } + + ret = ipc_service_register_endpoint(ipc0_instance, &ep, &ep_cfg); + if (ret < 0) { + LOG_ERR("ipc_service_register_endpoint() failure: %d", ret); + return ret; + } + + do { + k_sem_take(&bound_sem, K_FOREVER); + } while (!ipc0_bounded); + + LOG_INF("IPC connection estabilished"); + + return 0; +} + +static void timer_rebond_cb(struct k_timer *timer) +{ + (void)timer; + LOG_INF("Setting rebond request"); + k_event_set(&ipc_ev_req, IPC_TEST_EV_REBOND); +} + +static void timer_reboot_cb(struct k_timer *timer) +{ + (void)timer; + LOG_INF("Resetting CPU"); + reboot_anyway(); + __ASSERT(0, "Still working after reboot request"); +} + + +int main(void) +{ + int ret; + + k_timer_init(&timer_rebond, timer_rebond_cb, NULL); + k_timer_init(&timer_reboot, timer_reboot_cb, NULL); + ret = init_ipc(); + if (ret) { + return ret; + } + + while (1) { + uint32_t ev; + + ev = k_event_wait(&ipc_ev_req, ~0U, false, K_FOREVER); + k_event_clear(&ipc_ev_req, ev); + + if (ev & IPC_TEST_EV_REBOND) { + /* Rebond now */ + ret = ipc_service_deregister_endpoint(ep_cfg.priv); + if (ret) { + LOG_ERR("ipc_service_deregister_endpoint() failure: %d", ret); + continue; + } + ipc0_bounded = false; + + ret = ipc_service_register_endpoint(ipc0_instance, ep_cfg.priv, &ep_cfg); + if (ret < 0) { + LOG_ERR("ipc_service_register_endpoint() failure: %d", ret); + return ret; + } + + do { + k_sem_take(&bound_sem, K_FOREVER); + } while (!ipc0_bounded); + } + if (ev & IPC_TEST_EV_BOND) { + LOG_INF("Bonding endpoint"); + /* Bond missing endpoint */ + if (!ipc0_bounded) { + ret = ipc_service_register_endpoint(ipc0_instance, ep_cfg.priv, &ep_cfg); + if (ret < 0) { + LOG_ERR("ipc_service_register_endpoint() failure: %d", ret); + return ret; + } + + do { + k_sem_take(&bound_sem, K_FOREVER); + } while (!ipc0_bounded); + } + LOG_INF("Bonding done"); + } + + + } + + return 0; +} diff --git a/tests/subsys/ipc/ipc_sessions/src/data_queue.c b/tests/subsys/ipc/ipc_sessions/src/data_queue.c new file mode 100644 index 000000000000..03dc93257a38 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/src/data_queue.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "data_queue.h" + +#define DATA_QUEUE_MEMORY_ALIGN sizeof(uint32_t) + +struct data_queue_format { + uint32_t header; /* Required by kernel k_queue_append */ + size_t size; + uint32_t data[]; +}; + + +void data_queue_init(struct data_queue *q, void *mem, size_t bytes) +{ + k_heap_init(&q->h, mem, bytes); + k_queue_init(&q->q); +} + +int data_queue_put(struct data_queue *q, const void *data, size_t bytes, k_timeout_t timeout) +{ + struct data_queue_format *buffer = k_heap_aligned_alloc( + &q->h, + DATA_QUEUE_MEMORY_ALIGN, + bytes + sizeof(struct data_queue_format), + timeout); + + if (!buffer) { + return -ENOMEM; + } + buffer->size = bytes; + memcpy(buffer->data, data, bytes); + + k_queue_append(&q->q, buffer); + return 0; +} + +void *data_queue_get(struct data_queue *q, size_t *size, k_timeout_t timeout) +{ + struct data_queue_format *buffer = k_queue_get(&q->q, timeout); + + if (!buffer) { + return NULL; + } + + if (size) { + *size = buffer->size; + } + return buffer->data; +} + +void data_queue_release(struct data_queue *q, void *data) +{ + struct data_queue_format *buffer = CONTAINER_OF(data, struct data_queue_format, data); + + k_heap_free(&q->h, buffer); +} + +int data_queue_is_empty(struct data_queue *q) +{ + return k_queue_is_empty(&q->q); +} diff --git a/tests/subsys/ipc/ipc_sessions/src/data_queue.h b/tests/subsys/ipc/ipc_sessions/src/data_queue.h new file mode 100644 index 000000000000..5e5eef14c3e2 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/src/data_queue.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef DATA_QUEUE_H +#include + + +struct data_queue { + struct k_queue q; + struct k_heap h; +}; + +void data_queue_init(struct data_queue *q, void *mem, size_t bytes); + +int data_queue_put(struct data_queue *q, const void *data, size_t bytes, k_timeout_t timeout); + +void *data_queue_get(struct data_queue *q, size_t *size, k_timeout_t timeout); + +void data_queue_release(struct data_queue *q, void *data); + +int data_queue_is_empty(struct data_queue *q); + +#endif /* DATA_QUEUE_H */ diff --git a/tests/subsys/ipc/ipc_sessions/src/main.c b/tests/subsys/ipc/ipc_sessions/src/main.c new file mode 100644 index 000000000000..b401e13a4384 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/src/main.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include "data_queue.h" + +#include +LOG_MODULE_REGISTER(ipc_sessions, LOG_LEVEL_INF); + +enum test_ipc_events { + TEST_IPC_EVENT_BOUNDED, + TEST_IPC_EVENT_UNBOUNDED, + TEST_IPC_EVENT_ERROR +}; + +struct test_ipc_event_state { + enum test_ipc_events ev; + struct ipc_ep *ep; +}; + +static const struct device *ipc0_instance = DEVICE_DT_GET(DT_NODELABEL(ipc0)); +static volatile bool ipc0_bounded; +K_MSGQ_DEFINE(ipc_events, sizeof(struct test_ipc_event_state), 16, 4); + +static uint32_t data_queue_memory[ROUND_UP(CONFIG_IPC_TEST_MSG_HEAP_SIZE, sizeof(uint32_t))]; +static struct data_queue ipc_data_queue; + +static void ep_bound(void *priv) +{ + int ret; + struct test_ipc_event_state ev = { + .ev = TEST_IPC_EVENT_BOUNDED, + .ep = priv + }; + + ipc0_bounded = true; + ret = k_msgq_put(&ipc_events, &ev, K_NO_WAIT); + if (ret) { + LOG_ERR("Cannot put event in queue: %d", ret); + } +} + +static void ep_unbound(void *priv) +{ + int ret; + struct test_ipc_event_state ev = { + .ev = TEST_IPC_EVENT_UNBOUNDED, + .ep = priv + }; + + ipc0_bounded = false; + ret = k_msgq_put(&ipc_events, &ev, K_NO_WAIT); + if (ret) { + LOG_ERR("Cannot put event in queue: %d", ret); + } +} + +static void ep_recv(const void *data, size_t len, void *priv) +{ + int ret; + + ret = data_queue_put(&ipc_data_queue, data, len, K_NO_WAIT); + __ASSERT(ret >= 0, "Cannot put data into queue: %d", ret); + (void)ret; +} + +static void ep_error(const char *message, void *priv) +{ + int ret; + struct test_ipc_event_state ev = { + .ev = TEST_IPC_EVENT_ERROR, + .ep = priv + }; + + ret = k_msgq_put(&ipc_events, &ev, K_NO_WAIT); + if (ret) { + LOG_ERR("Cannot put event in queue: %d", ret); + } +} + +static struct ipc_ept_cfg ep_cfg = { + .cb = { + .bound = ep_bound, + .unbound = ep_unbound, + .received = ep_recv, + .error = ep_error + }, +}; + +static struct ipc_ept ep; + + + +/** + * @brief Estabilish connection before any test run + */ +void *test_suite_setup(void) +{ + int ret; + struct test_ipc_event_state ev; + + data_queue_init(&ipc_data_queue, data_queue_memory, sizeof(data_queue_memory)); + + ret = ipc_service_open_instance(ipc0_instance); + zassert_true((ret >= 0) || ret == -EALREADY, "ipc_service_open_instance() failure: %d", ret); + + /* Store the pointer to the endpoint */ + ep_cfg.priv = &ep; + ret = ipc_service_register_endpoint(ipc0_instance, &ep, &ep_cfg); + zassert_true((ret >= 0), "ipc_service_register_endpoint() failure: %d", ret); + + do { + ret = k_msgq_get(&ipc_events, &ev, K_MSEC(1000)); + zassert_ok(ret, "Cannot bound to the remote interface"); + } while (!ipc0_bounded); + + return NULL; +} + +/** + * @brief Prepare the test structures + */ +void test_suite_before(void *fixture) +{ + k_msgq_purge(&ipc_events); +} + +static void execute_test_ping_pong(void) +{ + int ret; + static const struct ipc_test_cmd cmd_ping = { IPC_TEST_CMD_PING }; + struct ipc_test_cmd *cmd_rsp; + size_t cmd_rsp_size; + + zassert_not_ok(data_queue_is_empty(&ipc_data_queue), "IPC data queue contains unexpected data"); + /* Sending data */ + ret = ipc_service_send(&ep, &cmd_ping, sizeof(cmd_ping)); + zassert_equal(ret, sizeof(cmd_ping), "ipc_service_send failed: %d, expected: %u", ret, sizeof(cmd_ping)); + /* Waiting for response */ + cmd_rsp = data_queue_get(&ipc_data_queue, &cmd_rsp_size, K_MSEC(1000)); + zassert_not_null(cmd_rsp, "No command response on time"); + zassert_equal(cmd_rsp_size, sizeof(struct ipc_test_cmd), "Unexpected response size: %u, expected: %u", cmd_rsp_size, sizeof(struct ipc_test_cmd)); + zassert_equal(cmd_rsp->cmd, IPC_TEST_CMD_PONG, "Unexpected response cmd value: %u, expected: %u", cmd_rsp->cmd, IPC_TEST_CMD_PONG); + data_queue_release(&ipc_data_queue, cmd_rsp); +} + +ZTEST(ipc_sessions, test_ping_pong) +{ + execute_test_ping_pong(); +} + +ZTEST(ipc_sessions, test_echo) +{ + int ret; + static const struct ipc_test_cmd cmd_echo = { IPC_TEST_CMD_ECHO, {'H', 'e', 'l', 'l', 'o', '!'} }; + struct ipc_test_cmd *cmd_rsp; + size_t cmd_rsp_size; + + zassert_not_ok(data_queue_is_empty(&ipc_data_queue), "IPC data queue contains unexpected data"); + /* Sending data */ + ret = ipc_service_send(&ep, &cmd_echo, sizeof(cmd_echo)); + zassert_equal(ret, sizeof(cmd_echo), "ipc_service_send failed: %d, expected: %u", ret, sizeof(cmd_echo)); + /* Waiting for response */ + cmd_rsp = data_queue_get(&ipc_data_queue, &cmd_rsp_size, K_MSEC(1000)); + zassert_not_null(cmd_rsp, "No command response on time"); + /* Checking response */ + zassert_equal(cmd_rsp_size, sizeof(cmd_echo), "Unexpected response size: %u, expected: %u", cmd_rsp_size, sizeof(cmd_echo)); + zassert_equal(cmd_rsp->cmd, IPC_TEST_CMD_ECHO_RSP, "Unexpected response cmd value: %u, expected: %u", cmd_rsp->cmd, IPC_TEST_CMD_ECHO_RSP); + zassert_mem_equal(cmd_rsp->data, cmd_echo.data, sizeof(cmd_echo) - sizeof(struct ipc_test_cmd), "Unexpected response content"); + data_queue_release(&ipc_data_queue, cmd_rsp); +} + +ZTEST(ipc_sessions, test_reboot) +{ + int ret; + struct test_ipc_event_state ev; + static const struct ipc_test_cmd_reboot cmd_rebond = { { IPC_TEST_CMD_REBOOT }, 10 }; + + zassert_not_ok(data_queue_is_empty(&ipc_data_queue), "IPC data queue contains unexpected data"); + /* Sending data */ + ret = ipc_service_send(&ep, &cmd_rebond, sizeof(cmd_rebond)); + zassert_equal(ret, sizeof(cmd_rebond), "ipc_service_send failed: %d, expected: %u", ret, sizeof(cmd_rebond)); + /* Waiting for IPC to unbound */ + ret = k_msgq_get(&ipc_events, &ev, K_MSEC(1000)); + zassert_ok(ret, "No IPC unbound event on time"); + zassert_equal(ev.ev, TEST_IPC_EVENT_UNBOUNDED, "Unexpected IPC event: %u, expected: %u", ev.ev, TEST_IPC_EVENT_UNBOUNDED); + zassert_equal_ptr(ev.ep, &ep, "Unexpected endpoint (unbound)"); + /* Reconnecting */ + ret = ipc_service_register_endpoint(ipc0_instance, &ep, &ep_cfg); + zassert_true((ret >= 0), "ipc_service_register_endpoint() failure: %d", ret); + /* Waiting for bound */ + ret = k_msgq_get(&ipc_events, &ev, K_MSEC(1000)); + zassert_ok(ret, "No IPC bound event on time"); + zassert_equal(ev.ev, TEST_IPC_EVENT_BOUNDED, "Unexpected IPC event: %u, expected: %u", ev.ev, TEST_IPC_EVENT_UNBOUNDED); + zassert_equal_ptr(ev.ep, &ep, "Unexpected endpoint (bound)"); + + /* After reconnection - test communication */ + execute_test_ping_pong(); +} + +ZTEST(ipc_sessions, test_rebond) +{ + int ret; + struct test_ipc_event_state ev; + static const struct ipc_test_cmd_reboot cmd_rebond = { { IPC_TEST_CMD_REBOND }, 10 }; + + zassert_not_ok(data_queue_is_empty(&ipc_data_queue), "IPC data queue contains unexpected data"); + /* Sending data */ + ret = ipc_service_send(&ep, &cmd_rebond, sizeof(cmd_rebond)); + zassert_equal(ret, sizeof(cmd_rebond), "ipc_service_send failed: %d, expected: %u", ret, sizeof(cmd_rebond)); + /* Waiting for IPC to unbound */ + ret = k_msgq_get(&ipc_events, &ev, K_MSEC(1000)); + zassert_ok(ret, "No IPC unbound event on time"); + zassert_equal(ev.ev, TEST_IPC_EVENT_UNBOUNDED, "Unexpected IPC event: %u, expected: %u", ev.ev, TEST_IPC_EVENT_UNBOUNDED); + zassert_equal_ptr(ev.ep, &ep, "Unexpected endpoint (unbound)"); + /* Reconnecting */ + ret = ipc_service_register_endpoint(ipc0_instance, &ep, &ep_cfg); + zassert_true((ret >= 0), "ipc_service_register_endpoint() failure: %d", ret); + /* Waiting for bound */ + ret = k_msgq_get(&ipc_events, &ev, K_MSEC(1000)); + zassert_ok(ret, "No IPC bound event on time"); + zassert_equal(ev.ev, TEST_IPC_EVENT_BOUNDED, "Unexpected IPC event: %u, expected: %u", ev.ev, TEST_IPC_EVENT_UNBOUNDED); + zassert_equal_ptr(ev.ep, &ep, "Unexpected endpoint (bound)"); + + /* After reconnection - test communication */ + execute_test_ping_pong(); +} + +ZTEST(ipc_sessions, test_local_rebond) +{ + int ret; + struct test_ipc_event_state ev; + + zassert_not_ok(data_queue_is_empty(&ipc_data_queue), "IPC data queue contains unexpected data"); + /* Rebond locally */ + ret = ipc_service_deregister_endpoint(ep_cfg.priv); + zassert_ok(ret, "ipc_service_deregister_endpoint() failure: %d", ret); + ipc0_bounded = false; + + ret = ipc_service_register_endpoint(ipc0_instance, &ep, &ep_cfg); + zassert_true((ret >= 0), "ipc_service_register_endpoint() failure: %d", ret); + do { + ret = k_msgq_get(&ipc_events, &ev, K_MSEC(1000)); + zassert_ok(ret, "Cannot bound to the remote interface"); + } while (!ipc0_bounded); + + /* After reconnection - test communication */ + execute_test_ping_pong(); +} + + +ZTEST_SUITE( + /* suite_name */ ipc_sessions, + /* ztest_suite_predicate_t */ NULL, + /* ztest_suite_setup_t */ test_suite_setup, + /* ztest_suite_before_t */ test_suite_before, + /* ztest_suite_after_t */ NULL, + /* ztest_suite_teardown_t */ NULL +); diff --git a/tests/subsys/ipc/ipc_sessions/sysbuild.cmake b/tests/subsys/ipc/ipc_sessions/sysbuild.cmake new file mode 100644 index 000000000000..329ae93115e1 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/sysbuild.cmake @@ -0,0 +1,26 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +if("${SB_CONFIG_REMOTE_BOARD}" STREQUAL "") + message(FATAL_ERROR "REMOTE_BOARD must be set to a valid board name") +endif() + +# Add remote project +ExternalZephyrProject_Add( + APPLICATION remote + SOURCE_DIR ${APP_DIR}/remote + BOARD ${SB_CONFIG_REMOTE_BOARD} + BOARD_REVISION ${BOARD_REVISION} +) +set_property(GLOBAL APPEND PROPERTY PM_DOMAINS CPUNET) +set_property(GLOBAL APPEND PROPERTY PM_CPUNET_IMAGES remote) +set_property(GLOBAL PROPERTY DOMAIN_APP_CPUNET remote) +set(CPUNET_PM_DOMAIN_DYNAMIC_PARTITION remote CACHE INTERNAL "") + +# Add a dependency so that the remote sample will be built and flashed first +sysbuild_add_dependencies(CONFIGURE ${DEFAULT_IMAGE} remote) +# Add dependency so that the remote image is flashed first. +sysbuild_add_dependencies(FLASH ${DEFAULT_IMAGE} remote) diff --git a/tests/subsys/ipc/ipc_sessions/testcase.yaml b/tests/subsys/ipc/ipc_sessions/testcase.yaml new file mode 100644 index 000000000000..4745cfd323f5 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/testcase.yaml @@ -0,0 +1,23 @@ +sample: + name: IPC Service integration test + description: IPC Service integration and efficiency test + +common: + sysbuild: true + tags: ipc ipc_sessions + harness: ztest + +tests: + sample.ipc.ipc_sessions.nrf5340dk: + platform_allow: + - nrf5340dk/nrf5340/cpuapp + integration_platforms: + - nrf5340dk/nrf5340/cpuapp + sample.ipc.ipc_sessions.nrf54h20dk_cpuapp_cpuppr: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: >- + FILE_SUFFIX=cpuppr + ipc_sessions_SNIPPET=nordic-ppr