lib: os: spsc_pbuf: Add option to use cache

Add flags option to init call and a flag to use cache.
Add Kconfig choice to pick how to approach cache. Cache can be
enforced in all spsc_pbuf instances, disable in all, or runtime selected
based on configuration flag. Option is added to allow memory footprint
savings.

Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
Krzysztof Chruscinski 2022-05-17 19:36:07 +02:00 committed by Carles Cufí
commit 0829610bbc
6 changed files with 103 additions and 27 deletions

View file

@ -17,25 +17,43 @@ extern "C" {
* @{
*/
/**@defgroup SPSC_PBUF_FLAGS MPSC packet buffer flags
* @{
*/
/** @brief Flag indicating that cache shall be handled. */
#define SPSC_PBUF_CACHE BIT(0)
/**@} */
/**
* @brief Inter core messaging buffer
* @brief Single producer, single consumer packet buffer
*
* The inter core messaging buffer implements lightweight unidirectional
* messaging buffer with read/write semantics on top of a memory region shared
* by the reader and writer. It embeds cache and memory barier management to
* ensure correct data access.
* The SPSC 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 barier
* management to ensure correct data access.
*
* This structure supports single writter and reader. Data stored in the buffer
* is encapsulated to a message.
* This structure supports single writer and reader. Data stored in the buffer
* is encapsulated to a message (with length header).
*
*/
struct spsc_pbuf {
uint32_t len; /* Length of data[] in bytes. */
uint32_t wr_idx; /* Index of the first free byte in data[] */
uint32_t rd_idx; /* Index of the first valid byte in data[] */
uint32_t flags; /* Flags. See @ref SPSC_PBUF_FLAGS */
uint8_t data[]; /* Buffer data. */
};
/** @brief Get buffer capacity.
*
* @param blen Length of the buffer dedicated for the packet buffer.
*
* @return Packet buffer capacity.
*/
#define SPSC_PBUF_CAPACITY(blen) ((blen) - offsetof(struct spsc_pbuf, data))
/**
* @brief Initialize the packet buffer.
*
@ -48,10 +66,11 @@ struct spsc_pbuf {
* contain the internal structure and at least two
* bytes of data (one is reserved for written
* messages length).
* @param flags Option flags. See @ref SPSC_PBUF_FLAGS.
* @retval struct spsc_pbuf* Pointer to the created buffer. The pointer
* points to the same address as buf.
*/
struct spsc_pbuf *spsc_pbuf_init(void *buf, size_t blen);
struct spsc_pbuf *spsc_pbuf_init(void *buf, size_t blen, uint32_t flags);
/**
* @brief Write specified amount of data to the packet buffer.

View file

@ -45,6 +45,41 @@ config SPSC_PBUF
storing variable length packets in a circular way and operate directly
on the buffer memory.
if SPSC_PBUF
choice SPSC_PBUF_CACHE_HANDLING
prompt "Cache handling"
default SPSC_PBUF_CACHE_ALWAYS if SPSC_PBUF_USE_CACHE && !SPSC_PBUF_NO_CACHE
default SPSC_PBUF_CACHE_NEVER if !SPSC_PBUF_USE_CACHE && SPSC_PBUF_NO_CACHE
default SPSC_PBUF_CACHE_FLAG
config SPSC_PBUF_CACHE_FLAG
bool "Use cache flag"
help
Use instance specific configuration flag for cache handling.
config SPSC_PBUF_CACHE_ALWAYS
bool "Always handle cache"
help
Handle cache writeback and invalidation for all instances. Option used
to avoid runtime check and thus reduce memory footprint.
config SPSC_PBUF_CACHE_NEVER
bool "Never handle cache"
help
Discar cache handling for all instances. Option used to avoid runtime
check and thus reduce memory footprint.
endchoice
config SPSC_PBUF_USE_CACHE
bool
config SPSC_PBUF_NO_CACHE
bool
endif # SPSC_PBUF
config SHARED_MULTI_HEAP
bool "Shared multi-heap manager"
help

View file

@ -24,7 +24,23 @@ static uint32_t idx_cut(uint32_t len, uint32_t idx)
return (idx >= len) ? (idx - len) : (idx);
}
struct spsc_pbuf *spsc_pbuf_init(void *buf, size_t blen)
static inline void cache_wb(void *data, size_t len, uint32_t flags)
{
if (IS_ENABLED(CONFIG_SPSC_PBUF_CACHE_ALWAYS) ||
(IS_ENABLED(CONFIG_SPSC_PBUF_CACHE_FLAG) && (flags & SPSC_PBUF_CACHE))) {
sys_cache_data_range(data, len, K_CACHE_WB);
}
}
static inline void cache_inv(void *data, size_t len, uint32_t flags)
{
if (IS_ENABLED(CONFIG_SPSC_PBUF_CACHE_ALWAYS) ||
(IS_ENABLED(CONFIG_SPSC_PBUF_CACHE_FLAG) && (flags & SPSC_PBUF_CACHE))) {
sys_cache_data_range(data, len, K_CACHE_INVD);
}
}
struct spsc_pbuf *spsc_pbuf_init(void *buf, size_t blen, uint32_t flags)
{
/* blen must be big enough to contain spsc_pbuf struct, byte of data
* and message len (2 bytes).
@ -36,9 +52,10 @@ struct spsc_pbuf *spsc_pbuf_init(void *buf, size_t blen)
pb->len = blen - sizeof(*pb);
pb->wr_idx = 0;
pb->rd_idx = 0;
pb->flags = flags;
__sync_synchronize();
sys_cache_data_range(pb, sizeof(*pb), K_CACHE_WB);
cache_wb(pb, sizeof(*pb), pb->flags);
return pb;
}
@ -55,7 +72,7 @@ int spsc_pbuf_write(struct spsc_pbuf *pb, const char *buf, uint16_t len)
*/
const uint32_t max_len = pblen - 1;
sys_cache_data_range(pb, sizeof(*pb), K_CACHE_INVD);
cache_inv(pb, sizeof(*pb), pb->flags);
__sync_synchronize();
uint32_t wr_idx = pb->wr_idx;
@ -76,23 +93,23 @@ int spsc_pbuf_write(struct spsc_pbuf *pb, const char *buf, uint16_t len)
/* Store info about the message length. */
pb->data[wr_idx] = (uint8_t)len;
sys_cache_data_range(&pb->data[wr_idx], sizeof(pb->data[wr_idx]), K_CACHE_WB);
cache_wb(&pb->data[wr_idx], sizeof(pb->data[wr_idx]), pb->flags);
wr_idx = idx_cut(pblen, wr_idx + sizeof(pb->data[wr_idx]));
pb->data[wr_idx] = (uint8_t)(len >> 8);
sys_cache_data_range(&pb->data[wr_idx], sizeof(pb->data[wr_idx]), K_CACHE_WB);
cache_wb(&pb->data[wr_idx], sizeof(pb->data[wr_idx]), pb->flags);
wr_idx = idx_cut(pblen, wr_idx + sizeof(pb->data[wr_idx]));
/* Write until the end of the buffer. */
uint32_t sz = MIN(len, pblen - wr_idx);
memcpy(&pb->data[wr_idx], buf, sz);
sys_cache_data_range(&pb->data[wr_idx], sz, K_CACHE_WB);
cache_wb(&pb->data[wr_idx], sz, pb->flags);
if (len > sz) {
/* Write remaining data at the buffer head. */
memcpy(&pb->data[0], buf + sz, len - sz);
sys_cache_data_range(&pb->data[0], len - sz, K_CACHE_WB);
cache_wb(&pb->data[0], len - sz, pb->flags);
}
/* Update write index - make other side aware data was written. */
@ -100,7 +117,7 @@ int spsc_pbuf_write(struct spsc_pbuf *pb, const char *buf, uint16_t len)
wr_idx = idx_cut(pblen, wr_idx + len);
pb->wr_idx = wr_idx;
sys_cache_data_range(pb, sizeof(*pb), K_CACHE_WB);
cache_wb(pb, sizeof(*pb), pb->flags);
return len;
}
@ -110,7 +127,7 @@ int spsc_pbuf_read(struct spsc_pbuf *pb, char *buf, uint16_t len)
/* The length of buffer is immutable - avoid reloading. */
const uint32_t pblen = pb->len;
sys_cache_data_range(pb, sizeof(*pb), K_CACHE_INVD);
cache_inv(pb, sizeof(*pb), pb->flags);
__sync_synchronize();
uint32_t rd_idx = pb->rd_idx;
@ -124,12 +141,12 @@ int spsc_pbuf_read(struct spsc_pbuf *pb, char *buf, uint16_t len)
uint32_t bytes_stored = idx_occupied(pblen, wr_idx, rd_idx);
/* Read message len. */
sys_cache_data_range(&pb->data[rd_idx], sizeof(pb->data[rd_idx]), K_CACHE_INVD);
cache_inv(&pb->data[rd_idx], sizeof(pb->data[rd_idx]), pb->flags);
uint16_t mlen = pb->data[rd_idx];
rd_idx = idx_cut(pblen, rd_idx + sizeof(pb->data[rd_idx]));
sys_cache_data_range(&pb->data[rd_idx], sizeof(pb->data[rd_idx]), K_CACHE_INVD);
cache_inv(&pb->data[rd_idx], sizeof(pb->data[rd_idx]), pb->flags);
mlen |= (pb->data[rd_idx] << 8);
rd_idx = idx_cut(pblen, rd_idx + sizeof(pb->data[rd_idx]));
@ -152,11 +169,11 @@ int spsc_pbuf_read(struct spsc_pbuf *pb, char *buf, uint16_t len)
/* Read up to the end of the buffer. */
uint32_t sz = MIN(len, pblen - rd_idx);
sys_cache_data_range(&pb->data[rd_idx], sz, K_CACHE_INVD);
cache_inv(&pb->data[rd_idx], sz, pb->flags);
memcpy(buf, &pb->data[rd_idx], sz);
if (len > sz) {
/* Read remaining bytes starting from the buffer head. */
sys_cache_data_range(&pb->data[0], len - sz, K_CACHE_INVD);
cache_inv(&pb->data[0], len - sz, pb->flags);
memcpy(&buf[sz], &pb->data[0], len - sz);
}
@ -165,7 +182,7 @@ int spsc_pbuf_read(struct spsc_pbuf *pb, char *buf, uint16_t len)
rd_idx = idx_cut(pblen, rd_idx + len);
pb->rd_idx = rd_idx;
sys_cache_data_range(pb, sizeof(*pb), K_CACHE_WB);
cache_wb(pb, sizeof(*pb), pb->flags);
return len;
}

View file

@ -23,6 +23,7 @@ config IPC_SERVICE_BACKEND_ICMSG
depends on MBOX
default $(dt_compat_enabled,$(DT_COMPAT_ZEPHYR_IPC_ICMSG))
select SPSC_PBUF
select SPSC_PBUF_USE_CACHE
help
Chosing this backend results in single endpoint implementation based
on circular packet buffer.

View file

@ -192,7 +192,9 @@ static int backend_init(const struct device *instance)
__ASSERT_NO_MSG(conf->tx_shm_size > sizeof(struct spsc_pbuf));
dev_data->tx_ib = spsc_pbuf_init((void *)conf->tx_shm_addr, conf->tx_shm_size);
dev_data->tx_ib = spsc_pbuf_init((void *)conf->tx_shm_addr,
conf->tx_shm_size,
SPSC_PBUF_CACHE);
dev_data->rx_ib = (void *)conf->rx_shm_addr;
return 0;

View file

@ -11,7 +11,7 @@
* 212 - sizeof(struct spsc_pbuf) - 1 = 199.
* -1 because internal rd/wr_idx is reserved to mean the buffer is empty.
*/
static uint8_t memory_area[212] __aligned(4);
static uint8_t memory_area[216] __aligned(4);
static void test_spsc_pbuf_ut(void)
{
@ -21,7 +21,7 @@ static void test_spsc_pbuf_ut(void)
int rlen;
int wlen;
ib = spsc_pbuf_init(memory_area, sizeof(memory_area));
ib = spsc_pbuf_init(memory_area, sizeof(memory_area), 0);
zassert_equal_ptr(ib, memory_area, NULL);
zassert_equal(ib->len, (sizeof(memory_area) - sizeof(*ib)), NULL);
zassert_equal(ib->wr_idx, 0, NULL);
@ -48,14 +48,16 @@ static void test_spsc_pbuf_ut(void)
zassert_equal(ib->wr_idx, (sizeof(message) + sizeof(uint16_t)), NULL);
zassert_equal(ib->rd_idx, (sizeof(message) + sizeof(uint16_t)), NULL);
/* Buffer size is 212 - 12 = 200 Bytes for len, wr_idx, rd_idx.
/* Buffer size is 216 - 16 = 200 Bytes for len, wr_idx, rd_idx and flags.
* When writing message of 20 Bytes, actually 22 Bytes are stored,
* (2 Bytes reserved for message len). Test if after 9 writes, 10th write
* would return -ENOMEM. 200 - (9 * 22) = 2 bytes left.
*
* Reset the buffer first.
*/
ib = spsc_pbuf_init(memory_area, sizeof(memory_area));
zassert_equal(SPSC_PBUF_CAPACITY(sizeof(memory_area)), 200, NULL);
ib = spsc_pbuf_init(memory_area, sizeof(memory_area), 0);
zassert_equal_ptr(ib, memory_area, NULL);
zassert_equal(ib->len, (sizeof(memory_area) - sizeof(*ib)), NULL);
zassert_equal(ib->wr_idx, 0, NULL);