/* * Copyright (c) 2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #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_rand8_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); } zassert_ok(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_rand16_get() % (ctx->capacity / 4)); int rpt = (sys_rand8_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_rand8_get() % 0x3; for (int i = 0; i < rpt; i++) { len = spsc_pbuf_claim(ctx->pbuf, &buf); if (len == 0) { return true; } zassert_ok(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 rnd = sys_rand16_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);