Skip to content

Commit

Permalink
ipc: add dynamically allocated buffers to icmsg
Browse files Browse the repository at this point in the history
The icmsg and icmsg_me backends has limitations in context of
concurrent access to single instance. Some limitations are:
* sending by more thread at the same time may cause -EBUSY
* allocating TX buffer will cause errors in other threads that want
  to allocate before first thread sent the message,
* during no-copy receive, when RX buffer is on hold receiving is
  totally blocked.
This backend resolves those limitations by adding dynamically allocated
buffers on shared memory next to ICmsg circular buffer. The data is
passed using those buffers, so concurrency is not a problem. The ICmsg
is used only to pass short 2-byte messages containing references to
those buffers. The backend also supports multiple endpoint.
The ipc/icmsg_me sample was modified to support this backend.

Signed-off-by: Dominik Kilian <[email protected]>
  • Loading branch information
doki-nordic authored and carlescufi committed Nov 13, 2023
1 parent a3ff19a commit 28df449
Show file tree
Hide file tree
Showing 8 changed files with 1,452 additions and 0 deletions.
83 changes: 83 additions & 0 deletions doc/services/ipc/ipc_service/backends/ipc_service_icbmsg.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.. _ipc_service_backend_icbmsg:

ICMsg with dynamically allocated buffers backend
################################################

This backend is built on top of the :ref:`ipc_service_backend_icmsg`.
Data transferred over this backend travels in dynamically allocated buffers on shared memory.
The ICMsg just sends references to the buffers.
It also supports multiple endpoints.

This architecture allows for overcoming some common problems with other backends (mostly related to multithread access and zero-copy).
This backend provides an alternative with no significant limitations.

Overview
========

The shared memory is divided into two parts.
One is reserved for the ICMsg and the other contains equal-sized blocks.
The number of blocks is configured in the devicetree.

The data sending process is following:

* The sender allocates one or more blocks.
If there are not enough sequential blocks, it waits using the timeout provided in the parameter that also includes K_FOREVER and K_NO_WAIT.
* The allocated blocks are filled with data.
For the zero-copy case, this is done by the caller, otherwise, it is copied automatically.
During this time other threads are not blocked in any way as long as there are enough free blocks for them.
They can allocate, send data and receive data.
* A message containing the block index is sent over ICMsg to the receiver.
The size of the ICMsg queue is large enough to hold messages for all blocks, so it will never overflow.
* The receiver can hold the data as long as desired.
Again, other threads are not blocked as long as there are enough free blocks for them.
* When data is no longer needed, the backend sends a release message over ICMsg.
* When the backend receives this message, it deallocates all blocks.
It is done internally by the backend and it is invisible to the caller.

Configuration
=============

The backend is configured using Kconfig and devicetree.
When configuring the backend, do the following:

* Define two memory regions and assign them to ``tx-region`` and ``rx-region`` of an instance.
Ensure that the memory regions used for data exchange are unique (not overlapping any other region) and accessible by both domains (or CPUs).
* Define the number of allocable blocks for each region with ``tx-blocks`` and ``rx-blocks``.
* Define MBOX devices for sending a signal that informs the other domain (or CPU) of the written data.
Ensure that the other domain (or CPU) can receive the signal.

See the following configuration example for one of the instances:

.. code-block:: devicetree
reserved-memory {
tx: memory@20070000 {
reg = <0x20070000 0x0800>;
};
rx: memory@20078000 {
reg = <0x20078000 0x0800>;
};
};
ipc {
ipc0: ipc0 {
compatible = "zephyr,ipc-icbmsg";
tx-region = <&tx>;
rx-region = <&rx>;
tx-blocks = <16>;
rx-blocks = <32>;
mboxes = <&mbox 0>, <&mbox 1>;
mbox-names = "tx", "rx";
status = "okay";
};
};
You must provide a similar configuration for the other side of the communication (domain or CPU).
Swap the MBOX channels, memory regions (``tx-region`` and ``rx-region``), and block count (``tx-blocks`` and ``rx-blocks``).

Samples
=======

* :ref:`ipc_multi_endpoint_sample`
1 change: 1 addition & 0 deletions doc/services/ipc/ipc_service/ipc_service.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ backend.
:maxdepth: 1

backends/ipc_service_icmsg.rst
backends/ipc_service_icbmsg.rst

API Reference
*************
Expand Down
22 changes: 22 additions & 0 deletions dts/bindings/ipc/zephyr,ipc-icbmsg.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#

description: Inter-core messaging backend with dynamically allocated buffers

compatible: "zephyr,ipc-icbmsg"

include: zephyr,ipc-icmsg.yaml

properties:
tx-blocks:
description: number of allocable TX blocks
required: true
type: int

rx-blocks:
description: number of allocable RX blocks
required: true
type: int
10 changes: 10 additions & 0 deletions include/zephyr/ipc/pbuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ struct pbuf {
.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.
*
Expand Down
1 change: 1 addition & 0 deletions subsys/ipc/ipc_service/backends/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICMSG ipc_icmsg.c)
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICMSG_ME_INITIATOR ipc_icmsg_me_initiator.c)
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICMSG_ME_FOLLOWER ipc_icmsg_me_follower.c)
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_ICBMSG ipc_icbmsg.c)
zephyr_sources_ifdef(CONFIG_IPC_SERVICE_BACKEND_RPMSG ipc_rpmsg_static_vrings.c)
1 change: 1 addition & 0 deletions subsys/ipc/ipc_service/backends/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ config IPC_SERVICE_BACKEND_ICMSG_ME_FOLLOWER
role.

rsource "Kconfig.icmsg_me"
rsource "Kconfig.icbmsg"
rsource "Kconfig.rpmsg"
31 changes: 31 additions & 0 deletions subsys/ipc/ipc_service/backends/Kconfig.icbmsg
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0

menuconfig IPC_SERVICE_BACKEND_ICBMSG
bool "ICMSG backend with dynamically allocated buffers"
default y
depends on MBOX
depends on DT_HAS_ZEPHYR_IPC_ICBMSG_ENABLED
select IPC_SERVICE_ICMSG
help
Choosing this backend results in multi endpoint implementation based
on dynamically allocated buffers. References to them are send over
ICMsg circular buffer.

if IPC_SERVICE_BACKEND_ICBMSG

config IPC_SERVICE_BACKEND_ICBMSG_NUM_EP
int "Endpoints count"
range 1 254
default 4
help
Number of endpoints supported by the ICBMsg IPC service
backend. The number of endpoints are applied to all the instances,
so this value should be maximum number among all the instances.

module = IPC_SERVICE_BACKEND_ICBMSG
module-str = ICMSG backend with separate buffers
module-help = Sets log level for ICMsg backend with buffers
source "subsys/logging/Kconfig.template.log_config"

endif # IPC_SERVICE_BACKEND_ICBMSG
Loading

0 comments on commit 28df449

Please sign in to comment.