From 87e60fac2a75d77cfed1a5ada065fc09c136fc66 Mon Sep 17 00:00:00 2001 From: Krzysztof Chruscinski Date: Fri, 30 Jul 2021 08:18:36 +0200 Subject: [PATCH] tests: lib: ringbuffer: Add tests for preemption Added test cases that validates if ring buffer handles correctly case with single producer single consumer from different contexts. Signed-off-by: Krzysztof Chruscinski --- tests/lib/ringbuffer/prj.conf | 2 + tests/lib/ringbuffer/src/concurrent.c | 356 ++++++++++++++++++++++++++ tests/lib/ringbuffer/src/main.c | 19 +- tests/lib/ringbuffer/testcase.yaml | 2 +- 4 files changed, 376 insertions(+), 3 deletions(-) diff --git a/tests/lib/ringbuffer/prj.conf b/tests/lib/ringbuffer/prj.conf index ca9eb30324..e07a04cbb4 100644 --- a/tests/lib/ringbuffer/prj.conf +++ b/tests/lib/ringbuffer/prj.conf @@ -1,3 +1,5 @@ CONFIG_ZTEST=y CONFIG_IRQ_OFFLOAD=y CONFIG_RING_BUFFER=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_SYS_CLOCK_TICKS_PER_SEC=100000 diff --git a/tests/lib/ringbuffer/src/concurrent.c b/tests/lib/ringbuffer/src/concurrent.c index af2022efb7..6cb143a3f7 100644 --- a/tests/lib/ringbuffer/src/concurrent.c +++ b/tests/lib/ringbuffer/src/concurrent.c @@ -6,6 +6,7 @@ #include #include #include +#include /** * @defgroup lib_ringbuffer_tests Ringbuffer @@ -21,6 +22,10 @@ #define VALUE 0xb #define TYPE 0xc +#define RINGBUFFER_API_ITEM 0 +#define RINGBUFFER_API_CPY 1 +#define RINGBUFFER_API_NOCPY 2 + static K_THREAD_STACK_DEFINE(thread_low_stack, STACKSIZE); static struct k_thread thread_low_data; static K_THREAD_STACK_DEFINE(thread_high_stack, STACKSIZE); @@ -32,6 +37,14 @@ static uint32_t output[LENGTH]; static uint32_t databuffer1[LENGTH]; static uint32_t databuffer2[LENGTH]; +static volatile int preempt_cnt; +static volatile bool in_task; + +typedef void (*test_ringbuf_action_t)(struct ring_buf *rbuf, bool reset); +static test_ringbuf_action_t produce_fn; +static test_ringbuf_action_t consume_fn; +volatile int test_microdelay_cnt; + static void data_write(uint32_t *input) { sys_mutex_lock(&mutex, K_FOREVER); @@ -121,3 +134,346 @@ void test_ringbuffer_concurrent(void) /* Revert priority of the main thread */ k_thread_priority_set(k_current_get(), old_prio); } + +static void produce_cpy(struct ring_buf *rbuf, bool reset) +{ + static int cnt; + uint8_t buf[3]; + uint32_t len; + + if (reset) { + cnt = 0; + return; + } + + for (int i = 0; i < sizeof(buf); i++) { + buf[i] = (uint8_t)cnt++; + } + + len = ring_buf_put(rbuf, buf, sizeof(buf)); + cnt -= (sizeof(buf) - len); +} + +static void consume_cpy(struct ring_buf *rbuf, bool reset) +{ + static int cnt; + uint8_t buf[3]; + uint32_t len; + + if (reset) { + cnt = 0; + return; + } + + len = ring_buf_get(rbuf, buf, sizeof(buf)); + for (int i = 0; i < len; i++) { + zassert_equal(buf[i], (uint8_t)cnt, NULL); + cnt++; + } +} + +static void produce_item(struct ring_buf *rbuf, bool reset) +{ + int err; + static uint16_t cnt; + uint32_t buf[2]; + + if (reset) { + cnt = 0; + return; + } + + err = ring_buf_item_put(rbuf, cnt++, VALUE, buf, 2); + (void)err; +} + +static void consume_item(struct ring_buf *rbuf, bool reset) +{ + int err; + static uint16_t cnt; + uint32_t data[2]; + uint16_t type; + uint8_t value; + uint8_t size32 = ARRAY_SIZE(data); + + if (reset) { + cnt = 0; + return; + } + + err = ring_buf_item_get(rbuf, &type, &value, data, &size32); + if (err == 0) { + zassert_equal(type, cnt++, NULL); + zassert_equal(value, VALUE, NULL); + } else if (err == -EMSGSIZE) { + zassert_true(false, NULL); + } +} + +static void produce(struct ring_buf *rbuf, bool reset) +{ + static int cnt; + static int wr = 8; + uint32_t len; + uint8_t *data; + + if (reset) { + cnt = 0; + return; + } + + len = ring_buf_put_claim(rbuf, &data, wr); + if (len == 0) { + len = ring_buf_put_claim(rbuf, &data, wr); + } + + if (len == 0) { + return; + } + + for (uint32_t i = 0; i < len; i++) { + data[i] = cnt++; + } + + wr++; + if (wr == 15) { + wr = 8; + } + + int err = ring_buf_put_finish(rbuf, len); + + zassert_equal(err, 0, "cnt: %d", cnt); +} + +static void consume(struct ring_buf *rbuf, bool reset) +{ + static int rd = 8; + static int cnt; + uint32_t len; + uint8_t *data; + + if (reset) { + cnt = 0; + return; + } + + len = ring_buf_get_claim(rbuf, &data, rd); + if (len == 0) { + len = ring_buf_get_claim(rbuf, &data, rd); + } + + if (len == 0) { + return; + } + + for (uint32_t i = 0; i < len; i++) { + zassert_equal(data[i], (uint8_t)cnt, + "Got %02x, exp: %02x", data[i], (uint8_t)cnt); + cnt++; + } + + rd++; + if (rd == 15) { + rd = 8; + } + + int err = ring_buf_get_finish(rbuf, len); + + zassert_equal(err, 0, NULL); +} + + +static void produce_timeout(struct k_timer *timer) +{ + struct ring_buf *rbuf = k_timer_user_data_get(timer); + + if (in_task) { + preempt_cnt++; + } + + produce_fn(rbuf, false); +} + +static void consume_timeout(struct k_timer *timer) +{ + struct ring_buf *rbuf = k_timer_user_data_get(timer); + + if (in_task) { + preempt_cnt++; + } + + consume_fn(rbuf, false); +} + +static void microdelay(int delay) +{ + for (int i = 0; i < delay; i++) { + test_microdelay_cnt++; + } +} + +/* Test is running 2 parts of ring buffer operations (producing, consuming) in + * two different contexts. One is the thread context and second is k_timer + * timeout interrupt which can preempt thread. The goal of this test is to + * provoke cases when one operation is preempted by another at multiple locations. + * It is achieved by starting a timer and then busywaiting for similar time + * before starting an operation in the thread context. Number of thread context + * preemptions is counted and test is considered valid if certain amount of + * preemptions occurred. + * + * Ring buffer claims that it is thread safe and requires no additional locking + * in single producer, single consumer case and this test aims to prove that. + * + * Depending on input parameter @p p2 thread context is used for producing or + * consuming. + */ +static void thread_entry_spsc(void *p1, void *p2, void *p3) +{ + struct ring_buf *rbuf = p1; + uint32_t timeout = 5000; + bool high_producer = (bool)p2; + uint32_t start = k_uptime_get_32(); + struct k_timer timer; + int i = 0; + int backoff_us = MAX(100, 3 * (1000000 / CONFIG_SYS_CLOCK_TICKS_PER_SEC)); + k_timeout_t t = K_USEC(backoff_us); + + k_timer_init(&timer, + high_producer ? produce_timeout : consume_timeout, + NULL); + k_timer_user_data_set(&timer, rbuf); + + consume_fn(rbuf, true); + produce_fn(rbuf, true); + + while (k_uptime_get_32() < (start + timeout)) { + int r = sys_rand32_get() % 200; + + k_timer_start(&timer, t, K_NO_WAIT); + k_busy_wait(backoff_us - 50 + i); + microdelay(r); + + in_task = true; + if (high_producer) { + consume_fn(rbuf, false); + } else { + produce_fn(rbuf, false); + } + in_task = false; + + i++; + if (i > 60) { + i = 0; + } + + k_timer_status_sync(&timer); + } + + PRINT("preempted: %d\n", preempt_cnt); + zassert_true(preempt_cnt > 1500, "If thread operation was not preempted " + "multiple times then we cannot have confidance that it " + "validated the module properly. Platform should not be " + "used in that case"); +} + +extern uint32_t test_rewind_threshold; + +/* Single producer, single consumer test */ +static void test_ringbuffer_spsc(bool higher_producer, int api_type) +{ + int old_prio = k_thread_priority_get(k_current_get()); + int prio = 10; + uint32_t old_rewind_threshold = test_rewind_threshold; + uint8_t buf[32]; + uint32_t buf32[32]; + + /* Native posix cannot have sys tick high enough to run this stress test. */ + if (IS_ENABLED(CONFIG_BOARD_NATIVE_POSIX)) { + ztest_test_skip(); + } + + test_rewind_threshold = 64; + + switch (api_type) { + case RINGBUFFER_API_ITEM: + ring_buf_init(&ringbuf, ARRAY_SIZE(buf32), buf32); + consume_fn = consume_item; + produce_fn = produce_item; + break; + case RINGBUFFER_API_NOCPY: + ring_buf_init(&ringbuf, ARRAY_SIZE(buf), buf); + consume_fn = consume; + produce_fn = produce; + break; + case RINGBUFFER_API_CPY: + ring_buf_init(&ringbuf, ARRAY_SIZE(buf), buf); + consume_fn = consume_cpy; + produce_fn = produce_cpy; + break; + default: + zassert_true(false, NULL); + } + + k_thread_priority_set(k_current_get(), prio); + + k_thread_create(&thread_high_data, thread_high_stack, STACKSIZE, + thread_entry_spsc, + &ringbuf, (void *)higher_producer, NULL, + prio + 1, 0, K_NO_WAIT); + k_sleep(K_MSEC(10)); + + /* Wait for thread exiting */ + k_thread_join(&thread_high_data, K_FOREVER); + + + /* Revert priority of the main thread */ + k_thread_priority_set(k_current_get(), old_prio); + test_rewind_threshold = old_rewind_threshold; +} + +/* Zero-copy API. Test is validating single producer, single consumer where + * producer has higher priority context which can preempt consumer. + */ +void test_ringbuffer_shpsc(void) +{ + test_ringbuffer_spsc(true, RINGBUFFER_API_NOCPY); +} + +/* Zero-copy API. Test is validating single producer, single consumer where + * consumer has higher priority context which can preempt producer. + */ +void test_ringbuffer_spshc(void) +{ + test_ringbuffer_spsc(false, RINGBUFFER_API_NOCPY); +} + +/* Copy API. Test is validating single producer, single consumer where + * producer has higher priority context which can preempt consumer. + */ +void test_ringbuffer_cpy_shpsc(void) +{ + test_ringbuffer_spsc(true, RINGBUFFER_API_CPY); +} + +/* Copy API. Test is validating single producer, single consumer where + * consumer has higher priority context which can preempt producer. + */ +void test_ringbuffer_cpy_spshc(void) +{ + test_ringbuffer_spsc(false, RINGBUFFER_API_CPY); +} +/* Item API. Test is validating single producer, single consumer where producer + * has higher priority context which can preempt consumer. + */ +void test_ringbuffer_item_shpsc(void) +{ + test_ringbuffer_spsc(true, RINGBUFFER_API_ITEM); +} + +/* Item API. Test is validating single producer, single consumer where consumer + * has higher priority context which can preempt producer. + */ +void test_ringbuffer_item_spshc(void) +{ + test_ringbuffer_spsc(false, RINGBUFFER_API_ITEM); +} diff --git a/tests/lib/ringbuffer/src/main.c b/tests/lib/ringbuffer/src/main.c index c65c87aeb5..62aebeac78 100644 --- a/tests/lib/ringbuffer/src/main.c +++ b/tests/lib/ringbuffer/src/main.c @@ -27,9 +27,12 @@ LOG_MODULE_REGISTER(test); #undef RING_BUFFER_MAX_SIZE #define RING_BUFFER_MAX_SIZE 0x00000200 +/* Use global variable that can be modified by the test. */ +uint32_t test_rewind_threshold = RING_BUFFER_MAX_SIZE; + uint32_t ring_buf_get_rewind_threshold(void) { - return RING_BUFFER_MAX_SIZE; + return test_rewind_threshold; } /** @@ -50,6 +53,12 @@ RING_BUF_ITEM_DECLARE_POW2(ring_buf1, 8); #define DATA_MAX_SIZE 3 #define POW 2 extern void test_ringbuffer_concurrent(void); +extern void test_ringbuffer_shpsc(void); +extern void test_ringbuffer_spshc(void); +extern void test_ringbuffer_cpy_shpsc(void); +extern void test_ringbuffer_cpy_spshc(void); +extern void test_ringbuffer_item_shpsc(void); +extern void test_ringbuffer_item_spshc(void); /** * @brief Test APIs of ring buffer * @@ -1048,7 +1057,13 @@ void test_main(void) ztest_unit_test(test_peek), ztest_unit_test(test_reset), ztest_unit_test(test_ringbuffer_performance), - ztest_unit_test(test_ringbuffer_concurrent) + ztest_unit_test(test_ringbuffer_concurrent), + ztest_unit_test(test_ringbuffer_shpsc), + ztest_unit_test(test_ringbuffer_spshc), + ztest_unit_test(test_ringbuffer_cpy_shpsc), + ztest_unit_test(test_ringbuffer_cpy_spshc), + ztest_unit_test(test_ringbuffer_item_shpsc), + ztest_unit_test(test_ringbuffer_item_spshc) ); ztest_run_test_suite(test_ringbuffer_api); } diff --git a/tests/lib/ringbuffer/testcase.yaml b/tests/lib/ringbuffer/testcase.yaml index c01e403857..329a5c7d8f 100644 --- a/tests/lib/ringbuffer/testcase.yaml +++ b/tests/lib/ringbuffer/testcase.yaml @@ -2,4 +2,4 @@ tests: libraries.data_structures: tags: ring_buffer circular_buffer integration_platforms: - - native_posix + - qemu_x86