blob: 2803de851c3fcb366d79e48ff2b81add1be36a6d [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <string.h>
#include <errno.h>
#include <cache.h>
#include <ipc/ipc_icmsg_buf.h>
/* Helpers */
static uint32_t idx_occupied(uint32_t len, uint32_t a, uint32_t b)
{
/* It is implicitly assumed a and b cannot differ by more then len. */
return (b > a) ? (len - (b - a)) : (a - b);
}
static uint32_t idx_cut(uint32_t len, uint32_t idx)
{
/* It is implicitly assumed a and b cannot differ by more then len. */
return (idx >= len) ? (idx - len) : (idx);
}
struct icmsg_buf *icmsg_buf_init(void *buf, size_t blen)
{
/* blen must be big enough to contain icmsg_buf struct, byte of data
* and message len (2 bytes).
*/
struct icmsg_buf *ib = buf;
__ASSERT_NO_MSG(blen > (sizeof(*ib) + sizeof(uint16_t)));
ib->len = blen - sizeof(*ib);
ib->wr_idx = 0;
ib->rd_idx = 0;
__sync_synchronize();
sys_cache_data_range(ib, sizeof(*ib), K_CACHE_WB);
return ib;
}
int icmsg_buf_write(struct icmsg_buf *ib, const char *buf, uint16_t len)
{
/* The length of buffer is immutable - avoid reloading that may happen
* due to memory bariers.
*/
const uint32_t iblen = ib->len;
/* rx_idx == wr_idx means the buffer is empty.
* Max bytes that can be stored is len - 1.
*/
const uint32_t max_len = iblen - 1;
sys_cache_data_range(ib, sizeof(*ib), K_CACHE_INVD);
__sync_synchronize();
uint32_t wr_idx = ib->wr_idx;
uint32_t rd_idx = ib->rd_idx;
if (len == 0) {
/* Incorrect call. */
return -EINVAL;
}
uint32_t avail = max_len - idx_occupied(iblen, wr_idx, rd_idx);
if ((len + sizeof(len) > avail) ||
(len + sizeof(len) > max_len)) {
/* No free space. */
return -ENOMEM;
}
/* Store info about the message length. */
ib->data[wr_idx] = (uint8_t)len;
sys_cache_data_range(&ib->data[wr_idx], sizeof(ib->data[wr_idx]), K_CACHE_WB);
wr_idx = idx_cut(iblen, wr_idx + sizeof(ib->data[wr_idx]));
ib->data[wr_idx] = (uint8_t)(len >> 8);
sys_cache_data_range(&ib->data[wr_idx], sizeof(ib->data[wr_idx]), K_CACHE_WB);
wr_idx = idx_cut(iblen, wr_idx + sizeof(ib->data[wr_idx]));
/* Write until the end of the buffer. */
uint32_t sz = MIN(len, iblen - wr_idx);
memcpy(&ib->data[wr_idx], buf, sz);
sys_cache_data_range(&ib->data[wr_idx], sz, K_CACHE_WB);
if (len > sz) {
/* Write remaining data at the buffer head. */
memcpy(&ib->data[0], buf + sz, len - sz);
sys_cache_data_range(&ib->data[0], len - sz, K_CACHE_WB);
}
/* Update write index - make other side aware data was written. */
__sync_synchronize();
wr_idx = idx_cut(iblen, wr_idx + len);
ib->wr_idx = wr_idx;
sys_cache_data_range(ib, sizeof(*ib), K_CACHE_WB);
return len;
}
int icmsg_buf_read(struct icmsg_buf *ib, char *buf, uint16_t len)
{
/* The length of buffer is immutable - avoid reloading. */
const uint32_t iblen = ib->len;
sys_cache_data_range(ib, sizeof(*ib), K_CACHE_INVD);
__sync_synchronize();
uint32_t rd_idx = ib->rd_idx;
uint32_t wr_idx = ib->wr_idx;
if (rd_idx == wr_idx) {
/* The buffer is empty. */
return 0;
}
uint32_t bytes_stored = idx_occupied(iblen, wr_idx, rd_idx);
/* Read message len. */
sys_cache_data_range(&ib->data[rd_idx], sizeof(ib->data[rd_idx]), K_CACHE_INVD);
uint16_t mlen = ib->data[rd_idx];
rd_idx = idx_cut(iblen, rd_idx + sizeof(ib->data[rd_idx]));
sys_cache_data_range(&ib->data[rd_idx], sizeof(ib->data[rd_idx]), K_CACHE_INVD);
mlen |= (ib->data[rd_idx] << 8);
rd_idx = idx_cut(iblen, rd_idx + sizeof(ib->data[rd_idx]));
if (!buf) {
return mlen;
}
if (len < mlen) {
return -ENOMEM;
}
if (bytes_stored < mlen + sizeof(mlen)) {
/* Part of message not available. Should not happen. */
__ASSERT_NO_MSG(false);
return -EAGAIN;
}
len = MIN(len, mlen);
/* Read up to the end of the buffer. */
uint32_t sz = MIN(len, iblen - rd_idx);
sys_cache_data_range(&ib->data[rd_idx], sz, K_CACHE_INVD);
memcpy(buf, &ib->data[rd_idx], sz);
if (len > sz) {
/* Read remaining bytes starting from the buffer head. */
sys_cache_data_range(&ib->data[0], len - sz, K_CACHE_INVD);
memcpy(&buf[sz], &ib->data[0], len - sz);
}
/* Update read index - make other side aware data was read. */
__sync_synchronize();
rd_idx = idx_cut(iblen, rd_idx + len);
ib->rd_idx = rd_idx;
sys_cache_data_range(ib, sizeof(*ib), K_CACHE_WB);
return len;
}