tests: lib: spsc_pbuf: Add tests for zero copy API

Extend test suite to validate zero copy API.
Add stress tests.
Add support for cache in the test. Test is not emulating cache
but ensures that buffer works correctly with cache enabled.

Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
Krzysztof Chruscinski 2022-05-19 09:27:28 +02:00 committed by Carles Cufí
commit 1986591beb
3 changed files with 449 additions and 31 deletions

View file

@ -1,3 +1,4 @@
CONFIG_ZTEST=y
CONFIG_ZTEST_NEW_API=y
CONFIG_ZTRESS=y
CONFIG_SPSC_PBUF=y

View file

@ -5,33 +5,47 @@
*/
#include <ztest.h>
#include <ztress.h>
#include <zephyr/sys/spsc_pbuf.h>
#include <zephyr/random/rand32.h>
/* 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 uint8_t memory_area[216] __aligned(4);
ZTEST(test_spsc_pbuf, test_spsc_pbuf_ut)
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(CONFIG_SPSC_PBUF_CACHE_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);
ib = spsc_pbuf_init(memory_area, sizeof(memory_area), 0);
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(ib->len, (sizeof(memory_area) - sizeof(*ib)), NULL);
zassert_equal(ib->wr_idx, 0, NULL);
zassert_equal(ib->rd_idx, 0, NULL);
zassert_equal(spsc_pbuf_capacity(ib), capacity, NULL);
/* Try writing invalid value. */
rlen = spsc_pbuf_write(ib, rbuf, 0);
zassert_equal(rlen, -EINVAL, NULL);
rlen = spsc_pbuf_write(ib, rbuf, SPSC_PBUF_MAX_LEN);
zassert_equal(rlen, -EINVAL, NULL);
/* Try to write more than buffer can store. */
rlen = spsc_pbuf_write(ib, rbuf, sizeof(rbuf));
zassert_equal(rlen, -ENOMEM, NULL);
zassert_equal(ib->wr_idx, 0, NULL);
zassert_equal(ib->rd_idx, 0, NULL);
/* Read empty buffer. */
rlen = spsc_pbuf_read(ib, rbuf, sizeof(rbuf));
@ -40,30 +54,16 @@ ZTEST(test_spsc_pbuf, test_spsc_pbuf_ut)
/* Single write and read. */
wlen = spsc_pbuf_write(ib, message, sizeof(message));
zassert_equal(wlen, sizeof(message), NULL);
zassert_equal(ib->wr_idx, (sizeof(message) + sizeof(uint16_t)), NULL);
zassert_equal(ib->rd_idx, 0, NULL);
rlen = spsc_pbuf_read(ib, rbuf, sizeof(rbuf));
zassert_equal(rlen, sizeof(message), NULL);
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 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.
*/
zassert_equal(SPSC_PBUF_CAPACITY(sizeof(memory_area)), 200, NULL);
ib = spsc_pbuf_init(memory_area, sizeof(memory_area), 0);
ib = spsc_pbuf_init(memory_area, sizeof(memory_area), flags);
zassert_equal_ptr(ib, memory_area, NULL);
zassert_equal(ib->len, (sizeof(memory_area) - sizeof(*ib)), NULL);
zassert_equal(ib->wr_idx, 0, NULL);
zassert_equal(ib->rd_idx, 0, NULL);
for (size_t i = 0; i < 9; i++) {
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), NULL);
}
@ -80,14 +80,14 @@ ZTEST(test_spsc_pbuf, test_spsc_pbuf_ut)
zassert_equal(rlen, -ENOMEM, NULL);
/* Read whole data from the buffer. */
for (size_t i = 0; i < 9; i++) {
zassert_equal(ib->rd_idx, i * (sizeof(message) + sizeof(uint16_t)), NULL);
for (size_t i = 0; i < repeat; i++) {
wlen = spsc_pbuf_read(ib, rbuf, sizeof(rbuf));
zassert_equal(wlen, sizeof(message), NULL);
}
zassert_equal(ib->wr_idx, 9 * (sizeof(message) + sizeof(uint16_t)), NULL);
zassert_equal(ib->rd_idx, 9 * (sizeof(message) + sizeof(uint16_t)), NULL);
/* Buffer is empty */
rlen = spsc_pbuf_read(ib, NULL, 0);
zassert_equal(rlen, 0, NULL);
/* Write message that would be wrapped around. */
wlen = spsc_pbuf_write(ib, message, sizeof(message));
@ -99,4 +99,398 @@ ZTEST(test_spsc_pbuf, test_spsc_pbuf_ut)
zassert_equal(message[0], 'a', NULL);
}
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[64] __aligned(MAX(CONFIG_SPSC_PBUF_CACHE_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 - ROUND_UP(len1, sizeof(uint32_t)) - 2 * sizeof(uint32_t);
/* 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[64] __aligned(MAX(CONFIG_SPSC_PBUF_CACHE_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[96] __aligned(MAX(CONFIG_SPSC_PBUF_CACHE_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 - len1 - sizeof(uint32_t) - 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[64] __aligned(MAX(CONFIG_SPSC_PBUF_CACHE_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 - ROUND_UP(len1, sizeof(uint32_t)) - 2 * sizeof(uint32_t);
zassert_equal(len2, exp_len2, "got %d, exp: %d", len2, exp_len2);
len = spsc_pbuf_claim(pb, &buf);
zassert_equal(len1, len, NULL);
spsc_pbuf_free(pb, len);
spsc_pbuf_commit(pb, len2);
len = spsc_pbuf_claim(pb, &buf);
zassert_equal(len2, len, NULL);
spsc_pbuf_free(pb, len);
}
ZTEST(test_spsc_pbuf, test_0cpy_corner2)
{
static uint8_t buffer[64] __aligned(MAX(CONFIG_SPSC_PBUF_CACHE_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 5 byte packet. */
len1 = 10;
PACKET_WRITE(pb, len1, len1, 0, len1);
/* Attempt to allocate packet that will leave 5 bytes at the end. */
len2 = capacity - ROUND_UP(len1, sizeof(uint32_t)) - 2 * sizeof(uint16_t) - 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 - ROUND_UP(len1, sizeof(uint32_t)) - 2 * sizeof(uint32_t);
PACKET_WRITE(pb, capacity, 0, 2, exp_len);
}
ZTEST(test_spsc_pbuf, test_largest_alloc)
{
static uint8_t buffer[96] __aligned(MAX(CONFIG_SPSC_PBUF_CACHE_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 - ROUND_UP(len1, sizeof(uint32_t)) - 2 * sizeof(uint32_t) - 10;
PACKET_WRITE(pb, len2, len2, 1, len2);
PACKET_WRITE(pb, SPSC_PBUF_MAX_LEN, 0, 1, 8);
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 - ROUND_UP(len1, sizeof(uint32_t)) - 2 * sizeof(uint16_t) - 8;
PACKET_WRITE(pb, len2, len2, 1, len2);
PACKET_WRITE(pb, SPSC_PBUF_MAX_LEN - 1, 0, 1, 12);
}
struct stress_data {
struct spsc_pbuf *pbuf;
uint32_t capacity;
uint32_t write_cnt;
uint32_t read_cnt;
};
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;
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));
zassert_true(len < sizeof(buf), "len:%d %d", len, ctx->capacity);
memset(buf, (uint8_t)ctx->write_cnt, len);
if (spsc_pbuf_write(ctx->pbuf, buf, len) == 0) {
ctx->write_cnt++;
}
return true;
}
ZTEST(test_spsc_pbuf, test_stress)
{
static uint8_t buffer[128] __aligned(MAX(CONFIG_SPSC_PBUF_CACHE_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(5000));
ZTRESS_EXECUTE(ZTRESS_THREAD(stress_read, &ctx, repeat, 0, Z_TIMEOUT_TICKS(4)),
ZTRESS_THREAD(stress_write, &ctx, repeat, 1000, Z_TIMEOUT_TICKS(4)));
ZTRESS_EXECUTE(ZTRESS_THREAD(stress_write, &ctx, repeat, 0, Z_TIMEOUT_TICKS(4)),
ZTRESS_THREAD(stress_read, &ctx, repeat, 1000, Z_TIMEOUT_TICKS(4)));
}
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;
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;
uint16_t len = 1 + (sys_rand32_get() % (ctx->capacity / 4));
char *buf;
int err;
err = spsc_pbuf_alloc(ctx->pbuf, len, &buf);
zassert_true(err >= 0, NULL);
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(CONFIG_SPSC_PBUF_CACHE_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(5000));
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);

View file

@ -4,3 +4,26 @@ tests:
- native_posix
# Exclude platform which does not link with cache functions
platform_exclude: ast1030_evb
lib.spsc_pbuf_cache:
integration_platforms:
- native_posix
# Exclude platform which does not link with cache functions
platform_exclude: ast1030_evb
extra_configs:
- CONFIG_SPSC_PBUF_CACHE_ALWAYS=y
lib.spsc_pbuf_nocache:
integration_platforms:
- native_posix
# Exclude platform which does not link with cache functions
platform_exclude: ast1030_evb
extra_configs:
- CONFIG_SPSC_PBUF_CACHE_NEVER=y
lib.spsc_pbuf_stress:
platform_allow: qemu_x86
extra_configs:
- CONFIG_SYS_CLOCK_TICKS_PER_SEC=100000
integration_platforms:
- qemu_x86