blob: 6d2954f7667929aa7270ad49e777019909c6ceb7 [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#include <zephyr/ztress.h>
#include <zephyr/sys/spsc_pbuf.h>
#include <zephyr/random/rand32.h>
#define HDR_LEN sizeof(uint32_t)
#define TLEN(len) ROUND_UP(HDR_LEN + len, sizeof(uint32_t))
#define STRESS_TIMEOUT_MS ((CONFIG_SYS_CLOCK_TICKS_PER_SEC < 10000) ? 1000 : 15000)
/* The buffer size itself would be 199 bytes.
* 212 - sizeof(struct spsc_pbuf) - 1 = 199.
* -1 because internal rd/wr_idx is reserved to mean the buffer is empty.
*/
static bool use_cache(uint32_t flags)
{
return IS_ENABLED(CONFIG_SPSC_PBUF_CACHE_ALWAYS) ||
(IS_ENABLED(CONFIG_SPSC_PBUF_CACHE_FLAG) && (flags & SPSC_PBUF_CACHE));
}
static void test_spsc_pbuf_flags(uint32_t flags)
{
static uint8_t memory_area[216] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
static uint8_t rbuf[198];
static uint8_t message[20] = {'a'};
struct spsc_pbuf *ib;
int rlen;
int wlen;
size_t capacity = (use_cache(flags) ?
(sizeof(memory_area) - offsetof(struct spsc_pbuf, ext.cache.data)) :
(sizeof(memory_area) - offsetof(struct spsc_pbuf, ext.nocache.data))) -
sizeof(uint32_t);
memset(memory_area, 0, sizeof(memory_area));
ib = spsc_pbuf_init(memory_area, sizeof(memory_area), flags);
zassert_equal_ptr(ib, memory_area, NULL);
zassert_equal(spsc_pbuf_capacity(ib), capacity);
/* Try writing invalid value. */
rlen = spsc_pbuf_write(ib, rbuf, 0);
zassert_equal(rlen, -EINVAL);
rlen = spsc_pbuf_write(ib, rbuf, SPSC_PBUF_MAX_LEN);
zassert_equal(rlen, -EINVAL);
/* Try to write more than buffer can store. */
rlen = spsc_pbuf_write(ib, rbuf, sizeof(rbuf));
zassert_equal(rlen, -ENOMEM);
/* Read empty buffer. */
rlen = spsc_pbuf_read(ib, rbuf, sizeof(rbuf));
zassert_equal(rlen, 0);
/* Single write and read. */
wlen = spsc_pbuf_write(ib, message, sizeof(message));
zassert_equal(wlen, sizeof(message));
rlen = spsc_pbuf_read(ib, rbuf, sizeof(rbuf));
zassert_equal(rlen, sizeof(message));
ib = spsc_pbuf_init(memory_area, sizeof(memory_area), flags);
zassert_equal_ptr(ib, memory_area, NULL);
int repeat = capacity / (sizeof(message) + sizeof(uint32_t));
for (int i = 0; i < repeat; i++) {
wlen = spsc_pbuf_write(ib, message, sizeof(message));
zassert_equal(wlen, sizeof(message));
}
wlen = spsc_pbuf_write(ib, message, sizeof(message));
zassert_equal(wlen, -ENOMEM);
/* Test reading with buf == NULL, should return len of the next message to read. */
rlen = spsc_pbuf_read(ib, NULL, 0);
zassert_equal(rlen, sizeof(message));
/* Read with len == 0 and correct buf pointer. */
rlen = spsc_pbuf_read(ib, rbuf, 0);
zassert_equal(rlen, -ENOMEM);
/* Read whole data from the buffer. */
for (size_t i = 0; i < repeat; i++) {
wlen = spsc_pbuf_read(ib, rbuf, sizeof(rbuf));
zassert_equal(wlen, sizeof(message));
}
/* Buffer is empty */
rlen = spsc_pbuf_read(ib, NULL, 0);
zassert_equal(rlen, 0);
/* Write message that would be wrapped around. */
wlen = spsc_pbuf_write(ib, message, sizeof(message));
zassert_equal(wlen, sizeof(message));
/* Read wrapped message. */
rlen = spsc_pbuf_read(ib, rbuf, sizeof(rbuf));
zassert_equal(rlen, sizeof(message));
zassert_equal(message[0], 'a');
}
ZTEST(test_spsc_pbuf, test_spsc_pbuf_ut)
{
test_spsc_pbuf_flags(0);
}
ZTEST(test_spsc_pbuf, test_spsc_pbuf_ut_cache)
{
test_spsc_pbuf_flags(SPSC_PBUF_CACHE);
}
static int check_buffer(char *buf, uint16_t len, char exp)
{
for (uint16_t i = 0; i < len; i++) {
if (buf[i] != exp) {
return -EINVAL;
}
}
return 0;
}
static void packet_write(struct spsc_pbuf *pb,
uint16_t len,
uint16_t outlen,
uint8_t id,
int exp_rv,
int line)
{
int rv;
char *buf;
rv = spsc_pbuf_alloc(pb, len, &buf);
zassert_equal(rv, exp_rv, "%d: Unexpected rv:%d (exp:%d)", line, rv, exp_rv);
if (rv < 0) {
return;
}
zassert_equal((uintptr_t)buf % sizeof(uint32_t), 0, "%d: Expected aligned buffer", line);
zassert_true(rv >= outlen, "%d: Unexpected rv (bigger than %d)", line, rv, outlen);
for (uint16_t i = 0; i < outlen; i++) {
buf[i] = id + i;
}
if (outlen > 0) {
spsc_pbuf_commit(pb, outlen);
}
}
#define PACKET_WRITE(_pb, _len, _outlen, _id, _exp_rv) \
packet_write(_pb, _len, _outlen, _id, _exp_rv, __LINE__)
static void packet_consume(struct spsc_pbuf *pb,
uint16_t exp_rv,
uint8_t exp_id,
int line)
{
uint16_t rv;
char *buf;
rv = spsc_pbuf_claim(pb, &buf);
zassert_equal(rv, exp_rv, "%d: Unexpected rv:%d (exp:%d)", line, rv, exp_rv);
if (rv == 0) {
return;
}
for (int i = 0; i < rv; i++) {
zassert_equal(buf[i], exp_id + i, "%d: Unexpected value %d (exp:%d) at %d",
line, buf[i], exp_id + i, i);
}
spsc_pbuf_free(pb, rv);
}
#define PACKET_CONSUME(_pb, _exp_rv, _exp_id) packet_consume(_pb, _exp_rv, _exp_id, __LINE__)
ZTEST(test_spsc_pbuf, test_0cpy)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
struct spsc_pbuf *pb = spsc_pbuf_init(buffer, sizeof(buffer), 0);
uint32_t capacity = spsc_pbuf_capacity(pb);
uint16_t len1;
uint16_t len2;
/* Writing 0 length returns error. */
PACKET_WRITE(pb, 0, 0, 0, -EINVAL);
spsc_pbuf_commit(pb, 0);
PACKET_WRITE(pb, SPSC_PBUF_MAX_LEN, 0, 0, capacity - sizeof(uint32_t));
len1 = capacity - 8 - 2 * sizeof(uint32_t);
PACKET_WRITE(pb, len1, len1, 0, len1);
/* Remaining space. */
len2 = capacity - TLEN(len1) - HDR_LEN;
/* Request exceeding capacity*/
PACKET_WRITE(pb, len2 + 1, 0, 1, len2);
PACKET_WRITE(pb, len2, len2, 1, len2);
/* Consume packets. */
PACKET_CONSUME(pb, len1, 0);
PACKET_CONSUME(pb, len2, 1);
/* No more packets. */
PACKET_CONSUME(pb, 0, 0);
}
ZTEST(test_spsc_pbuf, test_0cpy_smaller)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
struct spsc_pbuf *pb = spsc_pbuf_init(buffer, sizeof(buffer), 0);
uint32_t capacity = spsc_pbuf_capacity(pb);
uint16_t len1;
uint16_t len2;
len1 = capacity - 10 - sizeof(uint16_t);
PACKET_WRITE(pb, len1, len1 - 5, 0, len1);
len2 = 10 - sizeof(uint16_t) - 1;
PACKET_WRITE(pb, len2, len2, 1, len2);
/* Consume packets. */
PACKET_CONSUME(pb, len1 - 5, 0);
PACKET_CONSUME(pb, len2, 1);
PACKET_CONSUME(pb, 0, 0);
}
ZTEST(test_spsc_pbuf, test_0cpy_discard)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
struct spsc_pbuf *pb = spsc_pbuf_init(buffer, sizeof(buffer), 0);
uint32_t capacity = spsc_pbuf_capacity(pb);
int len1, len2;
len1 = 14;
PACKET_WRITE(pb, len1, len1, 0, len1);
len2 = capacity - TLEN(len1) - 10;
PACKET_WRITE(pb, len2, len2, 1, len2);
/* Consume first packet */
PACKET_CONSUME(pb, len1, 0);
/* Consume next packet. At this point buffer shall be completely empty. */
PACKET_CONSUME(pb, len2, 1);
/* Allocate but then discard by committing 0 length. Alloc will add padding. */
PACKET_WRITE(pb, len1, 0, 0, len1);
/* No packet in the buffer. */
PACKET_CONSUME(pb, 0, 0);
/* Buffer is empty except for the padding added by alloc. */
len2 = len1 + len2 - sizeof(uint16_t);
PACKET_WRITE(pb, len2, 0, 0, len2);
}
ZTEST(test_spsc_pbuf, test_0cpy_corner1)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
struct spsc_pbuf *pb;
uint32_t capacity;
char *buf;
uint16_t len;
uint16_t len1;
uint16_t len2;
pb = spsc_pbuf_init(buffer, sizeof(buffer), 0);
capacity = spsc_pbuf_capacity(pb);
/* Commit 5 byte packet. */
len1 = 5;
PACKET_WRITE(pb, len1, len1, 0, len1);
/* Attempt to allocate packet till the end of the buffer. */
len2 = capacity;
len2 = spsc_pbuf_alloc(pb, len2, &buf);
uint16_t exp_len2 = capacity - TLEN(len1) - HDR_LEN;
zassert_equal(len2, exp_len2, "got %d, exp: %d", len2, exp_len2);
len = spsc_pbuf_claim(pb, &buf);
zassert_equal(len1, len);
spsc_pbuf_free(pb, len);
spsc_pbuf_commit(pb, len2);
len = spsc_pbuf_claim(pb, &buf);
zassert_equal(len2, len);
spsc_pbuf_free(pb, len);
}
ZTEST(test_spsc_pbuf, test_0cpy_corner2)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
struct spsc_pbuf *pb;
uint32_t capacity;
uint16_t len1;
uint16_t len2;
pb = spsc_pbuf_init(buffer, sizeof(buffer), 0);
capacity = spsc_pbuf_capacity(pb);
/* Commit 16 byte packet. */
len1 = 16;
PACKET_WRITE(pb, len1, len1, 0, len1);
/* Attempt to allocate packet that will leave 5 bytes at the end. */
len2 = capacity - TLEN(len1) - HDR_LEN - 5;
PACKET_WRITE(pb, len2, len2, 1, len2);
/* Free first packet. */
PACKET_CONSUME(pb, len1, 0);
/* Allocate something that does not fit at the end. */
len1 = 8;
PACKET_WRITE(pb, len1, len1, 2, len1);
/* There should be no place in the buffer now, only length field would fill.*/
PACKET_WRITE(pb, 1, 0, 2, 0);
/* Free second packet. */
PACKET_CONSUME(pb, len2, 1);
/* Get longest available. As now there is only one packet at the beginning
* that should be remaining space decremented by length fields.
*/
uint16_t exp_len = capacity - TLEN(len1) - HDR_LEN;
PACKET_WRITE(pb, capacity, 0, 2, exp_len);
}
ZTEST(test_spsc_pbuf, test_largest_alloc)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
struct spsc_pbuf *pb;
uint32_t capacity;
uint16_t len1;
uint16_t len2;
pb = spsc_pbuf_init(buffer, sizeof(buffer), 0);
capacity = spsc_pbuf_capacity(pb);
len1 = 15;
PACKET_WRITE(pb, len1, len1, 0, len1);
PACKET_CONSUME(pb, len1, 0);
len2 = capacity - TLEN(len1) - TLEN(10);
PACKET_WRITE(pb, len2, len2, 1, len2);
PACKET_WRITE(pb, SPSC_PBUF_MAX_LEN, 0, 1, 12);
PACKET_WRITE(pb, SPSC_PBUF_MAX_LEN - 1, 0, 1, 12);
pb = spsc_pbuf_init(buffer, sizeof(buffer), 0);
capacity = spsc_pbuf_capacity(pb);
len1 = 15;
PACKET_WRITE(pb, len1, len1, 0, len1);
PACKET_CONSUME(pb, len1, 0);
len2 = capacity - TLEN(len1) - TLEN(12);
PACKET_WRITE(pb, len2, len2, 1, len2);
PACKET_WRITE(pb, SPSC_PBUF_MAX_LEN - 1, 0, 1, 12);
}
ZTEST(test_spsc_pbuf, test_utilization)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
struct spsc_pbuf *pb;
uint32_t capacity;
uint16_t len1, len2, len3;
int u;
pb = spsc_pbuf_init(buffer, sizeof(buffer), 0);
if (!IS_ENABLED(CONFIG_SPSC_PBUF_UTILIZATION)) {
zassert_equal(spsc_pbuf_get_utilization(pb), -ENOTSUP);
return;
}
capacity = spsc_pbuf_capacity(pb);
len1 = 10;
PACKET_WRITE(pb, len1, len1, 0, len1);
u = spsc_pbuf_get_utilization(pb);
zassert_equal(u, 0);
PACKET_CONSUME(pb, len1, 0);
u = spsc_pbuf_get_utilization(pb);
zassert_equal(u, TLEN(len1));
len2 = 11;
PACKET_WRITE(pb, len2, len2, 1, len2);
PACKET_CONSUME(pb, len2, 1);
u = spsc_pbuf_get_utilization(pb);
zassert_equal(u, TLEN(len2));
len3 = capacity - TLEN(len1) - TLEN(len2);
PACKET_WRITE(pb, SPSC_PBUF_MAX_LEN, len3, 2, len3);
PACKET_CONSUME(pb, len3, 2);
u = spsc_pbuf_get_utilization(pb);
int exp_u = TLEN(len3);
zassert_equal(u, exp_u);
}
struct stress_data {
struct spsc_pbuf *pbuf;
uint32_t capacity;
uint32_t write_cnt;
uint32_t read_cnt;
uint32_t wr_err;
};
bool stress_read(void *user_data, uint32_t cnt, bool last, int prio)
{
struct stress_data *ctx = (struct stress_data *)user_data;
char buf[128];
int len;
int rpt = (sys_rand32_get() & 3) + 1;
for (int i = 0; i < rpt; i++) {
len = spsc_pbuf_read(ctx->pbuf, buf, (uint16_t)sizeof(buf));
if (len == 0) {
return true;
}
if (len < 0) {
zassert_true(false, "Unexpected error: %d, cnt:%d", len, ctx->read_cnt);
}
check_buffer(buf, len, ctx->read_cnt);
ctx->read_cnt++;
}
return true;
}
bool stress_write(void *user_data, uint32_t cnt, bool last, int prio)
{
struct stress_data *ctx = (struct stress_data *)user_data;
char buf[128];
uint16_t len = 1 + (sys_rand32_get() % (ctx->capacity / 4));
int rpt = (sys_rand32_get() & 1) + 1;
zassert_true(len < sizeof(buf), "len:%d %d", len, ctx->capacity);
for (int i = 0; i < rpt; i++) {
memset(buf, (uint8_t)ctx->write_cnt, len);
if (spsc_pbuf_write(ctx->pbuf, buf, len) == len) {
ctx->write_cnt++;
} else {
ctx->wr_err++;
}
}
return true;
}
ZTEST(test_spsc_pbuf, test_stress)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
static struct stress_data ctx = {};
uint32_t repeat = 0;
ctx.pbuf = spsc_pbuf_init(buffer, sizeof(buffer), 0);
ctx.capacity = spsc_pbuf_capacity(ctx.pbuf);
ztress_set_timeout(K_MSEC(STRESS_TIMEOUT_MS));
TC_PRINT("Reading from an interrupt, writing from a thread\n");
ZTRESS_EXECUTE(ZTRESS_TIMER(stress_read, &ctx, repeat, Z_TIMEOUT_TICKS(4)),
ZTRESS_THREAD(stress_write, &ctx, repeat, 2000, Z_TIMEOUT_TICKS(4)));
TC_PRINT("Writes:%d failures: %d\n", ctx.write_cnt, ctx.wr_err);
TC_PRINT("Writing from an interrupt, reading from a thread\n");
ZTRESS_EXECUTE(ZTRESS_TIMER(stress_write, &ctx, repeat, Z_TIMEOUT_TICKS(4)),
ZTRESS_THREAD(stress_read, &ctx, repeat, 1000, Z_TIMEOUT_TICKS(4)));
TC_PRINT("Writes:%d failures: %d\n", ctx.write_cnt, ctx.wr_err);
}
bool stress_claim_free(void *user_data, uint32_t cnt, bool last, int prio)
{
struct stress_data *ctx = (struct stress_data *)user_data;
char *buf;
uint16_t len;
int rpt = sys_rand32_get() % 0x3;
for (int i = 0; i < rpt; i++) {
len = spsc_pbuf_claim(ctx->pbuf, &buf);
if (len == 0) {
return true;
}
check_buffer(buf, len, ctx->read_cnt);
spsc_pbuf_free(ctx->pbuf, len);
ctx->read_cnt++;
}
return true;
}
bool stress_alloc_commit(void *user_data, uint32_t cnt, bool last, int prio)
{
struct stress_data *ctx = (struct stress_data *)user_data;
uint32_t rnd = sys_rand32_get();
uint16_t len = 1 + (rnd % (ctx->capacity / 4));
int rpt = rnd % 0x3;
char *buf;
int err;
for (int i = 0; i < rpt; i++) {
err = spsc_pbuf_alloc(ctx->pbuf, len, &buf);
zassert_true(err >= 0);
if (err != len) {
return true;
}
memset(buf, (uint8_t)ctx->write_cnt, len);
spsc_pbuf_commit(ctx->pbuf, len);
ctx->write_cnt++;
}
return true;
}
ZTEST(test_spsc_pbuf, test_stress_0cpy)
{
static uint8_t buffer[128] __aligned(MAX(Z_SPSC_PBUF_DCACHE_LINE, 4));
static struct stress_data ctx;
uint32_t repeat = 0;
ctx.write_cnt = 0;
ctx.read_cnt = 0;
ctx.pbuf = spsc_pbuf_init(buffer, sizeof(buffer), 0);
ctx.capacity = spsc_pbuf_capacity(ctx.pbuf);
ztress_set_timeout(K_MSEC(STRESS_TIMEOUT_MS));
ZTRESS_EXECUTE(ZTRESS_THREAD(stress_claim_free, &ctx, repeat, 0, Z_TIMEOUT_TICKS(4)),
ZTRESS_THREAD(stress_alloc_commit, &ctx, repeat, 1000, Z_TIMEOUT_TICKS(4)));
ZTRESS_EXECUTE(ZTRESS_THREAD(stress_alloc_commit, &ctx, repeat, 0, Z_TIMEOUT_TICKS(4)),
ZTRESS_THREAD(stress_claim_free, &ctx, repeat, 1000, Z_TIMEOUT_TICKS(4)));
}
ZTEST_SUITE(test_spsc_pbuf, NULL, NULL, NULL, NULL, NULL);