diff --git a/doc/services/ipc/ipc_service/backends/ipc_service_icmsg.rst b/doc/services/ipc/ipc_service/backends/ipc_service_icmsg.rst index 251a332027a4..369929a876f5 100644 --- a/doc/services/ipc/ipc_service/backends/ipc_service_icmsg.rst +++ b/doc/services/ipc/ipc_service/backends/ipc_service_icmsg.rst @@ -83,8 +83,7 @@ connected through the IPC instance: memory. #. It then sends a signal to the other domain or CPU, informing that the data has been written. Sending the signal to the other domain or CPU is repeated - with timeout specified by - :kconfig:option:`CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS` option. + with timeout. #. When the signal from the other domain or CPU is received, the magic number is read from ``rx-region``. If it is correct, the bonding process is finished and the backend informs the application by calling diff --git a/dts/bindings/ipc/zephyr,ipc-icmsg.yaml b/dts/bindings/ipc/zephyr,ipc-icmsg.yaml index 13a9e2b84b69..d1730dddb0f0 100644 --- a/dts/bindings/ipc/zephyr,ipc-icmsg.yaml +++ b/dts/bindings/ipc/zephyr,ipc-icmsg.yaml @@ -21,6 +21,21 @@ properties: required: true type: phandle + unbound: + type: string + enum: + - disable + - enable + - detect + default: disable + description: | + Select unbound() callback mode. The callback can be enabled or disabled with the + "enable" and "disable" option respectively. This functionality requires session + number handshake procedure on both sides, so you cannot set "enable" on one side + and "disable" on the other. The "detect" mode detects if the remote side + supports handshake procedure and adjusts its behavior accordingly. The + "detect" mode is possible only if "dcache-alignment" is at least 8 bytes. + dcache-alignment: type: int description: | diff --git a/include/zephyr/ipc/icmsg.h b/include/zephyr/ipc/icmsg.h index 6e4cbaeaa86c..41967d751536 100644 --- a/include/zephyr/ipc/icmsg.h +++ b/include/zephyr/ipc/icmsg.h @@ -27,14 +27,58 @@ extern "C" { */ enum icmsg_state { + /** Instance is not initialized yet. In this state: sending will fail, opening allowed. + */ ICMSG_STATE_OFF, - ICMSG_STATE_BUSY, - ICMSG_STATE_READY, + + /** Instance is initializing without session handshake. In this state: sending will fail, + * opening will fail. + */ + ICMSG_STATE_INITIALIZING_SID_DISABLED, + + /** Instance is initializing with session handshake. It is waiting for remote to acknowledge + * local session id. In this state: sending will fail, opening is allowed (local session id + * will change, so the remote may get unbound() callback). + */ + ICMSG_STATE_INITIALIZING_SID_ENABLED, + + /** Instance is initializing with detection of session handshake support on remote side. + * It is waiting for remote to acknowledge local session id or to send magic bytes. + * In this state: sending will fail, opening is allowed (local session id + * will change, so the remote may get unbound() callback if it supports it). + */ + ICMSG_STATE_INITIALIZING_SID_DETECT, + + /** Instance was closed on remote side. The unbound() callback was send on local side. + * In this state: sending will be silently discarded (there may be outdated sends), + * opening is allowed. + */ + ICMSG_STATE_DISCONNECTED, + + /* Connected states must be at the end. */ + + /** Instance is connected without session handshake support. In this state: sending will be + * successful, opening will fail. + */ + ICMSG_STATE_CONNECTED_SID_DISABLED, + + /** Instance is connected with session handshake support. In this state: sending will be + * successful, opening is allowed (session will change and remote will get unbound() + * callback). + */ + ICMSG_STATE_CONNECTED_SID_ENABLED, +}; + +enum icmsg_unbound_mode { + ICMSG_UNBOUND_MODE_DISABLE = ICMSG_STATE_INITIALIZING_SID_DISABLED, + ICMSG_UNBOUND_MODE_ENABLE = ICMSG_STATE_INITIALIZING_SID_ENABLED, + ICMSG_UNBOUND_MODE_DETECT = ICMSG_STATE_INITIALIZING_SID_DETECT, }; struct icmsg_config_t { struct mbox_dt_spec mbox_tx; struct mbox_dt_spec mbox_rx; + enum icmsg_unbound_mode unbound_mode; }; struct icmsg_data_t { @@ -52,9 +96,10 @@ struct icmsg_data_t { /* General */ const struct icmsg_config_t *cfg; #ifdef CONFIG_MULTITHREADING - struct k_work_delayable notify_work; struct k_work mbox_work; #endif + uint16_t remote_sid; + uint16_t local_sid; atomic_t state; }; diff --git a/include/zephyr/ipc/ipc_service.h b/include/zephyr/ipc/ipc_service.h index 65411a6be1ca..dbc0b8b7ee48 100644 --- a/include/zephyr/ipc/ipc_service.h +++ b/include/zephyr/ipc/ipc_service.h @@ -151,6 +151,21 @@ struct ipc_service_cb { */ void (*bound)(void *priv); + /** @brief The endpoint unbound by the remote. + * + * This callback is called when the endpoint binding is removed. It may happen on + * different reasons, e.g. when the remote deregistered the endpoint, connection was + * lost, or remote CPU got reset. + * + * You may want to do some cleanup, resetting, e.t.c. and after that if you want to bound + * again, you can register the endpoint. When the remote becomes available again and it + * also registers the endpoint, the binding will be reestablished and the `bound()` + * callback will be called. + * + * @param[in] priv Private user data. + */ + void (*unbound)(void *priv); + /** @brief New packet arrived. * * This callback is called when new data is received. diff --git a/include/zephyr/ipc/pbuf.h b/include/zephyr/ipc/pbuf.h index 8783cdbbf146..4bc42bfdf450 100644 --- a/include/zephyr/ipc/pbuf.h +++ b/include/zephyr/ipc/pbuf.h @@ -47,20 +47,23 @@ extern "C" { * The structure contains configuration data. */ struct pbuf_cfg { - volatile uint32_t *rd_idx_loc; /* Address of the variable holding - * index value of the first valid byte - * in data[]. - */ - volatile uint32_t *wr_idx_loc; /* Address of the variable holding - * index value of the first free byte - * in data[]. - */ - uint32_t dcache_alignment; /* CPU data cache line size in bytes. - * Used for validation - TODO: To be - * replaced by flags. - */ - uint32_t len; /* Length of data[] in bytes. */ - uint8_t *data_loc; /* Location of the data[]. */ + volatile uint32_t *rd_idx_loc; /* Address of the variable holding + * index value of the first valid byte + * in data[]. + */ + volatile uint32_t *handshake_loc;/* Address of the variable holding + * handshake information. + */ + volatile uint32_t *wr_idx_loc; /* Address of the variable holding + * index value of the first free byte + * in data[]. + */ + uint32_t dcache_alignment; /* CPU data cache line size in bytes. + * Used for validation - TODO: To be + * replaced by flags. + */ + uint32_t len; /* Length of data[] in bytes. */ + uint8_t *data_loc; /* Location of the data[]. */ }; /** @@ -111,16 +114,21 @@ struct pbuf { * @param mem_addr Memory address for pbuf. * @param size Size of the memory. * @param dcache_align Data cache alignment. + * @param use_handshake Add handshake word inside shared memory that can be access with + * @ref pbuf_handshake_read and @ref pbuf_handshake_write. */ -#define PBUF_CFG_INIT(mem_addr, size, dcache_align) \ +#define PBUF_CFG_INIT(mem_addr, size, dcache_align, use_handshake) \ { \ .rd_idx_loc = (uint32_t *)(mem_addr), \ - .wr_idx_loc = (uint32_t *)((uint8_t *)(mem_addr) + \ - MAX(dcache_align, _PBUF_IDX_SIZE)), \ + .handshake_loc = use_handshake ? (uint32_t *)((uint8_t *)(mem_addr) + \ + _PBUF_IDX_SIZE) : NULL, \ + .wr_idx_loc = (uint32_t *)((uint8_t *)(mem_addr) + MAX(dcache_align, \ + (use_handshake ? 2 : 1) * _PBUF_IDX_SIZE)), \ .data_loc = (uint8_t *)((uint8_t *)(mem_addr) + \ - MAX(dcache_align, _PBUF_IDX_SIZE) + _PBUF_IDX_SIZE), \ - .len = (uint32_t)((uint32_t)(size) - MAX(dcache_align, _PBUF_IDX_SIZE) - \ - _PBUF_IDX_SIZE), \ + MAX(dcache_align, (use_handshake ? 2 : 1) * \ + _PBUF_IDX_SIZE) + _PBUF_IDX_SIZE), \ + .len = (uint32_t)((uint32_t)(size) - MAX(dcache_align, \ + (use_handshake ? 2 : 1) * _PBUF_IDX_SIZE) - _PBUF_IDX_SIZE), \ .dcache_alignment = (dcache_align), \ } @@ -140,9 +148,11 @@ struct pbuf { * @param name Name of the pbuf. * @param mem_addr Memory address for pbuf. * @param size Size of the memory. - * @param dcache_align Data cache line size. + * @param dcache_align Data cache line size. + * @param use_handshake Add handshake word inside shared memory that can be access with + * @ref pbuf_handshake_read and @ref pbuf_handshake_write. */ -#define PBUF_DEFINE(name, mem_addr, size, dcache_align) \ +#define PBUF_DEFINE(name, mem_addr, size, dcache_align, use_handshake, compatibility) \ BUILD_ASSERT(dcache_align >= 0, \ "Cache line size must be non negative."); \ BUILD_ASSERT((size) > 0 && IS_PTR_ALIGNED_BYTES(size, _PBUF_IDX_SIZE), \ @@ -151,8 +161,10 @@ struct pbuf { "Misaligned memory."); \ BUILD_ASSERT(size >= (MAX(dcache_align, _PBUF_IDX_SIZE) + _PBUF_IDX_SIZE + \ _PBUF_MIN_DATA_LEN), "Insufficient size."); \ + BUILD_ASSERT(!(compatibility) || (dcache_align) >= 8, \ + "Data cache alignment must be at least 8 if compatibility is enabled.");\ static PBUF_MAYBE_CONST struct pbuf_cfg cfg_##name = \ - PBUF_CFG_INIT(mem_addr, size, dcache_align); \ + PBUF_CFG_INIT(mem_addr, size, dcache_align, use_handshake); \ static struct pbuf name = { \ .cfg = &cfg_##name, \ } @@ -223,6 +235,40 @@ int pbuf_write(struct pbuf *pb, const char *buf, uint16_t len); */ int pbuf_read(struct pbuf *pb, char *buf, uint16_t len); +/** + * @brief Read handshake word from pbuf. + * + * The pb must be defined with "PBUF_DEFINE" with "use_handshake" set. + * + * @param pb A buffer from which data will be read. + * @retval uint32_t The handshake word value. + */ +uint32_t pbuf_handshake_read(struct pbuf *pb); + +/** + * @brief Write handshake word to pbuf. + * + * The pb must be defined with "PBUF_DEFINE" with "use_handshake" set. + * + * @param pb A buffer to which data will be written. + * @param value A handshake value. + */ +void pbuf_handshake_write(struct pbuf *pb, uint32_t value); + +/** + * @brief Get first buffer from pbuf. + * + * This function retrieves buffer located at the beginning of queue. + * It will be continuous block since it is the first buffer. + * + * @param pb A buffer from which data will be read. + * @param[out] buf A pointer to output pointer to the date of the first buffer. + * @param[out] len A pointer to output length the first buffer. + * @retval 0 on success. + * -EINVAL when there is no buffer at the beginning of queue. + */ +int pbuf_get_initial_buf(struct pbuf *pb, volatile char **buf, uint16_t *len); + /** * @} */ diff --git a/samples/subsys/ipc/ipc_service/icmsg/src/main.c b/samples/subsys/ipc/ipc_service/icmsg/src/main.c index 86469cac64b0..afb5016dbeb3 100644 --- a/samples/subsys/ipc/ipc_service/icmsg/src/main.c +++ b/samples/subsys/ipc/ipc_service/icmsg/src/main.c @@ -40,6 +40,11 @@ static void ep_bound(void *priv) LOG_INF("Ep bounded"); } +static void ep_unbound(void *priv) +{ + LOG_INF("Ep unbounded"); +} + static void ep_recv(const void *data, size_t len, void *priv) { #if defined(CONFIG_ASSERT) @@ -68,6 +73,11 @@ static void ep_recv(const void *data, size_t len, void *priv) } } +static void ep_error(const char *message, void *priv) +{ + LOG_ERR("ICMsg error: %s", message); +} + static int send_for_time(struct ipc_ept *ep, const int64_t sending_time_ms) { struct data_packet msg = {.data[0] = 'a'}; @@ -123,7 +133,9 @@ static int send_for_time(struct ipc_ept *ep, const int64_t sending_time_ms) static struct ipc_ept_cfg ep_cfg = { .cb = { .bound = ep_bound, + .unbound = ep_unbound, .received = ep_recv, + .error = ep_error, }, }; diff --git a/scripts/ci/check_compliance.py b/scripts/ci/check_compliance.py index c311b3f72652..4407891b6b44 100755 --- a/scripts/ci/check_compliance.py +++ b/scripts/ci/check_compliance.py @@ -1000,6 +1000,8 @@ def check_no_undef_outside_kconfig(self, kconf): "FOO_SETTING_2", "HEAP_MEM_POOL_ADD_SIZE_", # Used as an option matching prefix "HUGETLBFS", # Linux, in boards/xtensa/intel_adsp_cavs25/doc + "IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS", # Used in ICMsg tests for intercompatibility + # with older versions of the ICMsg. "LIBGCC_RTLIB", "LLVM_USE_LD", # Both LLVM_USE_* are in cmake/toolchain/llvm/Kconfig "LLVM_USE_LLD", # which are only included if LLVM is selected but diff --git a/subsys/ipc/ipc_service/backends/ipc_icbmsg.c b/subsys/ipc/ipc_service/backends/ipc_icbmsg.c index 7e985aa76deb..3dbb018e7e19 100644 --- a/subsys/ipc/ipc_service/backends/ipc_icbmsg.c +++ b/subsys/ipc/ipc_service/backends/ipc_icbmsg.c @@ -1408,11 +1408,11 @@ const static struct ipc_service_backend backend_ops = { PBUF_DEFINE(tx_icbmsg_pb_##i, \ GET_MEM_ADDR_INST(i, tx), \ GET_ICMSG_SIZE_INST(i, tx, rx), \ - GET_CACHE_ALIGNMENT(i)); \ + GET_CACHE_ALIGNMENT(i), 0, 0); \ PBUF_DEFINE(rx_icbmsg_pb_##i, \ GET_MEM_ADDR_INST(i, rx), \ GET_ICMSG_SIZE_INST(i, rx, tx), \ - GET_CACHE_ALIGNMENT(i)); \ + GET_CACHE_ALIGNMENT(i), 0, 0); \ static struct backend_data backend_data_##i = { \ .control_data = { \ .tx_pb = &tx_icbmsg_pb_##i, \ @@ -1424,6 +1424,7 @@ const static struct ipc_service_backend backend_ops = { .control_config = { \ .mbox_tx = MBOX_DT_SPEC_INST_GET(i, tx), \ .mbox_rx = MBOX_DT_SPEC_INST_GET(i, rx), \ + .unbound_mode = ICMSG_UNBOUND_MODE_DISABLE, \ }, \ .tx = { \ .blocks_ptr = (uint8_t *)GET_BLOCKS_ADDR_INST(i, tx, rx), \ diff --git a/subsys/ipc/ipc_service/backends/ipc_icmsg.c b/subsys/ipc/ipc_service/backends/ipc_icmsg.c index 40cc06b8a6ff..77b118633ea5 100644 --- a/subsys/ipc/ipc_service/backends/ipc_icmsg.c +++ b/subsys/ipc/ipc_service/backends/ipc_icmsg.c @@ -54,33 +54,52 @@ static int backend_init(const struct device *instance) return 0; } -#define DEFINE_BACKEND_DEVICE(i) \ - static const struct icmsg_config_t backend_config_##i = { \ - .mbox_tx = MBOX_DT_SPEC_INST_GET(i, tx), \ - .mbox_rx = MBOX_DT_SPEC_INST_GET(i, rx), \ - }; \ - \ - PBUF_DEFINE(tx_pb_##i, \ - DT_REG_ADDR(DT_INST_PHANDLE(i, tx_region)), \ - DT_REG_SIZE(DT_INST_PHANDLE(i, tx_region)), \ - DT_INST_PROP_OR(i, dcache_alignment, 0)); \ - PBUF_DEFINE(rx_pb_##i, \ - DT_REG_ADDR(DT_INST_PHANDLE(i, rx_region)), \ - DT_REG_SIZE(DT_INST_PHANDLE(i, rx_region)), \ - DT_INST_PROP_OR(i, dcache_alignment, 0)); \ - \ - static struct icmsg_data_t backend_data_##i = { \ - .tx_pb = &tx_pb_##i, \ - .rx_pb = &rx_pb_##i, \ - }; \ - \ - DEVICE_DT_INST_DEFINE(i, \ - &backend_init, \ - NULL, \ - &backend_data_##i, \ - &backend_config_##i, \ - POST_KERNEL, \ - CONFIG_IPC_SERVICE_REG_BACKEND_PRIORITY, \ +#define UNBOUND_MODE(i) CONCAT(ICMSG_UNBOUND_MODE_, DT_INST_STRING_UPPER_TOKEN(i, unbound)) + +#define DEFINE_BACKEND_DEVICE(i) \ + static const struct icmsg_config_t backend_config_##i = { \ + .mbox_tx = MBOX_DT_SPEC_INST_GET(i, tx), \ + .mbox_rx = MBOX_DT_SPEC_INST_GET(i, rx), \ + .unbound_mode = UNBOUND_MODE(i), \ + }; \ + \ + PBUF_DEFINE(tx_pb_##i, \ + DT_REG_ADDR(DT_INST_PHANDLE(i, tx_region)), \ + DT_REG_SIZE(DT_INST_PHANDLE(i, tx_region)), \ + DT_INST_PROP_OR(i, dcache_alignment, 0), \ + UNBOUND_MODE(i) != ICMSG_UNBOUND_MODE_DISABLE, \ + UNBOUND_MODE(i) == ICMSG_UNBOUND_MODE_DETECT); \ + PBUF_DEFINE(rx_pb_##i, \ + DT_REG_ADDR(DT_INST_PHANDLE(i, rx_region)), \ + DT_REG_SIZE(DT_INST_PHANDLE(i, rx_region)), \ + DT_INST_PROP_OR(i, dcache_alignment, 0), \ + UNBOUND_MODE(i) != ICMSG_UNBOUND_MODE_DISABLE, \ + UNBOUND_MODE(i) == ICMSG_UNBOUND_MODE_DETECT); \ + \ + BUILD_ASSERT(UNBOUND_MODE(i) != ICMSG_UNBOUND_MODE_DISABLE || \ + IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_DISABLED_ALLOWED), \ + "Unbound mode \"disabled\" is was forbidden in Kconfig."); \ + \ + BUILD_ASSERT(UNBOUND_MODE(i) != ICMSG_UNBOUND_MODE_ENABLE || \ + IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_ENABLED_ALLOWED), \ + "Unbound mode \"enabled\" is was forbidden in Kconfig."); \ + \ + BUILD_ASSERT(UNBOUND_MODE(i) != ICMSG_UNBOUND_MODE_DETECT || \ + IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_DETECT_ALLOWED), \ + "Unbound mode \"detect\" is was forbidden in Kconfig."); \ + \ + static struct icmsg_data_t backend_data_##i = { \ + .tx_pb = &tx_pb_##i, \ + .rx_pb = &rx_pb_##i, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(i, \ + &backend_init, \ + NULL, \ + &backend_data_##i, \ + &backend_config_##i, \ + POST_KERNEL, \ + CONFIG_IPC_SERVICE_REG_BACKEND_PRIORITY, \ &backend_ops); DT_INST_FOREACH_STATUS_OKAY(DEFINE_BACKEND_DEVICE) diff --git a/subsys/ipc/ipc_service/backends/ipc_icmsg_me_follower.c b/subsys/ipc/ipc_service/backends/ipc_icmsg_me_follower.c index cc374b31f57d..bf682cd51e18 100644 --- a/subsys/ipc/ipc_service/backends/ipc_icmsg_me_follower.c +++ b/subsys/ipc/ipc_service/backends/ipc_icmsg_me_follower.c @@ -280,16 +280,17 @@ static int backend_init(const struct device *instance) static const struct icmsg_config_t backend_config_##i = { \ .mbox_tx = MBOX_DT_SPEC_INST_GET(i, tx), \ .mbox_rx = MBOX_DT_SPEC_INST_GET(i, rx), \ + .unbound_mode = ICMSG_UNBOUND_MODE_DISABLE, \ }; \ \ PBUF_DEFINE(tx_pb_##i, \ DT_REG_ADDR(DT_INST_PHANDLE(i, tx_region)), \ DT_REG_SIZE(DT_INST_PHANDLE(i, tx_region)), \ - DT_INST_PROP_OR(i, dcache_alignment, 0)); \ + DT_INST_PROP_OR(i, dcache_alignment, 0), 0, 0); \ PBUF_DEFINE(rx_pb_##i, \ DT_REG_ADDR(DT_INST_PHANDLE(i, rx_region)), \ DT_REG_SIZE(DT_INST_PHANDLE(i, rx_region)), \ - DT_INST_PROP_OR(i, dcache_alignment, 0)); \ + DT_INST_PROP_OR(i, dcache_alignment, 0), 0, 0); \ \ static struct backend_data_t backend_data_##i = { \ .icmsg_me_data = { \ diff --git a/subsys/ipc/ipc_service/backends/ipc_icmsg_me_initiator.c b/subsys/ipc/ipc_service/backends/ipc_icmsg_me_initiator.c index 28170f909ece..f2064a9dc0f2 100644 --- a/subsys/ipc/ipc_service/backends/ipc_icmsg_me_initiator.c +++ b/subsys/ipc/ipc_service/backends/ipc_icmsg_me_initiator.c @@ -186,16 +186,17 @@ static int backend_init(const struct device *instance) static const struct icmsg_config_t backend_config_##i = { \ .mbox_tx = MBOX_DT_SPEC_INST_GET(i, tx), \ .mbox_rx = MBOX_DT_SPEC_INST_GET(i, rx), \ + .unbound_mode = ICMSG_UNBOUND_MODE_DISABLE, \ }; \ \ PBUF_DEFINE(tx_pb_##i, \ DT_REG_ADDR(DT_INST_PHANDLE(i, tx_region)), \ DT_REG_SIZE(DT_INST_PHANDLE(i, tx_region)), \ - DT_INST_PROP_OR(i, dcache_alignment, 0)); \ + DT_INST_PROP_OR(i, dcache_alignment, 0), 0, 0); \ PBUF_DEFINE(rx_pb_##i, \ DT_REG_ADDR(DT_INST_PHANDLE(i, rx_region)), \ DT_REG_SIZE(DT_INST_PHANDLE(i, rx_region)), \ - DT_INST_PROP_OR(i, dcache_alignment, 0)); \ + DT_INST_PROP_OR(i, dcache_alignment, 0), 0, 0); \ \ static struct backend_data_t backend_data_##i = { \ .icmsg_me_data = { \ diff --git a/subsys/ipc/ipc_service/lib/Kconfig.icmsg b/subsys/ipc/ipc_service/lib/Kconfig.icmsg index bc15d9a59993..6bbc79d4fa2a 100644 --- a/subsys/ipc/ipc_service/lib/Kconfig.icmsg +++ b/subsys/ipc/ipc_service/lib/Kconfig.icmsg @@ -21,14 +21,6 @@ config IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS Maximum time to wait, in milliseconds, for access to send data with backends basing on icmsg library. This time should be relatively low. -config IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS - int "Bond notification timeout in miliseconds" - range 1 100 - default 1 - help - Time to wait for remote bonding notification before the - notification is repeated. - config IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE bool "Use dedicated workqueue" depends on MULTITHREADING @@ -68,6 +60,33 @@ config IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY endif +config IPC_SERVICE_ICMSG_UNBOUND_ENABLED_ALLOWED + bool "Instance is allowed to set unbound to enabled" + default y + help + Controls whether the "enabled" value of the "unbound" + property is allowed to be set in any of the ICMsg instances. You + can set this option to "n", if you want to reduce code size and + no instance of ICMsg is using unbound functionality. + +config IPC_SERVICE_ICMSG_UNBOUND_DISABLED_ALLOWED + bool "Instance is allowed to set unbound to disabled" + default y + help + Controls whether the "disabled" value of the "unbound" + property is allowed to be set in any of the ICMsg instances. You + can set this option to "n", if you want to reduce code size and + all instances of ICMsg are using unbound functionality. + +config IPC_SERVICE_ICMSG_UNBOUND_DETECT_ALLOWED + bool "Instance is allowed to set unbound to detect" + default y + help + Controls whether the "detect" value of the "unbound" + property is allowed to be set in any of the ICMsg instances. You + can set this option to "n", if you want to reduce code size and + all instances of ICMsg are using unbound detection functionality. + # The Icmsg library in its simplicity requires the system workqueue to execute # at a cooperative priority. config SYSTEM_WORKQUEUE_PRIORITY diff --git a/subsys/ipc/ipc_service/lib/icmsg.c b/subsys/ipc/ipc_service/lib/icmsg.c index f574e71ec147..500ca20cde64 100644 --- a/subsys/ipc/ipc_service/lib/icmsg.c +++ b/subsys/ipc/ipc_service/lib/icmsg.c @@ -12,7 +12,39 @@ #include #include -#define BOND_NOTIFY_REPEAT_TO K_MSEC(CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS) + +#define UNBOUND_DISABLED IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_DISABLED_ALLOWED) +#define UNBOUND_ENABLED IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_ENABLED_ALLOWED) +#define UNBOUND_DETECT IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_DETECT_ALLOWED) + +/** Get local session id request from RX handshake word. + */ +#define LOCAL_SID_REQ_FROM_RX(rx_handshake) ((rx_handshake) & 0xFFFF) + +/** Get remote session id request from TX handshake word. + */ +#define REMOTE_SID_REQ_FROM_TX(tx_handshake) ((tx_handshake) & 0xFFFF) + +/** Get local session id acknowledge from TX handshake word. + */ +#define LOCAL_SID_ACK_FROM_TX(tx_handshake) ((tx_handshake) >> 16) + +/** Create RX handshake word from local session id request + * and remote session id acknowledge. + */ +#define MAKE_RX_HANDSHAKE(local_sid_req, remote_sid_ack) \ + ((local_sid_req) | ((remote_sid_ack) << 16)) + +/** Create TX handshake word from remote session id request + * and local session id acknowledge. + */ +#define MAKE_TX_HANDSHAKE(remote_sid_req, local_sid_ack) \ + ((remote_sid_req) | ((local_sid_ack) << 16)) + +/** Special session id indicating that peers are disconnected. + */ +#define SID_DISCONNECTED 0 + #define SHMEM_ACCESS_TO K_MSEC(CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS) static const uint8_t magic[] = {0x45, 0x6d, 0x31, 0x6c, 0x31, 0x4b, @@ -23,13 +55,10 @@ static const uint8_t magic[] = {0x45, 0x6d, 0x31, 0x6c, 0x31, 0x4b, static K_THREAD_STACK_DEFINE(icmsg_stack, CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE); static struct k_work_q icmsg_workq; static struct k_work_q *const workq = &icmsg_workq; -#else +#else /* defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE) */ static struct k_work_q *const workq = &k_sys_work_q; -#endif -static void mbox_callback_process(struct k_work *item); -#else -static void mbox_callback_process(struct icmsg_data_t *dev_data); -#endif +#endif /* defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE) */ +#endif /* def CONFIG_MULTITHREADING */ static int mbox_deinit(const struct icmsg_config_t *conf, struct icmsg_data_t *dev_data) @@ -48,76 +77,33 @@ static int mbox_deinit(const struct icmsg_config_t *conf, #ifdef CONFIG_MULTITHREADING (void)k_work_cancel(&dev_data->mbox_work); - (void)k_work_cancel_delayable(&dev_data->notify_work); #endif return 0; } -static bool is_endpoint_ready(struct icmsg_data_t *dev_data) +static bool is_endpoint_ready(atomic_t state) { - return atomic_get(&dev_data->state) == ICMSG_STATE_READY; + return state >= MIN(ICMSG_STATE_CONNECTED_SID_DISABLED, ICMSG_STATE_CONNECTED_SID_ENABLED); } -#ifdef CONFIG_MULTITHREADING -static void notify_process(struct k_work *item) +static inline int reserve_tx_buffer_if_unused(struct icmsg_data_t *dev_data) { - struct k_work_delayable *dwork = k_work_delayable_from_work(item); - struct icmsg_data_t *dev_data = - CONTAINER_OF(dwork, struct icmsg_data_t, notify_work); - - (void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL); - - atomic_t state = atomic_get(&dev_data->state); - - if (state != ICMSG_STATE_READY) { - int ret; - - ret = k_work_reschedule_for_queue(workq, dwork, BOND_NOTIFY_REPEAT_TO); - __ASSERT_NO_MSG(ret >= 0); - (void)ret; - } -} -#else -static void notify_process(struct icmsg_data_t *dev_data) -{ - (void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL); -#if defined(CONFIG_SYS_CLOCK_EXISTS) - int64_t start = k_uptime_get(); -#endif - - while (false == is_endpoint_ready(dev_data)) { - mbox_callback_process(dev_data); - -#if defined(CONFIG_SYS_CLOCK_EXISTS) - if ((k_uptime_get() - start) > CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS) { -#endif - (void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL); -#if defined(CONFIG_SYS_CLOCK_EXISTS) - start = k_uptime_get(); - }; -#endif - } -} -#endif - #ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC -static int reserve_tx_buffer_if_unused(struct icmsg_data_t *dev_data) -{ - int ret = k_mutex_lock(&dev_data->tx_lock, SHMEM_ACCESS_TO); - - if (ret < 0) { - return ret; - } - + return k_mutex_lock(&dev_data->tx_lock, SHMEM_ACCESS_TO); +#else return 0; +#endif } -static int release_tx_buffer(struct icmsg_data_t *dev_data) +static inline int release_tx_buffer(struct icmsg_data_t *dev_data) { +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC return k_mutex_unlock(&dev_data->tx_lock); -} +#else + return 0; #endif +} static uint32_t data_available(struct icmsg_data_t *dev_data) { @@ -135,103 +121,242 @@ static void submit_mbox_work(struct icmsg_data_t *dev_data) } } -static void submit_work_if_buffer_free(struct icmsg_data_t *dev_data) -{ - submit_mbox_work(dev_data); -} +#endif -static void submit_work_if_buffer_free_and_data_available( - struct icmsg_data_t *dev_data) +static int initialize_tx_with_sid_disabled(struct icmsg_data_t *dev_data) { - if (!data_available(dev_data)) { - return; + int ret; + + ret = pbuf_tx_init(dev_data->tx_pb); + + if (ret < 0) { + __ASSERT(false, "Incorrect Tx configuration"); + return ret; } - submit_mbox_work(dev_data); -} -#else -static void submit_if_buffer_free(struct icmsg_data_t *dev_data) -{ - mbox_callback_process(dev_data); -} + ret = pbuf_write(dev_data->tx_pb, magic, sizeof(magic)); -static void submit_if_buffer_free_and_data_available( - struct icmsg_data_t *dev_data) -{ + if (ret < 0) { + __ASSERT_NO_MSG(false); + return ret; + } - if (!data_available(dev_data)) { - return; + if (ret < (int)sizeof(magic)) { + __ASSERT_NO_MSG(ret == sizeof(magic)); + return -EINVAL; } - mbox_callback_process(dev_data); + return 0; } -#endif -#ifdef CONFIG_MULTITHREADING -static void mbox_callback_process(struct k_work *item) -#else -static void mbox_callback_process(struct icmsg_data_t *dev_data) -#endif +static bool callback_process(struct icmsg_data_t *dev_data) { -#ifdef CONFIG_MULTITHREADING - struct icmsg_data_t *dev_data = CONTAINER_OF(item, struct icmsg_data_t, mbox_work); -#endif + int ret; uint8_t rx_buffer[CONFIG_PBUF_RX_READ_BUF_SIZE] __aligned(4); - + uint32_t len = 0; + uint32_t len_available; + bool rerun = false; + bool notify_remote = false; atomic_t state = atomic_get(&dev_data->state); - uint32_t len = data_available(dev_data); - - if (len == 0) { - /* Unlikely, no data in buffer. */ - return; + switch (state) { + +#if UNBOUND_DETECT + case ICMSG_STATE_INITIALIZING_SID_DETECT: { + /* Initialization with detection of remote session awareness */ + volatile char *magic_buf; + uint16_t magic_len; + + ret = pbuf_get_initial_buf(dev_data->rx_pb, &magic_buf, &magic_len); + + if (ret == 0 && magic_len == sizeof(magic) && + memcmp((void *)magic_buf, magic, sizeof(magic)) == 0) { + /* Remote initialized in session-unaware mode, so we do old way of + * initialization. + */ + ret = initialize_tx_with_sid_disabled(dev_data); + if (ret < 0) { + if (dev_data->cb->error) { + dev_data->cb->error("Incorrect Tx configuration", + dev_data->ctx); + } + __ASSERT(false, "Incorrect Tx configuration"); + atomic_set(&dev_data->state, ICMSG_STATE_OFF); + return false; + } + /* We got magic data, so we can handle it later. */ + notify_remote = true; + rerun = true; + atomic_set(&dev_data->state, ICMSG_STATE_INITIALIZING_SID_DISABLED); + break; + } + /* If remote did not initialize the RX in session-unaware mode, we can try + * with session-aware initialization. + */ + __fallthrough; } +#endif /* UNBOUND_DETECT */ + +#if UNBOUND_ENABLED || UNBOUND_DETECT + case ICMSG_STATE_INITIALIZING_SID_ENABLED: { + uint32_t tx_handshake = pbuf_handshake_read(dev_data->tx_pb); + uint32_t remote_sid_req = REMOTE_SID_REQ_FROM_TX(tx_handshake); + uint32_t local_sid_ack = LOCAL_SID_ACK_FROM_TX(tx_handshake); + + if (remote_sid_req != dev_data->remote_sid && remote_sid_req != SID_DISCONNECTED) { + /* We can now initialize TX, since we know that remote, during receiving, + * will first read FIFO indexes and later, it will check if session has + * changed before using indexes to receive the message. Additionally, + * we know that remote after session request change will no try to receive + * more data. + */ + ret = pbuf_tx_init(dev_data->tx_pb); + if (ret < 0) { + if (dev_data->cb->error) { + dev_data->cb->error("Incorrect Tx configuration", + dev_data->ctx); + } + __ASSERT(false, "Incorrect Tx configuration"); + atomic_set(&dev_data->state, ICMSG_STATE_DISCONNECTED); + return false; + } + /* Acknowledge the remote session. */ + dev_data->remote_sid = remote_sid_req; + pbuf_handshake_write(dev_data->rx_pb, + MAKE_RX_HANDSHAKE(dev_data->local_sid, dev_data->remote_sid)); + notify_remote = true; + } - __ASSERT_NO_MSG(len <= sizeof(rx_buffer)); + if (local_sid_ack == dev_data->local_sid && + dev_data->remote_sid != SID_DISCONNECTED) { + /* We send acknowledge to remote, receive acknowledge from remote, + * so we are ready. + */ + atomic_set(&dev_data->state, ICMSG_STATE_CONNECTED_SID_ENABLED); + + if (dev_data->cb->bound) { + dev_data->cb->bound(dev_data->ctx); + } + /* Re-run this handler, because remote may already send something. */ + rerun = true; + notify_remote = true; + } - if (sizeof(rx_buffer) < len) { - return; + break; } +#endif /* UNBOUND_ENABLED || UNBOUND_DETECT */ - len = pbuf_read(dev_data->rx_pb, rx_buffer, sizeof(rx_buffer)); +#if UNBOUND_ENABLED || UNBOUND_DETECT + case ICMSG_STATE_CONNECTED_SID_ENABLED: +#endif +#if UNBOUND_DISABLED || UNBOUND_DETECT + case ICMSG_STATE_CONNECTED_SID_DISABLED: +#endif +#if UNBOUND_DISABLED + case ICMSG_STATE_INITIALIZING_SID_DISABLED: +#endif + + len_available = data_available(dev_data); - if (state == ICMSG_STATE_READY) { - if (dev_data->cb->received) { - dev_data->cb->received(rx_buffer, len, dev_data->ctx); + if (len_available > 0 && sizeof(rx_buffer) >= len_available) { + len = pbuf_read(dev_data->rx_pb, rx_buffer, sizeof(rx_buffer)); + } + + if (state == ICMSG_STATE_CONNECTED_SID_ENABLED && + (UNBOUND_ENABLED || UNBOUND_DETECT)) { + /* The incoming message is valid only if remote session is as expected, + * so we need to check remote session now. + */ + uint32_t remote_sid_req = REMOTE_SID_REQ_FROM_TX( + pbuf_handshake_read(dev_data->tx_pb)); + + if (remote_sid_req != dev_data->remote_sid) { + atomic_set(&dev_data->state, ICMSG_STATE_DISCONNECTED); + if (dev_data->cb->unbound) { + dev_data->cb->unbound(dev_data->ctx); + } + return false; + } + } + + if (len_available == 0) { + /* Unlikely, no data in buffer. */ + return false; } - } else { - __ASSERT_NO_MSG(state == ICMSG_STATE_BUSY); - /* Allow magic number longer than sizeof(magic) for future protocol version. */ - bool endpoint_invalid = (len < sizeof(magic) || - memcmp(magic, rx_buffer, sizeof(magic))); + __ASSERT_NO_MSG(len_available <= sizeof(rx_buffer)); - if (endpoint_invalid) { - __ASSERT_NO_MSG(false); - return; + if (sizeof(rx_buffer) < len_available) { + return false; } - if (dev_data->cb->bound) { - dev_data->cb->bound(dev_data->ctx); + if (state != ICMSG_STATE_INITIALIZING_SID_DISABLED || !UNBOUND_DISABLED) { + if (dev_data->cb->received) { + dev_data->cb->received(rx_buffer, len, dev_data->ctx); + } + } else { + /* Allow magic number longer than sizeof(magic) for future protocol + * version. + */ + bool endpoint_invalid = (len < sizeof(magic) || + memcmp(magic, rx_buffer, sizeof(magic))); + + if (endpoint_invalid) { + __ASSERT_NO_MSG(false); + return false; + } + + if (dev_data->cb->bound) { + dev_data->cb->bound(dev_data->ctx); + } + + atomic_set(&dev_data->state, ICMSG_STATE_CONNECTED_SID_DISABLED); + notify_remote = true; } - atomic_set(&dev_data->state, ICMSG_STATE_READY); + rerun = (data_available(dev_data) > 0); + break; + + case ICMSG_STATE_OFF: + case ICMSG_STATE_DISCONNECTED: + default: + /* Nothing to do in this state. */ + return false; } + + if (notify_remote) { + (void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL); + } + + return rerun; +} + #ifdef CONFIG_MULTITHREADING - submit_work_if_buffer_free_and_data_available(dev_data); -#else - submit_if_buffer_free_and_data_available(dev_data); -#endif +static void workq_callback_process(struct k_work *item) +{ + bool rerun; + struct icmsg_data_t *dev_data = CONTAINER_OF(item, struct icmsg_data_t, mbox_work); + + rerun = callback_process(dev_data); + if (rerun) { + submit_mbox_work(dev_data); + } } +#endif /* def CONFIG_MULTITHREADING */ static void mbox_callback(const struct device *instance, uint32_t channel, void *user_data, struct mbox_msg *msg_data) { + bool rerun; struct icmsg_data_t *dev_data = user_data; + #ifdef CONFIG_MULTITHREADING - submit_work_if_buffer_free(dev_data); + ARG_UNUSED(rerun); + submit_mbox_work(dev_data); #else - submit_if_buffer_free(dev_data); + do { + rerun = callback_process(dev_data); + } while (rerun); #endif } @@ -241,8 +366,7 @@ static int mbox_init(const struct icmsg_config_t *conf, int err; #ifdef CONFIG_MULTITHREADING - k_work_init(&dev_data->mbox_work, mbox_callback_process); - k_work_init_delayable(&dev_data->notify_work, notify_process); + k_work_init(&dev_data->mbox_work, workq_callback_process); #endif err = mbox_register_callback_dt(&conf->mbox_rx, mbox_callback, dev_data); @@ -257,9 +381,27 @@ int icmsg_open(const struct icmsg_config_t *conf, struct icmsg_data_t *dev_data, const struct ipc_service_cb *cb, void *ctx) { - if (!atomic_cas(&dev_data->state, ICMSG_STATE_OFF, ICMSG_STATE_BUSY)) { - /* Already opened. */ - return -EALREADY; + int ret; + enum icmsg_state old_state; + + __ASSERT(conf->unbound_mode != ICMSG_UNBOUND_MODE_DISABLE || UNBOUND_DISABLED, + "Unbound mode \"disabled\" is was forbidden in Kconfig."); + __ASSERT(conf->unbound_mode != ICMSG_UNBOUND_MODE_ENABLE || UNBOUND_ENABLED, + "Unbound mode \"enabled\" is was forbidden in Kconfig."); + __ASSERT(conf->unbound_mode != ICMSG_UNBOUND_MODE_DETECT || UNBOUND_DETECT, + "Unbound mode \"detect\" is was forbidden in Kconfig."); + + if (conf->unbound_mode == ICMSG_UNBOUND_MODE_DISABLE || + !(UNBOUND_ENABLED || UNBOUND_DETECT)) { + if (!atomic_cas(&dev_data->state, ICMSG_STATE_OFF, + ICMSG_STATE_INITIALIZING_SID_DISABLED)) { + /* Already opened. */ + return -EALREADY; + } + old_state = ICMSG_STATE_OFF; + } else { + /* Unbound mode has the same values as ICMSG_STATE_INITIALIZING_* */ + old_state = atomic_set(&dev_data->state, conf->unbound_mode); } dev_data->cb = cb; @@ -270,60 +412,82 @@ int icmsg_open(const struct icmsg_config_t *conf, k_mutex_init(&dev_data->tx_lock); #endif - int ret = pbuf_tx_init(dev_data->tx_pb); - - if (ret < 0) { - __ASSERT(false, "Incorrect Tx configuration"); - return ret; - } - ret = pbuf_rx_init(dev_data->rx_pb); if (ret < 0) { __ASSERT(false, "Incorrect Rx configuration"); - return ret; + goto cleanup_and_exit; } - ret = pbuf_write(dev_data->tx_pb, magic, sizeof(magic)); - - if (ret < 0) { - __ASSERT_NO_MSG(false); - return ret; + if (conf->unbound_mode != ICMSG_UNBOUND_MODE_DISABLE && + (UNBOUND_ENABLED || UNBOUND_DETECT)) { + /* Increment local session id without conflicts with forbidden values. */ + uint32_t local_sid_ack = + LOCAL_SID_ACK_FROM_TX(pbuf_handshake_read(dev_data->tx_pb)); + dev_data->local_sid = + LOCAL_SID_REQ_FROM_RX(pbuf_handshake_read(dev_data->tx_pb)); + dev_data->remote_sid = SID_DISCONNECTED; + do { + dev_data->local_sid = (dev_data->local_sid + 1) & 0xFFFF; + } while (dev_data->local_sid == local_sid_ack || + dev_data->local_sid == SID_DISCONNECTED); + /* Write local session id request without remote acknowledge */ + pbuf_handshake_write(dev_data->rx_pb, + MAKE_RX_HANDSHAKE(dev_data->local_sid, SID_DISCONNECTED)); + } else if (UNBOUND_DISABLED) { + ret = initialize_tx_with_sid_disabled(dev_data); } - if (ret < (int)sizeof(magic)) { - __ASSERT_NO_MSG(ret == sizeof(magic)); - return ret; + if (old_state == ICMSG_STATE_OFF && (UNBOUND_ENABLED || UNBOUND_DETECT)) { + /* Initialize mbox only if we are doing first-time open (not re-open + * after unbound) + */ + ret = mbox_init(conf, dev_data); + if (ret) { + goto cleanup_and_exit; + } } - ret = mbox_init(conf, dev_data); - if (ret) { - return ret; - } -#ifdef CONFIG_MULTITHREADING - ret = k_work_schedule_for_queue(workq, &dev_data->notify_work, K_NO_WAIT); + /* We need to send a notification to remote, it may not be delivered + * since it may be uninitialized yet, but when it finishes the initialization + * we get a notification from it. We need to send this notification in callback + * again to make sure that it arrived. + */ + ret = mbox_send_dt(&conf->mbox_tx, NULL); + if (ret < 0) { - return ret; + __ASSERT(false, "Cannot send mbox notification"); + goto cleanup_and_exit; } -#else - notify_process(dev_data); -#endif - return 0; + + return ret; + +cleanup_and_exit: + atomic_set(&dev_data->state, ICMSG_STATE_OFF); + return ret; } int icmsg_close(const struct icmsg_config_t *conf, struct icmsg_data_t *dev_data) { - int ret; + int ret = 0; + enum icmsg_state old_state; - ret = mbox_deinit(conf, dev_data); - if (ret) { - return ret; + if (conf->unbound_mode != ICMSG_UNBOUND_MODE_DISABLE && + (UNBOUND_ENABLED || UNBOUND_DETECT)) { + pbuf_handshake_write(dev_data->rx_pb, + MAKE_RX_HANDSHAKE(SID_DISCONNECTED, SID_DISCONNECTED)); } - atomic_set(&dev_data->state, ICMSG_STATE_OFF); + (void)mbox_send_dt(&conf->mbox_tx, NULL); - return 0; + old_state = atomic_set(&dev_data->state, ICMSG_STATE_OFF); + + if (old_state != ICMSG_STATE_OFF) { + ret = mbox_deinit(conf, dev_data); + } + + return ret; } int icmsg_send(const struct icmsg_config_t *conf, @@ -332,13 +496,15 @@ int icmsg_send(const struct icmsg_config_t *conf, { int ret; int write_ret; -#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC int release_ret; -#endif int sent_bytes; + uint32_t state = atomic_get(&dev_data->state); - if (!is_endpoint_ready(dev_data)) { - return -EBUSY; + if (!is_endpoint_ready(state)) { + /* If instance was disconnected on the remote side, some threads may still + * don't know it yet and still may try to send messages. + */ + return (state == ICMSG_STATE_DISCONNECTED) ? len : -EBUSY; } /* Empty message is not allowed */ @@ -346,19 +512,15 @@ int icmsg_send(const struct icmsg_config_t *conf, return -ENODATA; } -#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC ret = reserve_tx_buffer_if_unused(dev_data); if (ret < 0) { return -ENOBUFS; } -#endif write_ret = pbuf_write(dev_data->tx_pb, msg, len); -#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC release_ret = release_tx_buffer(dev_data); __ASSERT_NO_MSG(!release_ret); -#endif if (write_ret < 0) { return write_ret; diff --git a/subsys/ipc/ipc_service/lib/pbuf.c b/subsys/ipc/ipc_service/lib/pbuf.c index c744946f4ca4..674c6f4d4636 100644 --- a/subsys/ipc/ipc_service/lib/pbuf.c +++ b/subsys/ipc/ipc_service/lib/pbuf.c @@ -38,6 +38,7 @@ static int validate_cfg(const struct pbuf_cfg *cfg) /* Validate pointer alignment. */ if (!IS_PTR_ALIGNED_BYTES(cfg->rd_idx_loc, MAX(cfg->dcache_alignment, _PBUF_IDX_SIZE)) || !IS_PTR_ALIGNED_BYTES(cfg->wr_idx_loc, MAX(cfg->dcache_alignment, _PBUF_IDX_SIZE)) || + !IS_PTR_ALIGNED_BYTES(cfg->handshake_loc, _PBUF_IDX_SIZE) || !IS_PTR_ALIGNED_BYTES(cfg->data_loc, _PBUF_IDX_SIZE)) { return -EINVAL; } @@ -49,6 +50,8 @@ static int validate_cfg(const struct pbuf_cfg *cfg) /* Validate pointer values. */ if (!(cfg->rd_idx_loc < cfg->wr_idx_loc) || + (cfg->handshake_loc && !(cfg->rd_idx_loc < cfg->handshake_loc)) || + !(cfg->handshake_loc < cfg->wr_idx_loc) || !((uint8_t *)cfg->wr_idx_loc < cfg->data_loc) || !(((uint8_t *)cfg->rd_idx_loc + MAX(_PBUF_IDX_SIZE, cfg->dcache_alignment)) == (uint8_t *)cfg->wr_idx_loc)) { @@ -176,6 +179,44 @@ int pbuf_write(struct pbuf *pb, const char *data, uint16_t len) return len; } +int pbuf_get_initial_buf(struct pbuf *pb, volatile char **buf, uint16_t *len) +{ + uint32_t wr_idx; + uint16_t plen; + + if (pb == NULL || pb->data.rd_idx != 0) { + /* Incorrect call. */ + return -EINVAL; + } + + sys_cache_data_invd_range((void *)(pb->cfg->wr_idx_loc), sizeof(*(pb->cfg->wr_idx_loc))); + __sync_synchronize(); + + wr_idx = *(pb->cfg->wr_idx_loc); + if (wr_idx >= pb->cfg->len || wr_idx > 0xFFFF || wr_idx == 0) { + /* Wrong index - probably pbuf was not initialized or message was not send yet. */ + return -EINVAL; + } + + sys_cache_data_invd_range((void *)(pb->cfg->data_loc), PBUF_PACKET_LEN_SZ); + __sync_synchronize(); + + plen = sys_get_be16(&pb->cfg->data_loc[0]); + + if (plen + 4 > wr_idx) { + /* Wrong length - probably pbuf was not initialized or message was not send yet. */ + return -EINVAL; + } + + *buf = &pb->cfg->data_loc[PBUF_PACKET_LEN_SZ]; + *len = plen; + + sys_cache_data_invd_range((void *)*buf, plen); + __sync_synchronize(); + + return 0; +} + int pbuf_read(struct pbuf *pb, char *buf, uint16_t len) { if (pb == NULL) { @@ -253,3 +294,23 @@ int pbuf_read(struct pbuf *pb, char *buf, uint16_t len) return len; } + +uint32_t pbuf_handshake_read(struct pbuf *pb) +{ + volatile uint32_t *ptr = pb->cfg->handshake_loc; + + __ASSERT_NO_MSG(ptr); + sys_cache_data_invd_range((void *)ptr, sizeof(*ptr)); + __sync_synchronize(); + return *ptr; +} + +void pbuf_handshake_write(struct pbuf *pb, uint32_t value) +{ + volatile uint32_t *ptr = pb->cfg->handshake_loc; + + __ASSERT_NO_MSG(ptr); + *ptr = value; + __sync_synchronize(); + sys_cache_data_flush_range((void *)ptr, sizeof(*ptr)); +} diff --git a/tests/subsys/ipc/ipc_sessions/CMakeLists.txt b/tests/subsys/ipc/ipc_sessions/CMakeLists.txt new file mode 100644 index 000000000000..79b2b9c49c36 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/CMakeLists.txt @@ -0,0 +1,16 @@ +# 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}) + +zephyr_sources_ifdef(CONFIG_IPC_SERVICE_ICMSG_V1 interoperability/icmsg_v1.c) +zephyr_sources_ifdef(CONFIG_PBUF_V1 interoperability/pbuf_v1.c) +zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICMSG_V1 interoperability/ipc_icmsg_v1.c) diff --git a/tests/subsys/ipc/ipc_sessions/Kconfig b/tests/subsys/ipc/ipc_sessions/Kconfig new file mode 100644 index 000000000000..1c1ec5f3725c --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/Kconfig @@ -0,0 +1,37 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +rsource "interoperability/Kconfig" + +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. + +config IPC_TEST_SKIP_CORE_RESET + bool "Skip the tests that includes core resetting" + help + Some of the cores cannot be safely restarted. + Skip the tests that require it in such a cases. + +config IPC_TEST_BLOCK_SIZE + int "Block size for multiple transfers test" + default 32 + +config IPC_TEST_BLOCK_CNT + int "Number of blocks for multiple transfers test" + default 8000 + +config IPC_TEST_SKIP_UNBOUND + bool "Skip unbound tests" + help + Whether to skip tests that requires unbound callback functionality. diff --git a/tests/subsys/ipc/ipc_sessions/Kconfig.sysbuild b/tests/subsys/ipc/ipc_sessions/Kconfig.sysbuild new file mode 100644 index 000000000000..3c064f59d525 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/Kconfig.sysbuild @@ -0,0 +1,13 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +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/cpurad" 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..4946c417b161 --- /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: Apache-2.0 +# + +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..d87bb33b3b31 --- /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: Apache-2.0 + */ + +/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..68efc0800224 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Replace default ipc0 instance */ +&ipc0 { + compatible = "zephyr,ipc-icmsg"; + /delete-property/ tx-blocks; + /delete-property/ rx-blocks; + unbound = "enable"; +}; diff --git a/tests/subsys/ipc/ipc_sessions/boards/nrf54h20dk_nrf54h20_cpuapp_cpuppr.overlay b/tests/subsys/ipc/ipc_sessions/boards/nrf54h20dk_nrf54h20_cpuapp_cpuppr.overlay new file mode 100644 index 000000000000..90ef82327e9f --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/boards/nrf54h20dk_nrf54h20_cpuapp_cpuppr.overlay @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/* 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..f992d34d52ea --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/common/test_commands.h @@ -0,0 +1,81 @@ +/* + * 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 */ + /* Commands used for data transfer test */ + IPC_TEST_CMD_RXSTART, /**< Start receiving data */ + IPC_TEST_CMD_TXSTART, /**< Start sending data */ + IPC_TEST_CMD_RXGET, /**< Get rx status */ + IPC_TEST_CMD_TXGET, /**< Get tx status */ + IPC_TEST_CMD_XSTAT, /**< rx/tx status response */ + IPC_TEST_CMD_XDATA, /**< Transfer data block */ + /* End of commands used for data transfer test */ +}; + +/** + * @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; +}; + +/** + * @brief Start the rx or tx transfer + */ +struct ipc_test_cmd_xstart { + struct ipc_test_cmd base; + uint32_t blk_size; + uint32_t blk_cnt; + uint32_t seed; +}; + +/** + * @brief Get the status of rx or tx transfer + */ +struct ipc_test_cmd_xstat { + struct ipc_test_cmd base; + uint32_t blk_cnt; /**< Transfers left */ + int32_t result; /**< Current result */ +}; + +/** + * @brief The result of rx or tx transfer + */ +struct ipc_test_cmd_xrsp { + struct ipc_test_cmd base; + int32_t result; +}; + +#endif /* TEST_COMMANDS_H */ diff --git a/tests/subsys/ipc/ipc_sessions/interoperability/Kconfig b/tests/subsys/ipc/ipc_sessions/interoperability/Kconfig new file mode 100644 index 000000000000..e50f208b1534 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/interoperability/Kconfig @@ -0,0 +1,29 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +config IPC_SERVICE_BACKEND_ICMSG + default n if IPC_SERVICE_BACKEND_ICMSG_V1 + +config IPC_SERVICE_ICMSG + default n if IPC_SERVICE_ICMSG_V1 + +config IPC_SERVICE_BACKEND_ICMSG_V1 + bool "ICMSG backend with SPSC packet buffer (old implementation)" + depends on MBOX + select IPC_SERVICE_ICMSG_V1 + help + Chosing this backend results in single endpoint implementation based + on circular packet buffer. + +menuconfig IPC_SERVICE_ICMSG_V1 + bool "icmsg IPC library (old implementation)" + select PBUF_V1 + help + Icmsg library + +if IPC_SERVICE_ICMSG_V1 + rsource "Kconfig.icmsg_v1" +endif diff --git a/tests/subsys/ipc/ipc_sessions/interoperability/Kconfig.icmsg_v1 b/tests/subsys/ipc/ipc_sessions/interoperability/Kconfig.icmsg_v1 new file mode 100644 index 000000000000..463a1a0ad3ce --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/interoperability/Kconfig.icmsg_v1 @@ -0,0 +1,90 @@ +# Copyright (c) 2022 Nordic Semiconductor (ASA) +# SPDX-License-Identifier: Apache-2.0 + +config IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC_V1 + bool "Synchronize access to shared memory" + depends on MULTITHREADING + default y + help + Provide synchronization access to shared memory at a library level. + This option is enabled by default to allow to use sending API from + multiple contexts. Mutex is used to guard access to the memory. + This option can be safely disabled if an application ensures data + are sent from single context. + +config IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS_V1 + int "Mutex lock timeout in milliseconds" + depends on IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC_V1 + range 1 5 + default 1 + help + Maximum time to wait, in milliseconds, for access to send data with + backends basing on icmsg library. This time should be relatively low. + +config IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS_V1 + int "Bond notification timeout in miliseconds" + range 1 100 + default 1 + help + Time to wait for remote bonding notification before the + notification is repeated. + +config IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE_V1 + bool "Use dedicated workqueue" + depends on MULTITHREADING + default y + help + Enable dedicated workqueue thread for the ICMsg backend. + Disabling this configuration will cause the ICMsg backend to + process incoming data through the system workqueue context, and + therefore reduces the RAM footprint of the backend. + Disabling this config may result in deadlocks in certain usage + scenarios, such as when synchronous IPC is executed from the system + workqueue context. + The callbacks coming from the backend are executed from the workqueue + context. + When the option is disabled, the user must obey the restrictions + imposed by the system workqueue, such as never performing blocking + operations from within the callback. + +if IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE_V1 + +config IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE_V1 + int "Size of RX work queue stack" + default 1280 + help + Size of stack used by work queue RX thread. This work queue is + created to prevent notifying service users about received data + from the system work queue. The queue is shared among instances. + +config IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY_V1 + int "Priority of RX work queue thread" + default -1 + range -256 -1 + help + Priority of the ICMSG RX work queue thread. + The ICMSG library in its simplicity requires the workqueue to execute + at a cooperative priority. + +endif + +# The Icmsg library in its simplicity requires the system workqueue to execute +# at a cooperative priority. +config SYSTEM_WORKQUEUE_PRIORITY + range -256 -1 if !IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE_V1 + +config PBUF_V1 + bool "Packed buffer support library (old implementation)" + help + The packet buffer implements lightweight unidirectional packet buffer + with read/write semantics on top of a memory region shared by the + reader and writer. It optionally embeds cache and memory barrier + management to ensure correct data access. + +if PBUF_V1 + +config PBUF_RX_READ_BUF_SIZE_V1 + int "Size of PBUF read buffer in bytes" + default 128 + +endif # PBUF diff --git a/tests/subsys/ipc/ipc_sessions/interoperability/icmsg_v1.c b/tests/subsys/ipc/ipc_sessions/interoperability/icmsg_v1.c new file mode 100644 index 000000000000..ae83dd30beb9 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/interoperability/icmsg_v1.c @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "icmsg_v1.h" + +#include +#include +#include +#include "pbuf_v1.h" +#include + +#define BOND_NOTIFY_REPEAT_TO K_MSEC(CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS) +#define SHMEM_ACCESS_TO K_MSEC(CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS) + +static const uint8_t magic[] = {0x45, 0x6d, 0x31, 0x6c, 0x31, 0x4b, + 0x30, 0x72, 0x6e, 0x33, 0x6c, 0x69, 0x34}; + +#ifdef CONFIG_MULTITHREADING +#if defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE) +static K_THREAD_STACK_DEFINE(icmsg_stack, CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE); +static struct k_work_q icmsg_workq; +static struct k_work_q *const workq = &icmsg_workq; +#else +static struct k_work_q *const workq = &k_sys_work_q; +#endif +static void mbox_callback_process(struct k_work *item); +#else +static void mbox_callback_process(struct icmsg_data_t *dev_data); +#endif + +static int mbox_deinit(const struct icmsg_config_t *conf, + struct icmsg_data_t *dev_data) +{ + int err; + + err = mbox_set_enabled_dt(&conf->mbox_rx, 0); + if (err != 0) { + return err; + } + + err = mbox_register_callback_dt(&conf->mbox_rx, NULL, NULL); + if (err != 0) { + return err; + } + +#ifdef CONFIG_MULTITHREADING + (void)k_work_cancel(&dev_data->mbox_work); + (void)k_work_cancel_delayable(&dev_data->notify_work); +#endif + + return 0; +} + +static bool is_endpoint_ready(struct icmsg_data_t *dev_data) +{ + return atomic_get(&dev_data->state) == ICMSG_STATE_READY; +} + +#ifdef CONFIG_MULTITHREADING +static void notify_process(struct k_work *item) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(item); + struct icmsg_data_t *dev_data = + CONTAINER_OF(dwork, struct icmsg_data_t, notify_work); + + (void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL); + + atomic_t state = atomic_get(&dev_data->state); + + if (state != ICMSG_STATE_READY) { + int ret; + + ret = k_work_reschedule_for_queue(workq, dwork, BOND_NOTIFY_REPEAT_TO); + __ASSERT_NO_MSG(ret >= 0); + (void)ret; + } +} +#else +static void notify_process(struct icmsg_data_t *dev_data) +{ + (void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL); +#if defined(CONFIG_SYS_CLOCK_EXISTS) + int64_t start = k_uptime_get(); +#endif + + while (false == is_endpoint_ready(dev_data)) { + mbox_callback_process(dev_data); + +#if defined(CONFIG_SYS_CLOCK_EXISTS) + if ((k_uptime_get() - start) > CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS) { +#endif + (void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL); +#if defined(CONFIG_SYS_CLOCK_EXISTS) + start = k_uptime_get(); + }; +#endif + } +} +#endif + +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC +static int reserve_tx_buffer_if_unused(struct icmsg_data_t *dev_data) +{ + int ret = k_mutex_lock(&dev_data->tx_lock, SHMEM_ACCESS_TO); + + if (ret < 0) { + return ret; + } + + return 0; +} + +static int release_tx_buffer(struct icmsg_data_t *dev_data) +{ + return k_mutex_unlock(&dev_data->tx_lock); +} +#endif + +static uint32_t data_available(struct icmsg_data_t *dev_data) +{ + return pbuf_read(dev_data->rx_pb, NULL, 0); +} + +#ifdef CONFIG_MULTITHREADING +static void submit_mbox_work(struct icmsg_data_t *dev_data) +{ + if (k_work_submit_to_queue(workq, &dev_data->mbox_work) < 0) { + /* The mbox processing work is never canceled. + * The negative error code should never be seen. + */ + __ASSERT_NO_MSG(false); + } +} + +static void submit_work_if_buffer_free(struct icmsg_data_t *dev_data) +{ + submit_mbox_work(dev_data); +} + +static void submit_work_if_buffer_free_and_data_available( + struct icmsg_data_t *dev_data) +{ + if (!data_available(dev_data)) { + return; + } + + submit_mbox_work(dev_data); +} +#else +static void submit_if_buffer_free(struct icmsg_data_t *dev_data) +{ + mbox_callback_process(dev_data); +} + +static void submit_if_buffer_free_and_data_available( + struct icmsg_data_t *dev_data) +{ + + if (!data_available(dev_data)) { + return; + } + + mbox_callback_process(dev_data); +} +#endif + +#ifdef CONFIG_MULTITHREADING +static void mbox_callback_process(struct k_work *item) +#else +static void mbox_callback_process(struct icmsg_data_t *dev_data) +#endif +{ +#ifdef CONFIG_MULTITHREADING + struct icmsg_data_t *dev_data = CONTAINER_OF(item, struct icmsg_data_t, mbox_work); +#endif + uint8_t rx_buffer[CONFIG_PBUF_RX_READ_BUF_SIZE] __aligned(4); + + atomic_t state = atomic_get(&dev_data->state); + + uint32_t len = data_available(dev_data); + + if (len == 0) { + /* Unlikely, no data in buffer. */ + return; + } + + __ASSERT_NO_MSG(len <= sizeof(rx_buffer)); + + if (sizeof(rx_buffer) < len) { + return; + } + + len = pbuf_read(dev_data->rx_pb, rx_buffer, sizeof(rx_buffer)); + + if (state == ICMSG_STATE_READY) { + if (dev_data->cb->received) { + dev_data->cb->received(rx_buffer, len, dev_data->ctx); + } + } else { + __ASSERT_NO_MSG(state == ICMSG_STATE_BUSY); + + /* Allow magic number longer than sizeof(magic) for future protocol version. */ + bool endpoint_invalid = (len < sizeof(magic) || + memcmp(magic, rx_buffer, sizeof(magic))); + + if (endpoint_invalid) { + __ASSERT_NO_MSG(false); + return; + } + + if (dev_data->cb->bound) { + dev_data->cb->bound(dev_data->ctx); + } + + atomic_set(&dev_data->state, ICMSG_STATE_READY); + } +#ifdef CONFIG_MULTITHREADING + submit_work_if_buffer_free_and_data_available(dev_data); +#else + submit_if_buffer_free_and_data_available(dev_data); +#endif +} + +static void mbox_callback(const struct device *instance, uint32_t channel, + void *user_data, struct mbox_msg *msg_data) +{ + struct icmsg_data_t *dev_data = user_data; +#ifdef CONFIG_MULTITHREADING + submit_work_if_buffer_free(dev_data); +#else + submit_if_buffer_free(dev_data); +#endif +} + +static int mbox_init(const struct icmsg_config_t *conf, + struct icmsg_data_t *dev_data) +{ + int err; + +#ifdef CONFIG_MULTITHREADING + k_work_init(&dev_data->mbox_work, mbox_callback_process); + k_work_init_delayable(&dev_data->notify_work, notify_process); +#endif + + err = mbox_register_callback_dt(&conf->mbox_rx, mbox_callback, dev_data); + if (err != 0) { + return err; + } + + return mbox_set_enabled_dt(&conf->mbox_rx, 1); +} + +int icmsg_open(const struct icmsg_config_t *conf, + struct icmsg_data_t *dev_data, + const struct ipc_service_cb *cb, void *ctx) +{ + if (!atomic_cas(&dev_data->state, ICMSG_STATE_OFF, ICMSG_STATE_BUSY)) { + /* Already opened. */ + return -EALREADY; + } + + dev_data->cb = cb; + dev_data->ctx = ctx; + dev_data->cfg = conf; + +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC + k_mutex_init(&dev_data->tx_lock); +#endif + + int ret = pbuf_tx_init(dev_data->tx_pb); + + if (ret < 0) { + __ASSERT(false, "Incorrect configuration"); + return ret; + } + + (void)pbuf_rx_init(dev_data->rx_pb); + + ret = pbuf_write(dev_data->tx_pb, magic, sizeof(magic)); + + if (ret < 0) { + __ASSERT_NO_MSG(false); + return ret; + } + + if (ret < (int)sizeof(magic)) { + __ASSERT_NO_MSG(ret == sizeof(magic)); + return ret; + } + + ret = mbox_init(conf, dev_data); + if (ret) { + return ret; + } +#ifdef CONFIG_MULTITHREADING + ret = k_work_schedule_for_queue(workq, &dev_data->notify_work, K_NO_WAIT); + if (ret < 0) { + return ret; + } +#else + notify_process(dev_data); +#endif + return 0; +} + +int icmsg_close(const struct icmsg_config_t *conf, + struct icmsg_data_t *dev_data) +{ + int ret; + + ret = mbox_deinit(conf, dev_data); + if (ret) { + return ret; + } + + atomic_set(&dev_data->state, ICMSG_STATE_OFF); + + return 0; +} + +int icmsg_send(const struct icmsg_config_t *conf, + struct icmsg_data_t *dev_data, + const void *msg, size_t len) +{ + int ret; + int write_ret; +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC + int release_ret; +#endif + int sent_bytes; + + if (!is_endpoint_ready(dev_data)) { + return -EBUSY; + } + + /* Empty message is not allowed */ + if (len == 0) { + return -ENODATA; + } + +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC + ret = reserve_tx_buffer_if_unused(dev_data); + if (ret < 0) { + return -ENOBUFS; + } +#endif + + write_ret = pbuf_write(dev_data->tx_pb, msg, len); + +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC + release_ret = release_tx_buffer(dev_data); + __ASSERT_NO_MSG(!release_ret); +#endif + + if (write_ret < 0) { + return write_ret; + } else if (write_ret < len) { + return -EBADMSG; + } + sent_bytes = write_ret; + + __ASSERT_NO_MSG(conf->mbox_tx.dev != NULL); + + ret = mbox_send_dt(&conf->mbox_tx, NULL); + if (ret) { + return ret; + } + + return sent_bytes; +} + +#if defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE) + +static int work_q_init(void) +{ + struct k_work_queue_config cfg = { + .name = "icmsg_workq", + }; + + k_work_queue_start(&icmsg_workq, + icmsg_stack, + K_KERNEL_STACK_SIZEOF(icmsg_stack), + CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY, &cfg); + return 0; +} + +SYS_INIT(work_q_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); + +#endif diff --git a/tests/subsys/ipc/ipc_sessions/interoperability/icmsg_v1.h b/tests/subsys/ipc/ipc_sessions/interoperability/icmsg_v1.h new file mode 100644 index 000000000000..b0f2849765d2 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/interoperability/icmsg_v1.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_IPC_ICMSG_H_ +#define ZEPHYR_INCLUDE_IPC_ICMSG_H_ + +#include +#include +#include +#include +#include +#include "pbuf_v1.h" +#include + +/* Config aliases that prevenets from config collisions: */ +#undef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC_V1 +#define CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC_V1 +#endif +#undef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS_V1 +#define CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS_V1 +#endif +#undef CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS +#ifdef CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS_V1 +#define CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS \ + CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS_V1 +#endif +#undef CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE +#ifdef CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE_V1 +#define CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE_V1 +#endif +#undef CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE +#ifdef CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE_V1 +#define CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE \ + CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE_V1 +#endif +#undef CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY +#ifdef CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY_V1 +#define CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY_V1 +#endif +#undef CONFIG_PBUF_RX_READ_BUF_SIZE +#ifdef CONFIG_PBUF_RX_READ_BUF_SIZE_V1 +#define CONFIG_PBUF_RX_READ_BUF_SIZE CONFIG_PBUF_RX_READ_BUF_SIZE_V1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Icmsg IPC library API + * @defgroup ipc_icmsg_api Icmsg IPC library API + * @ingroup ipc + * @{ + */ + +enum icmsg_state { + ICMSG_STATE_OFF, + ICMSG_STATE_BUSY, + ICMSG_STATE_READY, +}; + +struct icmsg_config_t { + struct mbox_dt_spec mbox_tx; + struct mbox_dt_spec mbox_rx; +}; + +struct icmsg_data_t { + /* Tx/Rx buffers. */ + struct pbuf *tx_pb; + struct pbuf *rx_pb; +#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC + struct k_mutex tx_lock; +#endif + + /* Callbacks for an endpoint. */ + const struct ipc_service_cb *cb; + void *ctx; + + /* General */ + const struct icmsg_config_t *cfg; +#ifdef CONFIG_MULTITHREADING + struct k_work_delayable notify_work; + struct k_work mbox_work; +#endif + atomic_t state; +}; + +/** @brief Open an icmsg instance + * + * Open an icmsg instance to be able to send and receive messages to a remote + * instance. + * This function is blocking until the handshake with the remote instance is + * completed. + * This function is intended to be called late in the initialization process, + * possibly from a thread which can be safely blocked while handshake with the + * remote instance is being pefromed. + * + * @param[in] conf Structure containing configuration parameters for the icmsg + * instance. + * @param[inout] dev_data Structure containing run-time data used by the icmsg + * instance. + * @param[in] cb Structure containing callback functions to be called on + * events generated by this icmsg instance. The pointed memory + * must be preserved while the icmsg instance is active. + * @param[in] ctx Pointer to context passed as an argument to callbacks. + * + * + * @retval 0 on success. + * @retval -EALREADY when the instance is already opened. + * @retval other errno codes from dependent modules. + */ +int icmsg_open(const struct icmsg_config_t *conf, + struct icmsg_data_t *dev_data, + const struct ipc_service_cb *cb, void *ctx); + +/** @brief Close an icmsg instance + * + * Closing an icmsg instance results in releasing all resources used by given + * instance including the shared memory regions and mbox devices. + * + * @param[in] conf Structure containing configuration parameters for the icmsg + * instance being closed. Its content must be the same as used + * for creating this instance with @ref icmsg_open. + * @param[inout] dev_data Structure containing run-time data used by the icmsg + * instance. + * + * @retval 0 on success. + * @retval other errno codes from dependent modules. + */ +int icmsg_close(const struct icmsg_config_t *conf, + struct icmsg_data_t *dev_data); + +/** @brief Send a message to the remote icmsg instance. + * + * @param[in] conf Structure containing configuration parameters for the icmsg + * instance. + * @param[inout] dev_data Structure containing run-time data used by the icmsg + * instance. + * @param[in] msg Pointer to a buffer containing data to send. + * @param[in] len Size of data in the @p msg buffer. + * + * + * @retval Number of sent bytes. + * @retval -EBUSY when the instance has not finished handshake with the remote + * instance. + * @retval -ENODATA when the requested data to send is empty. + * @retval -EBADMSG when the requested data to send is too big. + * @retval -ENOBUFS when there are no TX buffers available. + * @retval other errno codes from dependent modules. + */ +int icmsg_send(const struct icmsg_config_t *conf, + struct icmsg_data_t *dev_data, + const void *msg, size_t len); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_IPC_ICMSG_H_ */ diff --git a/tests/subsys/ipc/ipc_sessions/interoperability/ipc_icmsg_v1.c b/tests/subsys/ipc/ipc_sessions/interoperability/ipc_icmsg_v1.c new file mode 100644 index 000000000000..4ce003667b05 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/interoperability/ipc_icmsg_v1.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ipc_icmsg_v1.h" + +#include +#include +#include "icmsg_v1.h" + +#include + +#define DT_DRV_COMPAT zephyr_ipc_icmsg + +static int register_ept(const struct device *instance, void **token, + const struct ipc_ept_cfg *cfg) +{ + const struct icmsg_config_t *conf = instance->config; + struct icmsg_data_t *dev_data = instance->data; + + /* Only one endpoint is supported. No need for a token. */ + *token = NULL; + + return icmsg_open(conf, dev_data, &cfg->cb, cfg->priv); +} + +static int deregister_ept(const struct device *instance, void *token) +{ + const struct icmsg_config_t *conf = instance->config; + struct icmsg_data_t *dev_data = instance->data; + + return icmsg_close(conf, dev_data); +} + +static int send(const struct device *instance, void *token, + const void *msg, size_t len) +{ + const struct icmsg_config_t *conf = instance->config; + struct icmsg_data_t *dev_data = instance->data; + + return icmsg_send(conf, dev_data, msg, len); +} + +const static struct ipc_service_backend backend_ops = { + .register_endpoint = register_ept, + .deregister_endpoint = deregister_ept, + .send = send, +}; + +static int backend_init(const struct device *instance) +{ + return 0; +} + +#define DEFINE_BACKEND_DEVICE(i) \ + static const struct icmsg_config_t backend_config_##i = { \ + .mbox_tx = MBOX_DT_SPEC_INST_GET(i, tx), \ + .mbox_rx = MBOX_DT_SPEC_INST_GET(i, rx), \ + }; \ + \ + PBUF_DEFINE(tx_pb_##i, \ + DT_REG_ADDR(DT_INST_PHANDLE(i, tx_region)), \ + DT_REG_SIZE(DT_INST_PHANDLE(i, tx_region)), \ + DT_INST_PROP_OR(i, dcache_alignment, 0)); \ + PBUF_DEFINE(rx_pb_##i, \ + DT_REG_ADDR(DT_INST_PHANDLE(i, rx_region)), \ + DT_REG_SIZE(DT_INST_PHANDLE(i, rx_region)), \ + DT_INST_PROP_OR(i, dcache_alignment, 0)); \ + \ + static struct icmsg_data_t backend_data_##i = { \ + .tx_pb = &tx_pb_##i, \ + .rx_pb = &rx_pb_##i, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(i, \ + &backend_init, \ + NULL, \ + &backend_data_##i, \ + &backend_config_##i, \ + POST_KERNEL, \ + CONFIG_IPC_SERVICE_REG_BACKEND_PRIORITY, \ + &backend_ops); + +DT_INST_FOREACH_STATUS_OKAY(DEFINE_BACKEND_DEVICE) diff --git a/tests/subsys/ipc/ipc_sessions/interoperability/ipc_icmsg_v1.h b/tests/subsys/ipc/ipc_sessions/interoperability/ipc_icmsg_v1.h new file mode 100644 index 000000000000..5407ea3e2e8c --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/interoperability/ipc_icmsg_v1.h @@ -0,0 +1,5 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ diff --git a/tests/subsys/ipc/ipc_sessions/interoperability/pbuf_v1.c b/tests/subsys/ipc/ipc_sessions/interoperability/pbuf_v1.c new file mode 100644 index 000000000000..7c72098af1d7 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/interoperability/pbuf_v1.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "pbuf_v1.h" +#include + +#if defined(CONFIG_ARCH_POSIX) +#include +#endif + +/* Helper funciton for getting number of bytes being written to the buffer. */ +static uint32_t idx_occupied(uint32_t len, uint32_t wr_idx, uint32_t rd_idx) +{ + /* It is implicitly assumed wr_idx and rd_idx cannot differ by more then len. */ + return (rd_idx > wr_idx) ? (len - (rd_idx - wr_idx)) : (wr_idx - rd_idx); +} + +/* Helper function for wrapping the index from the begging if above buffer len. */ +static uint32_t idx_wrap(uint32_t len, uint32_t idx) +{ + return (idx >= len) ? (idx % len) : (idx); +} + +static int validate_cfg(const struct pbuf_cfg *cfg) +{ + /* Validate pointers. */ + if (!cfg || !cfg->rd_idx_loc || !cfg->wr_idx_loc || !cfg->data_loc) { + return -EINVAL; + } + + /* Validate pointer alignment. */ + if (!IS_PTR_ALIGNED_BYTES(cfg->rd_idx_loc, MAX(cfg->dcache_alignment, _PBUF_IDX_SIZE)) || + !IS_PTR_ALIGNED_BYTES(cfg->wr_idx_loc, MAX(cfg->dcache_alignment, _PBUF_IDX_SIZE)) || + !IS_PTR_ALIGNED_BYTES(cfg->data_loc, _PBUF_IDX_SIZE)) { + return -EINVAL; + } + + /* Validate len. */ + if (cfg->len < _PBUF_MIN_DATA_LEN || !IS_PTR_ALIGNED_BYTES(cfg->len, _PBUF_IDX_SIZE)) { + return -EINVAL; + } + + /* Validate pointer values. */ + if (!(cfg->rd_idx_loc < cfg->wr_idx_loc) || + !((uint8_t *)cfg->wr_idx_loc < cfg->data_loc) || + !(((uint8_t *)cfg->rd_idx_loc + MAX(_PBUF_IDX_SIZE, cfg->dcache_alignment)) == + (uint8_t *)cfg->wr_idx_loc)) { + return -EINVAL; + } + + return 0; +} + +#if defined(CONFIG_ARCH_POSIX) +void pbuf_native_addr_remap(struct pbuf *pb) +{ + native_emb_addr_remap((void **)&pb->cfg->rd_idx_loc); + native_emb_addr_remap((void **)&pb->cfg->wr_idx_loc); + native_emb_addr_remap((void **)&pb->cfg->data_loc); +} +#endif + +int pbuf_tx_init(struct pbuf *pb) +{ + if (validate_cfg(pb->cfg) != 0) { + return -EINVAL; + } +#if defined(CONFIG_ARCH_POSIX) + pbuf_native_addr_remap(pb); +#endif + + /* Initialize local copy of indexes. */ + pb->data.wr_idx = 0; + pb->data.rd_idx = 0; + + /* Clear shared memory. */ + *(pb->cfg->wr_idx_loc) = pb->data.wr_idx; + *(pb->cfg->rd_idx_loc) = pb->data.rd_idx; + + __sync_synchronize(); + + /* Take care cache. */ + sys_cache_data_flush_range((void *)(pb->cfg->wr_idx_loc), sizeof(*(pb->cfg->wr_idx_loc))); + sys_cache_data_flush_range((void *)(pb->cfg->rd_idx_loc), sizeof(*(pb->cfg->rd_idx_loc))); + + return 0; +} + +int pbuf_rx_init(struct pbuf *pb) +{ + if (validate_cfg(pb->cfg) != 0) { + return -EINVAL; + } +#if defined(CONFIG_ARCH_POSIX) + pbuf_native_addr_remap(pb); +#endif + + /* Initialize local copy of indexes. */ + pb->data.wr_idx = 0; + pb->data.rd_idx = 0; + + return 0; +} + +int pbuf_write(struct pbuf *pb, const char *data, uint16_t len) +{ + if (pb == NULL || len == 0 || data == NULL) { + /* Incorrect call. */ + return -EINVAL; + } + + /* Invalidate rd_idx only, local wr_idx is used to increase buffer security. */ + sys_cache_data_invd_range((void *)(pb->cfg->rd_idx_loc), sizeof(*(pb->cfg->rd_idx_loc))); + __sync_synchronize(); + + uint8_t *const data_loc = pb->cfg->data_loc; + const uint32_t blen = pb->cfg->len; + uint32_t rd_idx = *(pb->cfg->rd_idx_loc); + uint32_t wr_idx = pb->data.wr_idx; + + /* wr_idx must always be aligned. */ + __ASSERT_NO_MSG(IS_PTR_ALIGNED_BYTES(wr_idx, _PBUF_IDX_SIZE)); + /* rd_idx shall always be aligned, but its value is received from the reader. + * Can not assert. + */ + if (!IS_PTR_ALIGNED_BYTES(rd_idx, _PBUF_IDX_SIZE)) { + return -EINVAL; + } + + uint32_t free_space = blen - idx_occupied(blen, wr_idx, rd_idx) - _PBUF_IDX_SIZE; + + /* Packet length, data + packet length size. */ + uint32_t plen = len + PBUF_PACKET_LEN_SZ; + + /* Check if packet will fit into the buffer. */ + if (free_space < plen) { + return -ENOMEM; + } + + /* Clear packet len with zeros and update. Clearing is done for possible versioning in the + * future. Writing is allowed now, because shared wr_idx value is updated at the very end. + */ + *((uint32_t *)(&data_loc[wr_idx])) = 0; + sys_put_be16(len, &data_loc[wr_idx]); + __sync_synchronize(); + sys_cache_data_flush_range(&data_loc[wr_idx], PBUF_PACKET_LEN_SZ); + + wr_idx = idx_wrap(blen, wr_idx + PBUF_PACKET_LEN_SZ); + + /* Write until end of the buffer, if data will be wrapped. */ + uint32_t tail = MIN(len, blen - wr_idx); + + memcpy(&data_loc[wr_idx], data, tail); + sys_cache_data_flush_range(&data_loc[wr_idx], tail); + + if (len > tail) { + /* Copy remaining data to buffer front. */ + memcpy(&data_loc[0], data + tail, len - tail); + sys_cache_data_flush_range(&data_loc[0], len - tail); + } + + wr_idx = idx_wrap(blen, ROUND_UP(wr_idx + len, _PBUF_IDX_SIZE)); + /* Update wr_idx. */ + pb->data.wr_idx = wr_idx; + *(pb->cfg->wr_idx_loc) = wr_idx; + __sync_synchronize(); + sys_cache_data_flush_range((void *)pb->cfg->wr_idx_loc, sizeof(*(pb->cfg->wr_idx_loc))); + + return len; +} + +int pbuf_read(struct pbuf *pb, char *buf, uint16_t len) +{ + if (pb == NULL) { + /* Incorrect call. */ + return -EINVAL; + } + + /* Invalidate wr_idx only, local rd_idx is used to increase buffer security. */ + sys_cache_data_invd_range((void *)(pb->cfg->wr_idx_loc), sizeof(*(pb->cfg->wr_idx_loc))); + __sync_synchronize(); + + uint8_t *const data_loc = pb->cfg->data_loc; + const uint32_t blen = pb->cfg->len; + uint32_t wr_idx = *(pb->cfg->wr_idx_loc); + uint32_t rd_idx = pb->data.rd_idx; + + /* rd_idx must always be aligned. */ + __ASSERT_NO_MSG(IS_PTR_ALIGNED_BYTES(rd_idx, _PBUF_IDX_SIZE)); + /* wr_idx shall always be aligned, but its value is received from the + * writer. Can not assert. + */ + if (!IS_PTR_ALIGNED_BYTES(wr_idx, _PBUF_IDX_SIZE)) { + return -EINVAL; + } + + if (rd_idx == wr_idx) { + /* Buffer is empty. */ + return 0; + } + + /* Get packet len.*/ + sys_cache_data_invd_range(&data_loc[rd_idx], PBUF_PACKET_LEN_SZ); + uint16_t plen = sys_get_be16(&data_loc[rd_idx]); + + if (!buf) { + return (int)plen; + } + + if (plen > len) { + return -ENOMEM; + } + + uint32_t occupied_space = idx_occupied(blen, wr_idx, rd_idx); + + if (occupied_space < plen + PBUF_PACKET_LEN_SZ) { + /* This should never happen. */ + return -EAGAIN; + } + + rd_idx = idx_wrap(blen, rd_idx + PBUF_PACKET_LEN_SZ); + + /* Packet will fit into provided buffer, truncate len if provided len + * is bigger than necessary. + */ + len = MIN(plen, len); + + /* Read until end of the buffer, if data are wrapped. */ + uint32_t tail = MIN(blen - rd_idx, len); + + sys_cache_data_invd_range(&data_loc[rd_idx], tail); + memcpy(buf, &data_loc[rd_idx], tail); + + if (len > tail) { + sys_cache_data_invd_range(&data_loc[0], len - tail); + memcpy(&buf[tail], &pb->cfg->data_loc[0], len - tail); + } + + /* Update rd_idx. */ + rd_idx = idx_wrap(blen, ROUND_UP(rd_idx + len, _PBUF_IDX_SIZE)); + + pb->data.rd_idx = rd_idx; + *(pb->cfg->rd_idx_loc) = rd_idx; + __sync_synchronize(); + sys_cache_data_flush_range((void *)pb->cfg->rd_idx_loc, sizeof(*(pb->cfg->rd_idx_loc))); + + return len; +} diff --git a/tests/subsys/ipc/ipc_sessions/interoperability/pbuf_v1.h b/tests/subsys/ipc/ipc_sessions/interoperability/pbuf_v1.h new file mode 100644 index 000000000000..8783cdbbf146 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/interoperability/pbuf_v1.h @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_IPC_PBUF_H_ +#define ZEPHYR_INCLUDE_IPC_PBUF_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Packed buffer API + * @defgroup pbuf Packed Buffer API + * @ingroup ipc + * @{ + */ + +/** @brief Size of packet length field. */ +#define PBUF_PACKET_LEN_SZ sizeof(uint32_t) + +/* Amount of data that is left unused to distinguish between empty and full. */ +#define _PBUF_IDX_SIZE sizeof(uint32_t) + +/* Minimal length of the data field in the buffer to store the smalest packet + * possible. + * (+1) for at least one byte of data. + * (+_PBUF_IDX_SIZE) to distinguish buffer full and buffer empty. + * Rounded up to keep wr/rd indexes pointing to aligned address. + */ +#define _PBUF_MIN_DATA_LEN ROUND_UP(PBUF_PACKET_LEN_SZ + 1 + _PBUF_IDX_SIZE, _PBUF_IDX_SIZE) + +#if defined(CONFIG_ARCH_POSIX) +/* For the native simulated boards we need to modify some pointers at init */ +#define PBUF_MAYBE_CONST +#else +#define PBUF_MAYBE_CONST const +#endif + +/** @brief Control block of packet buffer. + * + * The structure contains configuration data. + */ +struct pbuf_cfg { + volatile uint32_t *rd_idx_loc; /* Address of the variable holding + * index value of the first valid byte + * in data[]. + */ + volatile uint32_t *wr_idx_loc; /* Address of the variable holding + * index value of the first free byte + * in data[]. + */ + uint32_t dcache_alignment; /* CPU data cache line size in bytes. + * Used for validation - TODO: To be + * replaced by flags. + */ + uint32_t len; /* Length of data[] in bytes. */ + uint8_t *data_loc; /* Location of the data[]. */ +}; + +/** + * @brief Data block of the packed buffer. + * + * The structure contains local copies of wr and rd indexes used by writer and + * reader respectively. + */ +struct pbuf_data { + volatile uint32_t wr_idx; /* Index of the first holding first + * free byte in data[]. Used for + * writing. + */ + volatile uint32_t rd_idx; /* Index of the first holding first + * valid byte in data[]. Used for + * reading. + */ +}; + + +/** + * @brief Scure packed buffer. + * + * The packet buffer implements lightweight unidirectional packet + * buffer with read/write semantics on top of a memory region shared + * by the reader and writer. It embeds cache and memory barrier management to + * ensure correct data access. + * + * This structure supports single writer and reader. Data stored in the buffer + * is encapsulated to a message (with length header). The read/write API is + * written in a way to protect the data from being corrupted. + */ +struct pbuf { + PBUF_MAYBE_CONST struct pbuf_cfg *const cfg; /* Configuration of the + * buffer. + */ + struct pbuf_data data; /* Data used to read and write + * to the buffer + */ +}; + +/** + * @brief Macro for configuration initialization. + * + * It is recommended to use this macro to initialize packed buffer + * configuration. + * + * @param mem_addr Memory address for pbuf. + * @param size Size of the memory. + * @param dcache_align Data cache alignment. + */ +#define PBUF_CFG_INIT(mem_addr, size, dcache_align) \ +{ \ + .rd_idx_loc = (uint32_t *)(mem_addr), \ + .wr_idx_loc = (uint32_t *)((uint8_t *)(mem_addr) + \ + MAX(dcache_align, _PBUF_IDX_SIZE)), \ + .data_loc = (uint8_t *)((uint8_t *)(mem_addr) + \ + MAX(dcache_align, _PBUF_IDX_SIZE) + _PBUF_IDX_SIZE), \ + .len = (uint32_t)((uint32_t)(size) - MAX(dcache_align, _PBUF_IDX_SIZE) - \ + _PBUF_IDX_SIZE), \ + .dcache_alignment = (dcache_align), \ +} + +/** + * @brief Macro calculates memory overhead taken by the header in shared memory. + * + * It contains the read index, write index and padding. + * + * @param dcache_align Data cache alignment. + */ +#define PBUF_HEADER_OVERHEAD(dcache_align) \ + (MAX(dcache_align, _PBUF_IDX_SIZE) + _PBUF_IDX_SIZE) + +/** + * @brief Statically define and initialize pbuf. + * + * @param name Name of the pbuf. + * @param mem_addr Memory address for pbuf. + * @param size Size of the memory. + * @param dcache_align Data cache line size. + */ +#define PBUF_DEFINE(name, mem_addr, size, dcache_align) \ + BUILD_ASSERT(dcache_align >= 0, \ + "Cache line size must be non negative."); \ + BUILD_ASSERT((size) > 0 && IS_PTR_ALIGNED_BYTES(size, _PBUF_IDX_SIZE), \ + "Incorrect size."); \ + BUILD_ASSERT(IS_PTR_ALIGNED_BYTES(mem_addr, MAX(dcache_align, _PBUF_IDX_SIZE)), \ + "Misaligned memory."); \ + BUILD_ASSERT(size >= (MAX(dcache_align, _PBUF_IDX_SIZE) + _PBUF_IDX_SIZE + \ + _PBUF_MIN_DATA_LEN), "Insufficient size."); \ + static PBUF_MAYBE_CONST struct pbuf_cfg cfg_##name = \ + PBUF_CFG_INIT(mem_addr, size, dcache_align); \ + static struct pbuf name = { \ + .cfg = &cfg_##name, \ + } + +/** + * @brief Initialize the Tx packet buffer. + * + * This function initializes the Tx packet buffer based on provided configuration. + * If the configuration is incorrect, the function will return error. + * + * It is recommended to use PBUF_DEFINE macro for build time initialization. + * + * @param pb Pointer to the packed buffer containing + * configuration and data. Configuration has to be + * fixed before the initialization. + * @retval 0 on success. + * @retval -EINVAL when the input parameter is incorrect. + */ +int pbuf_tx_init(struct pbuf *pb); + +/** + * @brief Initialize the Rx packet buffer. + * + * This function initializes the Rx packet buffer. + * If the configuration is incorrect, the function will return error. + * + * It is recommended to use PBUF_DEFINE macro for build time initialization. + * + * @param pb Pointer to the packed buffer containing + * configuration and data. Configuration has to be + * fixed before the initialization. + * @retval 0 on success. + * @retval -EINVAL when the input parameter is incorrect. + */ +int pbuf_rx_init(struct pbuf *pb); + +/** + * @brief Write specified amount of data to the packet buffer. + * + * This function call writes specified amount of data to the packet buffer if + * the buffer will fit the data. + * + * @param pb A buffer to which to write. + * @param buf Pointer to the data to be written to the buffer. + * @param len Number of bytes to be written to the buffer. Must be positive. + * @retval int Number of bytes written, negative error code on fail. + * -EINVAL, if any of input parameter is incorrect. + * -ENOMEM, if len is bigger than the buffer can fit. + */ + +int pbuf_write(struct pbuf *pb, const char *buf, uint16_t len); + +/** + * @brief Read specified amount of data from the packet buffer. + * + * Single read allows to read the message send by the single write. + * The provided %p buf must be big enough to store the whole message. + * + * @param pb A buffer from which data will be read. + * @param buf Data pointer to which read data will be written. + * If NULL, len of stored message is returned. + * @param len Number of bytes to be read from the buffer. + * @retval int Bytes read, negative error code on fail. + * Bytes to be read, if buf == NULL. + * -EINVAL, if any of input parameter is incorrect. + * -ENOMEM, if message can not fit in provided buf. + * -EAGAIN, if not whole message is ready yet. + */ +int pbuf_read(struct pbuf *pb, char *buf, uint16_t len); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_IPC_PBUF_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..e721953414be --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/prj.conf @@ -0,0 +1,10 @@ +# Copyright 2021 Carlo Caione +# SPDX-License-Identifier: Apache-2.0 + +# We need rand_r function +CONFIG_GNU_C_EXTENSIONS=y + +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..1fa2d9c278be --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +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}) + +zephyr_sources_ifdef(CONFIG_IPC_SERVICE_ICMSG_V1 ../interoperability/icmsg_v1.c) +zephyr_sources_ifdef(CONFIG_PBUF_V1 ../interoperability/pbuf_v1.c) +zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICMSG_V1 ../interoperability/ipc_icmsg_v1.c) diff --git a/tests/subsys/ipc/ipc_sessions/remote/Kconfig b/tests/subsys/ipc/ipc_sessions/remote/Kconfig new file mode 100644 index 000000000000..169ab615a206 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/Kconfig @@ -0,0 +1,11 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +rsource "../interoperability/Kconfig" + +menu "Zephyr" +source "Kconfig.zephyr" +endmenu 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..95dffe367e4d --- /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: Apache-2.0 + */ + +/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..5728b0a1f32a --- /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: Apache-2.0 + */ + +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/boards/nrf54h20dk_nrf54h20_cpurad.overlay b/tests/subsys/ipc/ipc_sessions/remote/boards/nrf54h20dk_nrf54h20_cpurad.overlay new file mode 100644 index 000000000000..00e376b5df86 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/boards/nrf54h20dk_nrf54h20_cpurad.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +&uart135 { + /delete-property/ hw-flow-control; +}; + +&ipc0 { + compatible = "zephyr,ipc-icmsg"; + /delete-property/ tx-blocks; + /delete-property/ rx-blocks; + unbound = "enable"; +}; 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..78730ee7dd59 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/prj.conf @@ -0,0 +1,23 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# We need rand_r function +CONFIG_GNU_C_EXTENSIONS=y +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_WATCHDOG=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..dd2b845587ae --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/remote/src/remote.c @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 +#define IPC_TEST_EV_TXTEST 0x04 + +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); + +struct ipc_xfer_params { + uint32_t blk_size; + uint32_t blk_cnt; + unsigned int seed; + int result; +}; + +static struct ipc_xfer_params ipc_rx_params; +static struct ipc_xfer_params ipc_tx_params; + +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; + } + + 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; + } + case IPC_TEST_CMD_RXSTART: { + LOG_INF("Command processing: RXSTART"); + + struct ipc_test_cmd_xstart *cmd_rxstart = (struct ipc_test_cmd_xstart *)cmd; + + ipc_rx_params.blk_size = cmd_rxstart->blk_size; + ipc_rx_params.blk_cnt = cmd_rxstart->blk_cnt; + ipc_rx_params.seed = cmd_rxstart->seed; + ipc_rx_params.result = 0; + break; + } + case IPC_TEST_CMD_TXSTART: { + LOG_INF("Command processing: TXSTART"); + + struct ipc_test_cmd_xstart *cmd_txstart = (struct ipc_test_cmd_xstart *)cmd; + + ipc_tx_params.blk_size = cmd_txstart->blk_size; + ipc_tx_params.blk_cnt = cmd_txstart->blk_cnt; + ipc_tx_params.seed = cmd_txstart->seed; + ipc_tx_params.result = 0; + k_event_set(&ipc_ev_req, IPC_TEST_EV_TXTEST); + break; + } + case IPC_TEST_CMD_RXGET: { + LOG_INF("Command processing: RXGET"); + + int ret; + struct ipc_test_cmd_xstat cmd_stat = { + .base.cmd = IPC_TEST_CMD_XSTAT, + .blk_cnt = ipc_rx_params.blk_cnt, + .result = ipc_rx_params.result + }; + + ret = ipc_service_send(ep, &cmd_stat, sizeof(cmd_stat)); + if (ret < 0) { + LOG_ERR("RXGET response send failed"); + } + break; + } + case IPC_TEST_CMD_TXGET: { + LOG_INF("Command processing: TXGET"); + + int ret; + struct ipc_test_cmd_xstat cmd_stat = { + .base.cmd = IPC_TEST_CMD_XSTAT, + .blk_cnt = ipc_tx_params.blk_cnt, + .result = ipc_tx_params.result + }; + + ret = ipc_service_send(ep, &cmd_stat, sizeof(cmd_stat)); + if (ret < 0) { + LOG_ERR("TXGET response send failed"); + } + break; + } + case IPC_TEST_CMD_XDATA: { + if ((ipc_rx_params.blk_cnt % 1000) == 0) { + /* Logging only every N-th command not to slowdown the transfer too much */ + LOG_INF("Command processing: XDATA (left: %u)", ipc_rx_params.blk_cnt); + } + + /* Ignore if there is an error */ + if (ipc_rx_params.result) { + LOG_ERR("There is error in Rx transfer already"); + break; + } + + if (len != ipc_rx_params.blk_size + offsetof(struct ipc_test_cmd, data)) { + LOG_ERR("Size mismatch"); + ipc_rx_params.result = -EMSGSIZE; + break; + } + + if (ipc_rx_params.blk_cnt <= 0) { + LOG_ERR("Data not expected"); + ipc_rx_params.result = -EFAULT; + break; + } + + /* Check the data */ + for (size_t n = 0; n < ipc_rx_params.blk_size; ++n) { + uint8_t expected = (uint8_t)rand_r(&ipc_rx_params.seed); + + if (cmd->data[n] != expected) { + LOG_ERR("Data value error at %u", n); + ipc_rx_params.result = -EINVAL; + break; + } + } + + ipc_rx_params.blk_cnt -= 1; + 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"); + } + if (ev & IPC_TEST_EV_TXTEST) { + LOG_INF("Transfer TX test started"); + + size_t cmd_size = ipc_tx_params.blk_size + offsetof(struct ipc_test_cmd, + data); + struct ipc_test_cmd *cmd_data = k_malloc(cmd_size); + + if (!cmd_data) { + LOG_ERR("Cannot create TX test buffer"); + ipc_tx_params.result = -ENOMEM; + continue; + } + + LOG_INF("Initial seed: %u", ipc_tx_params.seed); + + cmd_data->cmd = IPC_TEST_CMD_XDATA; + for (/* No init */; ipc_tx_params.blk_cnt > 0; --ipc_tx_params.blk_cnt) { + int ret; + + if (ipc_tx_params.blk_cnt % 1000 == 0) { + LOG_INF("Sending: %u blocks left", ipc_tx_params.blk_cnt); + } + /* Generate the block data */ + for (size_t n = 0; n < ipc_tx_params.blk_size; ++n) { + cmd_data->data[n] = (uint8_t)rand_r(&ipc_tx_params.seed); + } + do { + ret = ipc_service_send(ep_cfg.priv, cmd_data, cmd_size); + } while (ret == -ENOMEM); + if (ret < 0) { + LOG_ERR("Cannot send TX test buffer: %d", ret); + ipc_tx_params.result = -EIO; + continue; + } + } + + k_free(cmd_data); + + LOG_INF("Transfer TX test finished"); + } + + + } + + 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..1215530e09f8 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/src/main.c @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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; + +struct test_cmd_xdata { + struct ipc_test_cmd base; + uint8_t data[CONFIG_IPC_TEST_BLOCK_SIZE]; +}; + +static void (*ep_received_override_cb)(const void *data, size_t len, void *priv); + +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; + + if (ep_received_override_cb) { + ep_received_override_cb(data, len, priv); + } else { + 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) +{ + ep_received_override_cb = NULL; + 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) +{ + Z_TEST_SKIP_IFDEF(CONFIG_IPC_TEST_SKIP_UNBOUND); + Z_TEST_SKIP_IFDEF(CONFIG_IPC_TEST_SKIP_CORE_RESET); + + 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) +{ + Z_TEST_SKIP_IFDEF(CONFIG_IPC_TEST_SKIP_UNBOUND); + + 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) +{ + Z_TEST_SKIP_IFDEF(CONFIG_IPC_TEST_SKIP_UNBOUND); + + 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(ipc_sessions, test_tx_long) +{ + #define SEED_TXSTART_VALUE 1 + int ret; + static const struct ipc_test_cmd_xstart cmd_rxstart = { + .base = { .cmd = IPC_TEST_CMD_RXSTART }, + .blk_size = CONFIG_IPC_TEST_BLOCK_SIZE, + .blk_cnt = CONFIG_IPC_TEST_BLOCK_CNT, + .seed = SEED_TXSTART_VALUE }; + static const struct ipc_test_cmd cmd_rxget = { IPC_TEST_CMD_RXGET }; + struct test_cmd_xdata cmd_txdata = { .base = { .cmd = IPC_TEST_CMD_XDATA } }; + unsigned int seed = SEED_TXSTART_VALUE; + + struct ipc_test_cmd_xstat *cmd_rxstat; + size_t cmd_rsp_size; + + zassert_not_ok(data_queue_is_empty(&ipc_data_queue), + "IPC data queue contains unexpected data"); + + /* Sending command for the remote to start receiving the data */ + ret = ipc_service_send(&ep, &cmd_rxstart, sizeof(cmd_rxstart)); + zassert_equal(ret, sizeof(cmd_rxstart), "ipc_service_send failed: %d, expected: %u", ret, + sizeof(cmd_rxstart)); + /* Check current status */ + ret = ipc_service_send(&ep, &cmd_rxget, sizeof(cmd_rxget)); + zassert_equal(ret, sizeof(cmd_rxget), "ipc_service_send failed: %d, expected: %u", ret, + sizeof(cmd_rxget)); + cmd_rxstat = data_queue_get(&ipc_data_queue, &cmd_rsp_size, K_MSEC(1000)); + zassert_not_null(cmd_rxstat, "No command response on time"); + zassert_equal(cmd_rsp_size, sizeof(*cmd_rxstat), + "Unexpected response size: %u, expected: %u", cmd_rsp_size, + sizeof(cmd_rxstat)); + zassert_equal(cmd_rxstat->base.cmd, IPC_TEST_CMD_XSTAT, + "Unexpected command in response: %u", cmd_rxstat->base.cmd); + zassert_ok(cmd_rxstat->result, "RX result not ok: %d", cmd_rxstat->result); + zassert_equal(cmd_rxstat->blk_cnt, cmd_rxstart.blk_cnt, + "RX blk_cnt in status does not match start command: %u vs %u", + cmd_rxstat->blk_cnt, cmd_rxstart.blk_cnt); + data_queue_release(&ipc_data_queue, cmd_rxstat); + + /* Sending data */ + for (size_t blk = 0; blk < cmd_rxstart.blk_cnt; ++blk) { + for (size_t n = 0; n < cmd_rxstart.blk_size; ++n) { + cmd_txdata.data[n] = (uint8_t)rand_r(&seed); + } + do { + ret = ipc_service_send(&ep, &cmd_txdata, sizeof(cmd_txdata)); + } while (ret == -ENOMEM); + if ((blk % 1000) == 0) { + LOG_INF("Transfer number: %u of %u", blk, cmd_rxstart.blk_cnt); + } + zassert_equal(ret, sizeof(cmd_txdata), "ipc_service_send failed: %d, expected: %u", + ret, sizeof(cmd_txdata)); + } + + /* Check current status */ + ret = ipc_service_send(&ep, &cmd_rxget, sizeof(cmd_rxget)); + zassert_equal(ret, sizeof(cmd_rxget), "ipc_service_send failed: %d, expected: %u", ret, + sizeof(cmd_rxget)); + cmd_rxstat = data_queue_get(&ipc_data_queue, &cmd_rsp_size, K_MSEC(1000)); + zassert_not_null(cmd_rxstat, "No command response on time"); + zassert_equal(cmd_rsp_size, sizeof(*cmd_rxstat), + "Unexpected response size: %u, expected: %u", cmd_rsp_size, + sizeof(cmd_rxstat)); + zassert_equal(cmd_rxstat->base.cmd, IPC_TEST_CMD_XSTAT, + "Unexpected command in response: %u", cmd_rxstat->base.cmd); + zassert_ok(cmd_rxstat->result, "RX result not ok: %d", cmd_rxstat->result); + zassert_equal(cmd_rxstat->blk_cnt, 0, + "RX blk_cnt in status does not match start command: %u vs %u", + cmd_rxstat->blk_cnt, 0); + data_queue_release(&ipc_data_queue, cmd_rxstat); +} + +static struct { + unsigned int seed; + size_t blk_left; +} test_rx_long_data; +K_SEM_DEFINE(test_rx_long_sem, 0, 1); + +static void test_rx_long_rec_cb(const void *data, size_t len, void *priv) +{ + const struct test_cmd_xdata *cmd_rxdata = data; + + zassert_true(test_rx_long_data.blk_left > 0, "No data left to interpret"); + zassert_equal(len, sizeof(*cmd_rxdata), + "Unexpected response size: %u, expected: %u", len, sizeof(*cmd_rxdata)); + zassert_equal(cmd_rxdata->base.cmd, IPC_TEST_CMD_XDATA, + "Unexpected command in response: %u", cmd_rxdata->base.cmd); + for (size_t n = 0; n < CONFIG_IPC_TEST_BLOCK_SIZE; ++n) { + uint8_t expected = (uint8_t)rand_r(&test_rx_long_data.seed); + + zassert_equal(cmd_rxdata->data[n], expected, + "Data mismatch at %u while %u blocks left", n, + test_rx_long_data.blk_left); + } + + if (test_rx_long_data.blk_left % 1000 == 0) { + LOG_INF("Receivng left: %u", test_rx_long_data.blk_left); + } + test_rx_long_data.blk_left -= 1; + if (test_rx_long_data.blk_left <= 0) { + LOG_INF("Interpretation marked finished"); + ep_received_override_cb = NULL; + k_sem_give(&test_rx_long_sem); + } +} + +ZTEST(ipc_sessions, test_rx_long) +{ + #define SEED_RXSTART_VALUE 1 + int ret; + static const struct ipc_test_cmd_xstart cmd_txstart = { + .base = { .cmd = IPC_TEST_CMD_TXSTART }, + .blk_size = CONFIG_IPC_TEST_BLOCK_SIZE, + .blk_cnt = CONFIG_IPC_TEST_BLOCK_CNT, + .seed = SEED_RXSTART_VALUE }; + static const struct ipc_test_cmd cmd_txget = { IPC_TEST_CMD_TXGET }; + struct ipc_test_cmd_xstat *cmd_txstat; + size_t cmd_rsp_size; + + zassert_not_ok(data_queue_is_empty(&ipc_data_queue), + "IPC data queue contains unexpected data"); + + /* Configuring the callback to interpret the incoming data */ + test_rx_long_data.seed = SEED_RXSTART_VALUE; + test_rx_long_data.blk_left = cmd_txstart.blk_cnt; + ep_received_override_cb = test_rx_long_rec_cb; + + /* Sending command for the remote to start sending the data */ + ret = ipc_service_send(&ep, &cmd_txstart, sizeof(cmd_txstart)); + zassert_equal(ret, sizeof(cmd_txstart), "ipc_service_send failed: %d, expected: %u", ret, + sizeof(cmd_txstart)); + + /* Waiting for all the data */ + ret = k_sem_take(&test_rx_long_sem, K_SECONDS(30)); + LOG_INF("Interpretation finished"); + zassert_ok(ret, "Incoming packet interpretation timeout"); + zassert_is_null(ep_received_override_cb, "Seems like interpretation callback failed"); + + /* Check current status */ + ret = ipc_service_send(&ep, &cmd_txget, sizeof(cmd_txget)); + zassert_equal(ret, sizeof(cmd_txget), "ipc_service_send failed: %d, expected: %u", ret, + sizeof(cmd_txget)); + cmd_txstat = data_queue_get(&ipc_data_queue, &cmd_rsp_size, K_MSEC(1000)); + zassert_not_null(cmd_txstat, "No command response on time"); + zassert_equal(cmd_rsp_size, sizeof(*cmd_txstat), + "Unexpected response size: %u, expected: %u", cmd_rsp_size, + sizeof(cmd_txstat)); + zassert_equal(cmd_txstat->base.cmd, IPC_TEST_CMD_XSTAT, + "Unexpected command in response: %u", cmd_txstat->base.cmd); + zassert_ok(cmd_txstat->result, "RX result not ok: %d", cmd_txstat->result); + zassert_equal(cmd_txstat->blk_cnt, 0, + "RX blk_cnt in status does not match start command: %u vs %u", + cmd_txstat->blk_cnt, 0); + data_queue_release(&ipc_data_queue, cmd_txstat); +} + + +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..9aa0b80096b1 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/sysbuild.cmake @@ -0,0 +1,26 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +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/sysbuild_cpuppr.conf b/tests/subsys/ipc/ipc_sessions/sysbuild_cpuppr.conf new file mode 100644 index 000000000000..3b3ca1ea91f3 --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/sysbuild_cpuppr.conf @@ -0,0 +1,4 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +SB_CONFIG_REMOTE_BOARD="nrf54h20dk/nrf54h20/cpuppr" diff --git a/tests/subsys/ipc/ipc_sessions/testcase.yaml b/tests/subsys/ipc/ipc_sessions/testcase.yaml new file mode 100644 index 000000000000..f76a61b8d68e --- /dev/null +++ b/tests/subsys/ipc/ipc_sessions/testcase.yaml @@ -0,0 +1,50 @@ +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_cpurad: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + - CONFIG_IPC_TEST_SKIP_CORE_RESET=y + 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 + sample.ipc.ipc_sessions.nrf54h20dk_cpuapp_no_unbound_cpuppr: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + - FILE_SUFFIX=cpuppr + - ipc_sessions_SNIPPET=nordic-ppr + - CONFIG_IPC_TEST_SKIP_UNBOUND=y + - CONFIG_IPC_SERVICE_BACKEND_ICMSG_V1=y + sample.ipc.ipc_sessions.nrf54h20dk_cpuapp_cpuppr_no_unbound: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + - FILE_SUFFIX=cpuppr + - ipc_sessions_SNIPPET=nordic-ppr + - CONFIG_IPC_TEST_SKIP_UNBOUND=y + - remote_CONFIG_IPC_SERVICE_BACKEND_ICMSG_V1=y diff --git a/tests/subsys/ipc/pbuf/src/main.c b/tests/subsys/ipc/pbuf/src/main.c index 4af9da68c473..4d563204cb08 100644 --- a/tests/subsys/ipc/pbuf/src/main.c +++ b/tests/subsys/ipc/pbuf/src/main.c @@ -48,7 +48,7 @@ ZTEST(test_pbuf, test_rw) * order to avoid clang complains about memory_area not being constant * expression. */ - static PBUF_MAYBE_CONST struct pbuf_cfg cfg = PBUF_CFG_INIT(memory_area, MEM_AREA_SZ, 0); + static PBUF_MAYBE_CONST struct pbuf_cfg cfg = PBUF_CFG_INIT(memory_area, MEM_AREA_SZ, 0, 0); static struct pbuf pb = { .cfg = &cfg, @@ -115,9 +115,11 @@ ZTEST(test_pbuf, test_retcodes) * order to avoid clang complains about memory_area not being constant * expression. */ - static PBUF_MAYBE_CONST struct pbuf_cfg cfg0 = PBUF_CFG_INIT(memory_area, MEM_AREA_SZ, 32); - static PBUF_MAYBE_CONST struct pbuf_cfg cfg1 = PBUF_CFG_INIT(memory_area, MEM_AREA_SZ, 0); - static PBUF_MAYBE_CONST struct pbuf_cfg cfg2 = PBUF_CFG_INIT(memory_area, 20, 4); + static PBUF_MAYBE_CONST struct pbuf_cfg cfg0 = PBUF_CFG_INIT(memory_area, MEM_AREA_SZ, + 32, 0); + static PBUF_MAYBE_CONST struct pbuf_cfg cfg1 = PBUF_CFG_INIT(memory_area, MEM_AREA_SZ, + 0, 0); + static PBUF_MAYBE_CONST struct pbuf_cfg cfg2 = PBUF_CFG_INIT(memory_area, 20, 4, 0); static struct pbuf pb0 = { .cfg = &cfg0, @@ -268,7 +270,7 @@ ZTEST(test_pbuf, test_stress) * order to avoid clang complains about buffer not being constant * expression. */ - static PBUF_MAYBE_CONST struct pbuf_cfg cfg = PBUF_CFG_INIT(buffer, MEM_AREA_SZ, 4); + static PBUF_MAYBE_CONST struct pbuf_cfg cfg = PBUF_CFG_INIT(buffer, MEM_AREA_SZ, 4, 0); static struct pbuf pb = { .cfg = &cfg,