ipc: icmsg: send nocopy feature

Add a nocopy feature for sending message through the icmsg IPC library.

Also eliminate data races if multiple threads are using the
same ipc instance and are trying to send a message simultaneously.

Signed-off-by: Hubert Miś <hubert.mis@gmail.com>
This commit is contained in:
Hubert Miś 2022-11-11 16:09:46 +01:00 committed by Carles Cufí
commit 3a1c9bce6e
3 changed files with 264 additions and 12 deletions

View file

@ -15,8 +15,8 @@ config IPC_SERVICE_ICMSG_NOCOPY_RX
bool
depends on IPC_SERVICE_ICMSG
help
Enable nocopy feature for the icmsg library that might be used by
backends based on icmsg.
Enable nocopy feature for receiving path of the icmsg library that
might be used by backends based on icmsg.
# The Icmsg library in its simplicity requires the system workqueue to execute
# at a cooperative priority.

View file

@ -13,8 +13,11 @@
#define RX_BUF_SIZE CONFIG_IPC_SERVICE_ICMSG_CB_BUF_SIZE
#define BOND_NOTIFY_REPEAT_TO K_MSEC(CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS)
#define BUFFER_RELEASED 0
#define BUFFER_HELD 1
#define RX_BUFFER_RELEASED 0
#define RX_BUFFER_HELD 1
#define SEND_BUFFER_UNUSED 0
#define SEND_BUFFER_RESERVED 1
static const uint8_t magic[] = {0x45, 0x6d, 0x31, 0x6c, 0x31, 0x4b,
0x30, 0x72, 0x6e, 0x33, 0x6c, 0x69, 0x34};
@ -66,10 +69,35 @@ static bool is_endpoint_ready(struct icmsg_data_t *dev_data)
return atomic_get(&dev_data->state) == ICMSG_STATE_READY;
}
static bool is_send_buffer_reserved(struct icmsg_data_t *dev_data)
{
return atomic_get(&dev_data->send_buffer_reserved) ==
SEND_BUFFER_RESERVED;
}
static int reserve_send_buffer_if_unused(struct icmsg_data_t *dev_data)
{
bool was_unused;
was_unused = atomic_cas(&dev_data->send_buffer_reserved,
SEND_BUFFER_UNUSED, SEND_BUFFER_RESERVED);
return was_unused ? 0 : -EALREADY;
}
static int release_send_buffer(struct icmsg_data_t *dev_data)
{
bool was_reserved;
was_reserved = atomic_cas(&dev_data->send_buffer_reserved,
SEND_BUFFER_RESERVED, SEND_BUFFER_UNUSED);
return was_reserved ? 0 : -EALREADY;
}
static bool is_rx_buffer_free(struct icmsg_data_t *dev_data)
{
#ifdef CONFIG_IPC_SERVICE_ICMSG_NOCOPY_RX
return atomic_get(&dev_data->rx_buffer_held) == BUFFER_RELEASED;
return atomic_get(&dev_data->rx_buffer_held) == RX_BUFFER_RELEASED;
#else
return true;
#endif
@ -256,6 +284,8 @@ int icmsg_send(const struct icmsg_config_t *conf,
const void *msg, size_t len)
{
int ret;
int write_ret;
int release_ret;
int sent_bytes;
if (!is_endpoint_ready(dev_data)) {
@ -267,13 +297,122 @@ int icmsg_send(const struct icmsg_config_t *conf,
return -ENODATA;
}
ret = spsc_pbuf_write(dev_data->tx_ib, msg, len);
if (ret < 0) {
return ret;
} else if (ret < len) {
ret = reserve_send_buffer_if_unused(dev_data);
if (ret) {
return -ENOBUFS;
}
write_ret = spsc_pbuf_write(dev_data->tx_ib, msg, len);
release_ret = release_send_buffer(dev_data);
__ASSERT_NO_MSG(!release_ret);
if (write_ret < 0) {
return write_ret;
} else if (write_ret < len) {
return -EBADMSG;
}
sent_bytes = ret;
sent_bytes = write_ret;
__ASSERT_NO_MSG(conf->mbox_tx.dev != NULL);
ret = mbox_send(&conf->mbox_tx, NULL);
if (ret) {
return ret;
}
return sent_bytes;
}
int icmsg_get_tx_buffer(const struct icmsg_config_t *conf,
struct icmsg_data_t *dev_data,
void **data, size_t *size)
{
int ret;
int release_ret;
uint16_t requested_size;
int allocated_len;
char *allocated_buf;
if (*size == 0) {
/* Requested allocation of maximal size.
* Try to allocate maximal buffer size from spsc,
* potentially after wrapping marker.
*/
requested_size = SPSC_PBUF_MAX_LEN - 1;
} else {
requested_size = *size;
}
ret = reserve_send_buffer_if_unused(dev_data);
if (ret) {
return -ENOBUFS;
}
ret = spsc_pbuf_alloc(dev_data->tx_ib, requested_size, &allocated_buf);
if (ret < 0) {
release_ret = release_send_buffer(dev_data);
__ASSERT_NO_MSG(!release_ret);
return ret;
}
allocated_len = ret;
if (*size == 0) {
/* Requested allocation of maximal size.
* Pass the buffer that was allocated.
*/
*size = allocated_len;
*data = allocated_buf;
return 0;
}
if (*size == allocated_len) {
/* Allocated buffer is of requested size. */
*data = allocated_buf;
return 0;
}
/* Allocated smaller buffer than requested.
* Silently stop using the allocated buffer what is allowed by SPSC API
*/
release_send_buffer(dev_data);
*size = allocated_len;
return -ENOMEM;
}
int icmsg_drop_tx_buffer(const struct icmsg_config_t *conf,
struct icmsg_data_t *dev_data,
const void *data)
{
/* Silently stop using the allocated buffer what is allowed by SPSC API
*/
return release_send_buffer(dev_data);
}
int icmsg_send_nocopy(const struct icmsg_config_t *conf,
struct icmsg_data_t *dev_data,
const void *msg, size_t len)
{
int ret;
int sent_bytes;
if (!is_endpoint_ready(dev_data)) {
return -EBUSY;
}
/* Empty message is not allowed */
if (len == 0) {
return -ENODATA;
}
if (!is_send_buffer_reserved(dev_data)) {
return -ENXIO;
}
spsc_pbuf_commit(dev_data->tx_ib, len);
sent_bytes = len;
ret = release_send_buffer(dev_data);
__ASSERT_NO_MSG(!ret);
__ASSERT_NO_MSG(conf->mbox_tx.dev != NULL);
@ -301,7 +440,7 @@ int icmsg_hold_rx_buffer(const struct icmsg_config_t *conf,
}
was_released = atomic_cas(&dev_data->rx_buffer_held,
BUFFER_RELEASED, BUFFER_HELD);
RX_BUFFER_RELEASED, RX_BUFFER_HELD);
if (!was_released) {
return -EALREADY;
}
@ -324,7 +463,7 @@ int icmsg_release_rx_buffer(const struct icmsg_config_t *conf,
}
was_held = atomic_cas(&dev_data->rx_buffer_held,
BUFFER_HELD, BUFFER_RELEASED);
RX_BUFFER_HELD, RX_BUFFER_RELEASED);
if (!was_held) {
return -EALREADY;
}