diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fed0d9346..e83c247579 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 - 2020 Nordic Semiconductor +# Copyright (c) 2019 - 2021 Nordic Semiconductor # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # @@ -19,3 +19,4 @@ add_subdirectory_ifdef(CONFIG_BT softdevice_controller) add_subdirectory_ifdef(CONFIG_NET_L2_OPENTHREAD openthread) add_subdirectory_ifdef(CONFIG_NRF_RPC nrf_rpc) add_subdirectory_ifdef(CONFIG_ZIGBEE zboss) +add_subdirectory_ifdef(CONFIG_SM_IPT sm_ipt) diff --git a/CODEOWNERS b/CODEOWNERS index 6528ba612f..4a87ef15de 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -34,3 +34,4 @@ doc/* @b-gent /zboss/ @tomchy /zephyr/ @carlescufi /nrf_rpc/ @doki-nordic @KAGA164 +/sm_ipt/ @doki-nordic diff --git a/Kconfig.nrfxlib b/Kconfig.nrfxlib index 753e125614..7c70e71737 100644 --- a/Kconfig.nrfxlib +++ b/Kconfig.nrfxlib @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 - 2020 Nordic Semiconductor +# Copyright (c) 2019 - 2021 Nordic Semiconductor # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # @@ -13,6 +13,7 @@ rsource "crypto/Kconfig" rsource "nrf_security/Kconfig" rsource "softdevice_controller/Kconfig" rsource "nrf_rpc/Kconfig" +rsource "sm_ipt/Kconfig" rsource "zboss/Kconfig" rsource "openthread/Kconfig" rsource "nrf_802154/zephyr/Kconfig.nrfxlib" diff --git a/README.rst b/README.rst index 51422c38d4..6110876caf 100644 --- a/README.rst +++ b/README.rst @@ -23,3 +23,4 @@ Refer to their respective documentation for more information. nrf_rpc/README softdevice_controller/README zboss/README + sm_ipt/README diff --git a/sm_ipt/CMakeLists.txt b/sm_ipt/CMakeLists.txt new file mode 100644 index 0000000000..46e019d625 --- /dev/null +++ b/sm_ipt/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_include_directories(include) + +zephyr_library() + +zephyr_library_sources(sm_ipt.c) diff --git a/sm_ipt/Kconfig b/sm_ipt/Kconfig new file mode 100644 index 0000000000..ad98fbcc24 --- /dev/null +++ b/sm_ipt/Kconfig @@ -0,0 +1,42 @@ +# +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig SM_IPT + bool "Shared memory Inter-Processor Transport" + help + Enable Shared memory Inter-Processor Transport library + +if SM_IPT + +choice + prompt "Shared memory blocks" + default SM_IPT_NUM_BLOCKS_32 + +config SM_IPT_NUM_BLOCKS_32 + bool "32" + help + Selects 32 shared memory blocks per single input or output channel. + +config SM_IPT_NUM_BLOCKS_64 + bool "64" + help + Selects 64 shared memory blocks per single input or output channel. + This will increase allocatable memory granularity, but it will have + some impact on performance and code size. + +endchoice + +config SM_IPT_PRIMARY + bool "Primary role" + help + One side must have primary role (this option selected) and the other + side must have secondary role. Shared memory layout depends on this configuration. + +module = SM_IPT +module-str = SM IPT +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif # SM_IPT diff --git a/sm_ipt/README.rst b/sm_ipt/README.rst new file mode 100644 index 0000000000..55c041a5c8 --- /dev/null +++ b/sm_ipt/README.rst @@ -0,0 +1,16 @@ +.. _sm_ipt: + +Shared Memory Inter-Processor Transport library (SM IPT) +######################################################## + +SM IPT is a inter-processor transport library for |NCS| enabling inter-processor communication on Nordic Semiconductor SoCs. +The library is RTOS-agnostic and provides porting templates. + +It is designed to be used with as a transport layer, or standalone. + +.. toctree:: + :maxdepth: 2 + :caption: Subpages: + + doc/usage + doc/api diff --git a/sm_ipt/doc/api.rst b/sm_ipt/doc/api.rst new file mode 100644 index 0000000000..20073496d4 --- /dev/null +++ b/sm_ipt/doc/api.rst @@ -0,0 +1,17 @@ +.. _sm_ipt_api: + +API documentation +################# + +.. contents:: + :local: + :depth: 2 + +.. _sm_ipt_api_documentation: + +SM IPT +------ + +.. doxygengroup:: sm_ipt + :project: nrfxlib + :members: diff --git a/sm_ipt/doc/img/architecture.drawio.svg b/sm_ipt/doc/img/architecture.drawio.svg new file mode 100644 index 0000000000..a2e3f67788 --- /dev/null +++ b/sm_ipt/doc/img/architecture.drawio.svg @@ -0,0 +1,3 @@ + + + User APIUser APIUser APIUser APIUser APIUser APIUser APIUser APISM IPTsm_ipt.hSM IPT...OS abstractionsm_ipt_os_tmpl.hOS abstraction...Logger abstractionsm_ipt_log_tmpl.hLogger abstraction...Shared MemoryShared MemoryOSOSUser APIUser APISM IPT OS-independent layerCreating packetsManaging memorySM IPT OS-independent layer...SM IPT OS-dependent layerCooperating with OSManaging callbacksLogging supportAcquiring shared memorySM IPT OS-dependent layer...OS and HW layerOS and HW layerViewer does not support full SVG 1.1 \ No newline at end of file diff --git a/sm_ipt/doc/usage.rst b/sm_ipt/doc/usage.rst new file mode 100644 index 0000000000..1e4af1db38 --- /dev/null +++ b/sm_ipt/doc/usage.rst @@ -0,0 +1,213 @@ +.. _sm_ipt_usage: + +Usage and implementation +######################## + +.. contents:: + :local: + :depth: 2 + +The following picture gives an overview of the SM IPT architecture: + +.. figure:: img/architecture.drawio.svg + :alt: SM IPT architecture layers + :align: center + + SM IPT architecture layers + +Usage +===== + +.. note:: + Following examples are purely conceptual and simplified to demonstrate the library functions. + They should be rewritten to suit particular need and use OS modules like FIFO. + +Initialization +-------------- + +The initialization function initializes the context data and retrieves the shared memory parameters from the :c:func:`sm_ipt_os_init` function. + +.. code-block:: c + + struct sm_ipt_ctx sm_ipt_ctx; + + void sm_ipt_receive_handler(const uint8_t *packet, size_t len) + { + /* Process data */ + some_data_processing_function(); + } + + int initialize(void) + { + return sm_ipt_init(&sm_ipt_ctx, sm_ipt_receive_handler); + } + +The above code uses the :c:func:`sm_ipt_init` function to initialize the :c:struct:`sm_ipt_ctx` SM IPT context data structure and to assign the receive handler. + +Data send full-copy +------------------- + +The data is prepared beforehand, copied into the allocated buffer and sent to another core. + +.. code-block:: c + + int send(const void *data, size_t len) + { + uint8_t *buffer; + + sm_ipt_alloc_tx_buf(&sm_ipt_ctx, &buffer, len); + + memcpy(buffer, data, len); + + return sm_ipt_send(&sm_ipt_ctx, buffer, len); + } + +The above code uses the :c:func:`sm_ipt_alloc_tx_buf` function to allocate the transmit buffer in the shared memory. +The data is copied into the said buffer and sent to another core through the :c:func:`sm_ipt_send` function. + +Data send no-copy +----------------- + +It is possible to operate directly on the transmit buffer to avoid copying from the local buffer. + +.. code-block:: c + + int send_nocopy(size_t len) + { + uint8_t *buffer; + size_t i; + + sm_ipt_alloc_tx_buf(&sm_ipt_ctx, &buffer, len); + + /* Use buffer as any other allocated buffer */ + some_data_preparation_function(buffer); + + return sm_ipt_send(&sm_ipt_ctx, buffer, len); + } + +The above code uses the :c:func:`sm_ipt_alloc_tx_buf` function to allocate the transmit buffer in the shared memory. +The buffer is prepared by the :c:func:`some_data_preparation_function` and sent to another core through the :c:func:`sm_ipt_send` function. + +Data send abort +--------------- + +It is possible to free the transmit buffer instead of sending it. + +.. code-block:: c + + int send_if(size_t len) + { + uint8_t *buffer; + bool preparation_success; + + sm_ipt_alloc_tx_buf(&sm_ipt_ctx, &buffer, len); + + /* Prepare some data */ + preparation_success = some_data_preparation_function(buffer); + + if (preparation_success) { + return sm_ipt_send(&sm_ipt_ctx, buffer, len); + } else { + sm_ipt_free_tx_buf(&sm_ipt_ctx, buffer); + + return ERROR_ABORT; + } + } + +The above code uses the :c:func:`sm_ipt_alloc_tx_buf` function to allocate the transmit buffer in the shared memory. +The buffer is sent only if ``preparation_success`` is true. +If ``preparation_success`` is false, the buffer is freed using the :c:func:`sm_ipt_free_tx_buf` function and the send operation is aborted. + +Data receive full-copy +---------------------- + +When data is received, it can be processed using the full-copy mechanism. +The buffer must be copied to the local memory. +The shared memory rx buffer must be freed and data processing takes place on the local copy of the buffer. + +.. code-block:: c + + void sm_ipt_receive_handler(const uint8_t *packet, size_t len) + { + /* Write content of the buffer to some pipe */ + write_bytes_to_pipe(packet, len); + + /* Free receive buffer */ + sm_ipt_free_rx_buf(&sm_ipt_ctx, packet); + + /* Signal message received */ + data_received = true; + } + +The above code uses the :c:func:`write_bytes_to_pipe` function to copy the shared memory buffer into the local pipe. +The shared memory buffer is freed using the :c:func:`sm_ipt_free_rx_buf` function. +The data is processed locally using a copy of the shared memory buffer. + +Data receive no-copy +-------------------- + +When the data is received, it can be processed using the no-copy mechanism. +The pointer to the shared memory buffer must be saved for later use. +The shared memory rx buffer must be freed and data processing takes place on the shared memory buffer. + +.. note:: + This approach increases the risk of out-of-memory errors if the buffer is not freed right after receiving data. + +.. code-block:: c + + void sm_ipt_receive_handler(const uint8_t *packet, size_t len) + { + /* Put the buffer pointer to some FIFO */ + put_buffer_ptr_to_fifo(packet, len); + } + + void data_processing_thread(void) + { + uint8_t *packet; + size_t len; + + /* Wait for incoming data */ + get_buffer_ptr_from_fifo(&packet, &len); + + /*Process data */ + some_data_processing_function(packet, len); + + /* Free receive buffer */ + sm_ipt_free_rx_buf(&sm_ipt_ctx, packet); + } + +The above code sets the pointer to the shared memory buffer to the local fifo using the :c:func:`put_buffer_ptr_to_fifo` function. +Next data is processed using the original buffer in the shared memory. +The shared memory buffer is freed using the :c:func:`sm_ipt_free_rx_buf` function. + + +Lower layers +============ + +The lower layers of SM IPT are OS-dependent. +They are responsible for memory management, synchronization and logging support. + +.. note:: + Details about the implementation of the lower layers are not required when using the SM IPT API. + However, this knowledge is needed to port to a different operating system. + +Operating system abstraction +---------------------------- + +The operating system abstraction provides the SM IPT functionalities that depend on the operating system. +It manages the shared memory, thread synchronization, and atomic support. + +The template header describing the OS abstraction is :file:`template/sm_ipt_os_tmpl.h`. + + +Logging +------- + +SM IPT logs some of its activities. +This allows for tracking, diagnosis, and debugging. +It provides four levels for logging: errors, warnings, information, and debug. + +Error logs indicate serious errors, so they should be enabled if possible. +Debug logs should be enabled only for debugging purpose. + +The template header describing the logger is :file:`template/sm_ipt_log_tmpl.h`. diff --git a/sm_ipt/include/sm_ipt.h b/sm_ipt/include/sm_ipt.h new file mode 100644 index 0000000000..20f1a5a78f --- /dev/null +++ b/sm_ipt/include/sm_ipt.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic + */ + +#ifndef SM_IPT_H_ +#define SM_IPT_H_ + +#include +#include +#include +#include "sm_ipt_os.h" + +/** + * @defgroup sm_ipt SM IPT (Shared Memory Inter-Processor Transport) module. + * @{ + * @brief Module to facilitate communication using shared memory. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Callback to data receiving function. + * + * This callback will be called when packet is received. + * + * @param packet Received data. + * @param len Length of received data. + */ +typedef void (*sm_ipt_receive_handler_t)(const uint8_t *packet, size_t len); + +/** @brief Single shared memory queue. + * + * Used by @ref sm_ipt_ctx to store queue information. + * + * Fields of this structre are used internally and should not + * be used by the user. + */ +struct sm_ipt_queue { + /** Maximum allocable shared memory size. */ + size_t allocable_size; + + /** Size of a single allocable block. */ + size_t block_size; + + /** Queue placed in shared memory. */ + struct sm_ipt_shm_queue *shm_queue; +}; + +/** @brief SM IPT context. + * + * Used to store current context data. + * + * Fields of this structre are used internally and should not + * be used by the user. + */ +struct sm_ipt_ctx { + /** Input queue. */ + struct sm_ipt_queue in; + + /** Output queue. */ + struct sm_ipt_queue out; + + /** Output semaphore for allocation. */ + sm_ipt_os_sem_t out_sem; + + /** Output mutex for sending. */ + sm_ipt_os_mutex_t out_mutex; + + /** Allocation bitmap. */ + sm_ipt_os_atomic_t free_mask[IS_ENABLED(CONFIG_SM_IPT_NUM_BLOCKS_64) ? 2 : 1]; + + /** Receive handler. */ + sm_ipt_receive_handler_t receive_handler; + + /** OS Context - to be implemented by OS layer. */ + struct sm_ipt_os_ctx os_ctx; +}; + +/** @brief Initialize the SM IPT + * + * This function initializes shared memory and context data. + * + * @param ctx SM IPT Context. + * @param callback Callback to receive handler. + * + * @return 0 on success or negative error code. + */ +int sm_ipt_init(struct sm_ipt_ctx *ctx, sm_ipt_receive_handler_t callback); + +/** @brief Free SM IPT rx buffer + * + * This function frees shared memory buffer for receiving data. + * + * @param ctx SM IPT Context. + * @param packet Selected buffer. + */ +void sm_ipt_free_rx_buf(struct sm_ipt_ctx *ctx, const uint8_t *buf); + +/** @brief Allocate SM IPT tx buffer + * + * This function allocates shared memory buffer for trasmitting data. + * + * @param ctx SM IPT Context. + * @param packet Pointer to selected buffer. + * @param len Length of allocation. + */ +void sm_ipt_alloc_tx_buf(struct sm_ipt_ctx *ctx, uint8_t **buf, size_t len); + +/** @brief Free SM IPT tx buffer + * + * This function frees shared memory buffer for trasmitting data. + * + * @param ctx SM IPT Context. + * @param packet Pointer to selected buffer. + */ +void sm_ipt_free_tx_buf(struct sm_ipt_ctx *ctx, uint8_t *buf); + +/** @brief Send message through SM IPT + * + * This function sends data stored at buf in shared memory. + * + * @param ctx SM IPT Context. + * @param packet Selected buffer. + * @param len Length of message. + * + * @return 0 on success or negative error code. + */ +int sm_ipt_send(struct sm_ipt_ctx *ctx, uint8_t *buf, size_t len); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* SM_IPT_H_ */ diff --git a/sm_ipt/include/sm_ipt_errno.h b/sm_ipt/include/sm_ipt_errno.h new file mode 100644 index 0000000000..b0ace07aad --- /dev/null +++ b/sm_ipt/include/sm_ipt_errno.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef SM_IPT_ERRNO_H__ +#define SM_IPT_ERRNO_H__ + +/** + * @defgroup nrf_rpc_errno Error codes for SM IPT. + * @{ + * @ingroup sm_ipt + * + * @brief Defies error codes that can be used in SM IPT. + */ + +#define NRF_EPERM 1 /**< Operation not permitted */ +#define NRF_ENOENT 2 /**< No such file or directory */ +#define NRF_EIO 5 /**< Input/output error */ +#define NRF_ENOEXEC 8 /**< Exec format error */ +#define NRF_EBADF 9 /**< Bad file descriptor */ +#define NRF_ENOMEM 12 /**< Cannot allocate memory */ +#define NRF_EACCES 13 /**< Permission denied */ +#define NRF_EFAULT 14 /**< Bad address */ +#define NRF_ENODEV 19 /**< No such device */ +#define NRF_EINVAL 22 /**< Invalid argument */ +#define NRF_EMFILE 24 /**< Too many open files */ +#define NRF_ENOSPC 28 /**< No space left on device */ +#define NRF_EAGAIN 35 /**< Resource temporarily unavailable*/ +#define NRF_EDOM 37 /**< Domain error */ +#define NRF_EMSGSIZE 40 /**< Message too long */ +#define NRF_EPROTOTYPE 41 /**< Protocol wrong type for socket */ +#define NRF_ENOPROTOOPT 42 /**< Protocol not available */ +#define NRF_EPROTONOSUPPORT 43 /**< Protocol not supported */ +#define NRF_ESOCKTNOSUPPORT 44 /**< Socket type not supported */ +#define NRF_EOPNOTSUPP 45 /**< Operation not supported */ +#define NRF_EAFNOSUPPORT 47 /**< Address family not supported by protocol */ +#define NRF_EADDRINUSE 48 /**< Address already in use */ +#define NRF_ENETDOWN 50 /**< Network is down */ +#define NRF_ENETUNREACH 51 /**< Network is unreachable */ +#define NRF_ENETRESET 52 /**< Connection aborted by network */ +#define NRF_ECONNRESET 54 /**< Connection reset by peer */ +#define NRF_EISCONN 56 /**< Transport endpoint is already connected */ +#define NRF_ENOTCONN 57 /**< Transport endpoint is not connected */ +#define NRF_ETIMEDOUT 60 /**< Connection timed out */ +#define NRF_EBADMSG 77 /**< Bad message */ +#define NRF_ENOBUFS 105 /**< No buffer space available */ + +#define NRF_EHOSTDOWN 112 /**< Host is down */ +#define NRF_EALREADY 114 /**< Operation already in progress */ +#define NRF_EINPROGRESS 115 /**< Operation in progress */ +#define NRF_ECANCELED 125 /**< Operation canceled */ + +#define NRF_ENOKEY 126 /**< Required key not available */ +#define NRF_EKEYEXPIRED 127 /**< Key has expired */ +#define NRF_EKEYREVOKED 128 /**< Key has been revoked */ +#define NRF_EKEYREJECTED 129 /**< Key was rejected by service */ + +/** + * @} + */ + +#endif // SM_IPT_ERRNO_H__ diff --git a/sm_ipt/sm_ipt.c b/sm_ipt/sm_ipt.c new file mode 100644 index 0000000000..f6495c802d --- /dev/null +++ b/sm_ipt/sm_ipt.c @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic + */ + + +#include +#include +#include + +#include "sm_ipt.h" +#include "sm_ipt_log.h" +#include "sm_ipt_errno.h" + + +#define FLAG_RELEASE 0x80 + +#define WORD_SIZE sizeof(uint32_t) + +#if defined(CONFIG_SM_IPT_NUM_BLOCKS_32) +typedef uint32_t mask_t; +typedef int32_t smask_t; +#define NUM_BLOCKS 32 +#define mask_clz sm_ipt_os_clz32 +#elif defined(CONFIG_SM_IPT_NUM_BLOCKS_64) +typedef uint64_t mask_t; +typedef int64_t smask_t; +#define NUM_BLOCKS 64 +#define mask_clz sm_ipt_os_clz64 +#else +#error Number of shared dynamic memory blocks is not configured. +#endif + +#define QUEUE_INDEX_MASK (NUM_BLOCKS - 1) + +/* Worst case scenario: NUM_BLOCKS count of messages waiting to be sent and NUM_BLOCKS count of + * buffers waiting to be freed + 1 item that must always be free in circular buffer + */ +#define QUEUE_ITEMS_CNT (2 * NUM_BLOCKS + 1) + + +struct sm_ipt_shm_queue { + uint32_t queue_tx; + uint32_t queue_rx; + uint8_t queue[QUEUE_ITEMS_CNT]; + uint8_t handshake; + uint16_t _padding; /* Care should be taken to align allocable to word boundary */ + uint8_t allocable[]; +}; + +struct sm_ipt_message { + uint32_t size; + uint8_t buf[]; +}; + + +static void memory_corrupted_error(void) +{ + SM_IPT_ERR("Shared memory corrupted"); + sm_ipt_os_fatal(); + SM_IPT_ASSERT(0) +} + +static void free_mask_set(struct sm_ipt_ctx *ctx, mask_t mask) +{ + sm_ipt_os_atomic_or(&ctx->free_mask[0], mask); + + if (NUM_BLOCKS > 32) { + sm_ipt_os_atomic_or(&ctx->free_mask[1], (uint64_t)mask >> 32); + } + + SM_IPT_OS_MEMORY_BARRIER(); +} + +static bool free_mask_unset(struct sm_ipt_ctx *ctx, mask_t mask) +{ + uint32_t old; + uint32_t mask_part; + + mask_part = (uint32_t)mask; + old = sm_ipt_os_atomic_and(&ctx->free_mask[0], ~mask_part); + if ((old & mask_part) != mask_part) { + sm_ipt_os_atomic_or(&ctx->free_mask[0], old & mask_part); + + return false; + } + + if (NUM_BLOCKS > 32) { + mask_part = (uint32_t)((uint64_t)mask >> 32); + old = sm_ipt_os_atomic_and(&ctx->free_mask[1], ~mask_part); + + if ((old & mask_part) != mask_part) { + sm_ipt_os_atomic_or(&ctx->free_mask[1], old & mask_part); + sm_ipt_os_atomic_or(&ctx->free_mask[0], (uint32_t)mask); + return false; + } + } + + SM_IPT_OS_MEMORY_BARRIER(); + + return true; +} + +static mask_t free_mask_get(struct sm_ipt_ctx *ctx) +{ + mask_t mask; + + mask = sm_ipt_os_atomic_get(&ctx->free_mask[0]); + + if (NUM_BLOCKS > 32) { + mask = (uint64_t)sm_ipt_os_atomic_get(&ctx->free_mask[1]) << 32; + } + + return mask; +} + +static void free_mask_init(struct sm_ipt_ctx *ctx) +{ + free_mask_set(ctx, ~(mask_t)0); +} + +static mask_t calc_mask(size_t blocks, size_t index) +{ + mask_t mask; + + mask = (mask_t)1 << (NUM_BLOCKS - 1); /* 100000000... */ + mask = (smask_t)mask >> (blocks - 1); /* 111000000... (e.g. blocks = 3) */ + mask = mask >> index; /* 000011100... (e.g. index = 4) */ + + return mask; +} + +void sm_ipt_alloc_tx_buf(struct sm_ipt_ctx *ctx, uint8_t **buf, size_t len) +{ + /* Actual allocated memory: | 32-bit size | data | padding | */ + size_t blocks = ((sizeof(struct sm_ipt_message) + len) + (ctx->out.block_size - 1)) + / ctx->out.block_size; + bool sem_taken = false; + mask_t cur_mask; + mask_t sh_mask; + bool unset_success; + mask_t mask; + size_t free_index; + + SM_IPT_DBG("Trying to allocate %d bytes", len); + + if ((blocks > NUM_BLOCKS) || (blocks == 0)) { + SM_IPT_ERR("Requested %d bytes, maximum is %d", len, + ctx->out.allocable_size - sizeof(struct sm_ipt_message)); + *buf = NULL; + return; + } + + do { + do { + /* create shifted mask with bits set where `blocks` can be allocated */ + cur_mask = free_mask_get(ctx); + sh_mask = cur_mask; + for (size_t i = 1; i < blocks; i++) { + sh_mask &= (sh_mask << 1); + } + + /* if no memory */ + if (sh_mask == 0) { + /* wait for any block to be empty */ + SM_IPT_DBG("Not enough free shared memory. Waiting."); + sm_ipt_os_take(&ctx->out_sem); + sem_taken = true; + } + + } while (sh_mask == 0); + + /* get first available blocks */ + free_index = mask_clz(sh_mask); + /* create bit mask with blocks that will be used */ + mask = calc_mask(blocks, free_index); + /* update masks */ + unset_success = free_mask_unset(ctx, mask); + + /* there is a small probability that unset will be unsuccessful */ + if (!unset_success) { + /* give semaphore, because free_mask_unset may cause other thread + * waiting before it reverted the changes + */ + sm_ipt_os_give(&ctx->out_sem); + sem_taken = false; + } + } while (!unset_success); + + /* Give semaphore back, because there may be some other thread waiting */ + if (sem_taken && ((cur_mask & ~mask) != 0)) { + sm_ipt_os_give(&ctx->out_sem); + } + + struct sm_ipt_message *msg = (struct sm_ipt_message *)&ctx->out.shm_queue->allocable[ + ctx->out.block_size * free_index]; + + msg->size = blocks * ctx->out.block_size; + + *buf = msg->buf; + SM_IPT_DBG("Allocated memory at 0x%x", (uint32_t)*buf); +} + +void sm_ipt_free_tx_buf(struct sm_ipt_ctx *ctx, uint8_t *buf) +{ + struct sm_ipt_message *msg = SM_IPT_OS_GET_CONTAINTER(buf, struct sm_ipt_message, buf); + uint32_t offset = (uint8_t *)msg - (uint8_t *)ctx->out.shm_queue->allocable; + uint32_t block_index = offset / ctx->out.block_size; + uint32_t allocated_blocks; + + SM_IPT_ASSERT(block_index < NUM_BLOCKS); + SM_IPT_ASSERT(buf == &ctx->out.shm_queue->allocable[block_index * ctx->out.block_size]); + + allocated_blocks = msg->size / ctx->out.block_size; + + SM_IPT_ASSERT(allocated_blocks % ctx->out.block_size == 0); + SM_IPT_ASSERT(msg->size <= ctx->out.allocable_size); + SM_IPT_ASSERT(offset + msg->size <= ctx->out.allocable_size); + + free_mask_set(ctx, calc_mask(allocated_blocks, block_index)); + sm_ipt_os_give(&ctx->out_sem); + SM_IPT_DBG("Dealocated TX block at 0x%x", (uint32_t)msg->buf); +} + + +static void queue_send(struct sm_ipt_ctx *ctx, uint8_t data) +{ + uint32_t tx; + uint32_t dst; + + sm_ipt_os_lock(&ctx->out_mutex); + + tx = ctx->out.shm_queue->queue_tx; + dst = tx; + + if (dst >= QUEUE_ITEMS_CNT) { + sm_ipt_os_unlock(&ctx->out_mutex); + memory_corrupted_error(); + return; + } + + tx++; + + if (tx >= QUEUE_ITEMS_CNT) { + tx = 0; + } + + ctx->out.shm_queue->queue[dst] = data; + + SM_IPT_OS_MEMORY_BARRIER(); + + ctx->out.shm_queue->queue_tx = tx; + + sm_ipt_os_unlock(&ctx->out_mutex); + + sm_ipt_os_signal(&ctx->os_ctx); +} + + +int sm_ipt_send(struct sm_ipt_ctx *ctx, uint8_t *buf, size_t len) +{ + struct sm_ipt_message *msg = SM_IPT_OS_GET_CONTAINTER(buf, struct sm_ipt_message, buf); + uint32_t offset = (uint8_t *)msg - (uint8_t *)ctx->out.shm_queue->allocable; + uint32_t block_index = offset / ctx->out.block_size; + uint32_t allocated_blocks; + uint32_t blocks = (len + ctx->out.block_size - 1) / ctx->out.block_size; + uint32_t total_len = len + sizeof(struct sm_ipt_message); + + SM_IPT_DBG("Trying to send message of: %d bytes, at address: 0x%x", len, + (uint32_t)msg->buf); + + SM_IPT_ASSERT(block_index < NUM_BLOCKS); + SM_IPT_ASSERT((uint8_t *)msg == + (&ctx->out.shm_queue->allocable[block_index * ctx->out.block_size])); + + allocated_blocks = msg->size / ctx->out.block_size; + + SM_IPT_ASSERT(msg->size % ctx->out.block_size == 0); + SM_IPT_ASSERT(msg->size <= ctx->out.allocable_size); + SM_IPT_ASSERT(offset + msg->size <= ctx->out.allocable_size); + SM_IPT_ASSERT(total_len <= msg->size); + + if (blocks < allocated_blocks) { + free_mask_set(ctx, calc_mask(allocated_blocks - blocks, block_index + blocks)); + sm_ipt_os_give(&ctx->out_sem); + } + + msg->size = total_len; + + queue_send(ctx, block_index); + + return 0; +} + + +static int queue_recv(struct sm_ipt_ctx *ctx) +{ + uint32_t tx = ctx->in.shm_queue->queue_tx; + uint32_t rx = ctx->in.shm_queue->queue_rx; + uint8_t data; + + tx = ctx->in.shm_queue->queue_tx; + rx = ctx->in.shm_queue->queue_rx; + + if (rx >= QUEUE_ITEMS_CNT) { + memory_corrupted_error(); + return -NRF_EIO; + } + + if (tx == rx) { + return -NRF_EBADMSG; + } + + SM_IPT_OS_MEMORY_BARRIER(); + + data = ctx->in.shm_queue->queue[rx]; + + rx++; + + if (rx >= QUEUE_ITEMS_CNT) { + rx = 0; + } + + ctx->in.shm_queue->queue_rx = rx; + + return data; +} + + +static void signal_received(struct sm_ipt_os_ctx *os_ctx) +{ + int block; + int release; + struct sm_ipt_message *msg; + uint32_t blocks; + struct sm_ipt_ctx *ctx; + + ctx = SM_IPT_OS_GET_CONTAINTER(os_ctx, struct sm_ipt_ctx, os_ctx); + + while (true) { + block = queue_recv(ctx); + if (block < 0) { + break; + } + + release = (block & FLAG_RELEASE); + block &= ~FLAG_RELEASE; + + if (block >= NUM_BLOCKS) { + continue; + } + + if (release) { + msg = (struct sm_ipt_message *)(&ctx->out.shm_queue->allocable[ + block * ctx->out.block_size]); + blocks = (msg->size + ctx->out.block_size - 1) / ctx->out.block_size; + free_mask_set(ctx, calc_mask(blocks, block)); + sm_ipt_os_give(&ctx->out_sem); + } else { + msg = (struct sm_ipt_message *)(&ctx->in.shm_queue->allocable[ + block * ctx->in.block_size]); + + if ((msg->size >= (&ctx->in.shm_queue->allocable[ctx->in.allocable_size] - + (uint8_t *)msg)) || (msg->size < sizeof(struct sm_ipt_message))) { + memory_corrupted_error(); + break; + } + + SM_IPT_DBG("Message received at: 0x%x, length: %d", (uint32_t)msg->buf, + (msg->size - sizeof(struct sm_ipt_message))); + + ctx->receive_handler(msg->buf, msg->size - sizeof(struct sm_ipt_message)); + } + } +} + +void sm_ipt_free_rx_buf(struct sm_ipt_ctx *ctx, const uint8_t *buf) +{ + struct sm_ipt_message *msg = SM_IPT_OS_GET_CONTAINTER(buf, struct sm_ipt_message, buf); + uint32_t offset = (uint8_t *)msg - (uint8_t *)ctx->in.shm_queue->allocable; + uint32_t block_index = offset / ctx->in.block_size; + + SM_IPT_ASSERT(block_index < NUM_BLOCKS); + + queue_send(ctx, (block_index | FLAG_RELEASE)); + SM_IPT_DBG("Dealocated RX block at 0x%x", (uint32_t)msg->buf); +} + +static void handshake_step(struct sm_ipt_ctx *ctx, uint8_t this_value, uint8_t next_value) +{ + SM_IPT_DBG("Setting out handshake to: %d", this_value); + + ctx->out.shm_queue->handshake = this_value; + + SM_IPT_OS_MEMORY_BARRIER(); + + SM_IPT_DBG("Waiting for handshake next value: %d", next_value); + + while ((ctx->in.shm_queue->handshake != this_value) && + (ctx->in.shm_queue->handshake != next_value)) { + sm_ipt_os_yield(); + SM_IPT_OS_MEMORY_BARRIER(); + } +} + +int sm_ipt_init(struct sm_ipt_ctx *ctx, sm_ipt_receive_handler_t callback) +{ + int err; + + ctx->receive_handler = callback; + + err = sm_ipt_os_init(&ctx->os_ctx); + if (err < 0) { + return err; + } + + free_mask_init(ctx); + sm_ipt_os_sem_init(&ctx->out_sem); + sm_ipt_os_mutex_init(&ctx->out_mutex); + + ctx->out.shm_queue = (struct sm_ipt_shm_queue *) ctx->os_ctx.out_shmem_ptr; + ctx->in.shm_queue = (struct sm_ipt_shm_queue *) ctx->os_ctx.in_shmem_ptr; + + SM_IPT_ASSERT((uint32_t)ctx->out.shm_queue.allocable % WORD_SIZE == 0); + SM_IPT_ASSERT((uint32_t)ctx->in.shm_queue.allocable % WORD_SIZE == 0); + + ctx->out.block_size = (((ctx->os_ctx.out_total_size - sizeof(struct sm_ipt_queue)) + / NUM_BLOCKS) / WORD_SIZE) * WORD_SIZE; + ctx->in.block_size = (((ctx->os_ctx.in_total_size - sizeof(struct sm_ipt_queue)) + / NUM_BLOCKS) / WORD_SIZE) * WORD_SIZE; + ctx->out.allocable_size = ctx->out.block_size * NUM_BLOCKS; + ctx->in.allocable_size = ctx->in.block_size * NUM_BLOCKS; + + handshake_step(ctx, 0x32, 0x43); + handshake_step(ctx, 0x43, 0xF6); + + ctx->out.shm_queue->queue_tx = 0; + ctx->out.shm_queue->queue_rx = 0; + ctx->in.shm_queue->queue_tx = 0; + ctx->in.shm_queue->queue_rx = 0; + + sm_ipt_os_signal_handler(&ctx->os_ctx, signal_received); + + handshake_step(ctx, 0xF6, 0xA8); + handshake_step(ctx, 0xA8, 0xA8); + + SM_IPT_INF("Initialized successfully"); + SM_IPT_INF("Max TX size: %d", ctx->out.allocable_size); + SM_IPT_INF("Max RX size: %d", ctx->in.allocable_size); + + return 0; +} diff --git a/sm_ipt/template/sm_ipt_log_tmpl.h b/sm_ipt/template/sm_ipt_log_tmpl.h new file mode 100644 index 0000000000..112382d315 --- /dev/null +++ b/sm_ipt/template/sm_ipt_log_tmpl.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef SM_IPT_LOG_H_ +#define SM_IPT_LOG_H_ + +/* + * THIS IS A TEMPLATE FILE. + * It should be copied to a suitable location within the host environment into + * which Remote Procedure serialization is integrated, and the following macros + * should be provided with appropriate implementations. + */ + +/** + * @defgroup sm_ipt_log Logging functionality for SM IPT + * @{ + * @ingroup sm_ipt + * + * @brief Logging functionality for SM IPT + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Macro for logging a message with the severity level ERR. + * + * @param ... printf-style format string, optionally followed by arguments + * to be formatted and inserted in the resulting string. + */ +#define SM_IPT_ERR(...) + +/** + * @brief Macro for logging a message with the severity level WRN. + * + * @param ... printf-style format string, optionally followed by arguments + * to be formatted and inserted in the resulting string. + */ +#define SM_IPT_WRN(...) + +/** + * @brief Macro for logging a message with the severity level INF. + * + * @param ... printf-style format string, optionally followed by arguments + * to be formatted and inserted in the resulting string. + */ +#define SM_IPT_INF(...) + +/** + * @brief Macro for logging a message with the severity level DBG. + * + * @param ... printf-style format string, optionally followed by arguments + * to be formatted and inserted in the resulting string. + */ +#define SM_IPT_DBG(...) + + +#ifdef __cplusplus +} +#endif + +/** + *@} + */ + +#endif /* SM_IPT_LOG_H_ */ diff --git a/sm_ipt/template/sm_ipt_os_tmpl.h b/sm_ipt/template/sm_ipt_os_tmpl.h new file mode 100644 index 0000000000..ffca76b213 --- /dev/null +++ b/sm_ipt/template/sm_ipt_os_tmpl.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef SM_IPT_OS_H_ +#define SM_IPT_OS_H_ + +/* + * THIS IS A TEMPLATE FILE. + * It should be copied to a suitable location within the host environment into + * which Shared memory inter-processor transport is integrated, and the following + * macros and structures should be provided with appropriate implementations. + */ + +/** + * @defgroup sm_ipt_os OS-dependent functionality for SM IPT + * @{ + * @ingroup sm_ipt_os + * + * @brief OS-dependent functionality for SM IPT + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* --------------- Shared memory information --------------- */ + +/** @brief This struct holds all the data required by + * os layer to function. + */ +struct sm_ipt_os_ctx { + /** @brief Pointer to out share memory. */ + void *out_shmem_ptr; + + /** @brief Pointer to in share memory. */ + void *in_shmem_ptr; + + /** @brief Size of out share memory. */ + uint32_t out_total_size; + + /** @brief Size of in share memory. */ + uint32_t in_total_size; + + /** @brief Any other data needed by OS layer */ + some_type other_os_context_data; +}; + +/** @brief Assertion function. + */ +#define SM_IPT_ASSERT(_expr) __ASSERT_FUNCTION(_expr) + +/** @brief Full memory barier. + */ +#define SM_IPT_OS_MEMORY_BARRIER() __sync_synchronize() + +/** @brief Returns pointer to the container struct. + */ +#define SM_IPT_OS_GET_CONTAINTER(ptr, type, field) SOME_CONTAINER_MACRO(ptr, type, field) + +/* --------------- Inter-core signaling --------------- */ + +/** @brief Signal other core that new data is waiting. + */ +void nrf_rpc_os_signal(struct sm_ipt_os_ctx *os_ctx); + +/** @brief Sets callback that will be called when the other core + * has signaled incoming data. + */ +void nrf_rpc_os_signal_handler(struct sm_ipt_os_ctx *os_ctx, + void (*handler)(struct sm_ipt_os_ctx *)); + +/** @brief Initializes shared memory and returns all pointers + * used by sm_ipt. + */ +int sm_ipt_os_init(struct sm_ipt_os_ctx *os_ctx); + +/* --------------- Atomics --------------- */ + +/* This atomic type must be 32 bits in size. + */ +typedef some_type nrf_rpc_os_atomic_t; + +/* Perform the operation suggested by the name, and return the value that + * had previously been in *atomic. + */ +uint32_t nrf_rpc_os_atomic_or(nrf_rpc_os_atomic_t *atomic, uint32_t value); +uint32_t nrf_rpc_os_atomic_and(nrf_rpc_os_atomic_t *atomic, uint32_t value); +uint32_t nrf_rpc_os_atomic_get(nrf_rpc_os_atomic_t *atomic); + +/* --------------- Mutexes --------------- */ + +typedef some_type nrf_rpc_os_mutex_t; +void nrf_rpc_os_mutex_init(nrf_rpc_os_mutex_t *mutex); +void nrf_rpc_os_lock(nrf_rpc_os_mutex_t *mutex); +void nrf_rpc_os_unlock(nrf_rpc_os_mutex_t *mutex); + +/* --------------- Semaphores --------------- */ + +typedef some_type nrf_rpc_os_sem_t; +void nrf_rpc_os_sem_init(nrf_rpc_os_sem_t *sem); +void nrf_rpc_os_take(nrf_rpc_os_sem_t *sem); +void nrf_rpc_os_give(nrf_rpc_os_sem_t *sem); + +/* --------------- Other OS functionality --------------- */ + +void nrf_rpc_os_yield(void); +void nrf_rpc_os_fatal(void); +int nrf_rpc_os_clz64(uint64_t value); +int nrf_rpc_os_clz32(uint32_t value); + +#ifdef __cplusplus +} +#endif + +/** + *@} + */ + +#endif /* SM_IPT_OS_H_ */