| /* |
| * Copyright (c) 2021 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <ztest.h> |
| #include <sys/ring_buffer.h> |
| #include <sys/mutex.h> |
| #include <random/rand32.h> |
| |
| /** |
| * @defgroup lib_ringbuffer_tests Ringbuffer |
| * @ingroup all_tests |
| * @{ |
| * @} |
| */ |
| |
| #define STACKSIZE (512 + CONFIG_TEST_EXTRA_STACKSIZE) |
| |
| #define RINGBUFFER 256 |
| #define LENGTH 64 |
| #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); |
| static struct k_thread thread_high_data; |
| |
| static ZTEST_BMEM SYS_MUTEX_DEFINE(mutex); |
| RING_BUF_ITEM_DECLARE_SIZE(ringbuf, RINGBUFFER); |
| 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); |
| int ret = ring_buf_item_put(&ringbuf, TYPE, VALUE, |
| input, LENGTH); |
| zassert_equal(ret, 0, NULL); |
| sys_mutex_unlock(&mutex); |
| } |
| |
| static void data_read(uint32_t *output) |
| { |
| uint16_t type; |
| uint8_t value, size32 = LENGTH; |
| int ret; |
| |
| sys_mutex_lock(&mutex, K_FOREVER); |
| ret = ring_buf_item_get(&ringbuf, &type, &value, output, &size32); |
| sys_mutex_unlock(&mutex); |
| |
| zassert_equal(ret, 0, NULL); |
| zassert_equal(type, TYPE, NULL); |
| zassert_equal(value, VALUE, NULL); |
| zassert_equal(size32, LENGTH, NULL); |
| if (output[0] == 1) { |
| zassert_equal(memcmp(output, databuffer1, size32), 0, NULL); |
| } else { |
| zassert_equal(memcmp(output, databuffer2, size32), 0, NULL); |
| } |
| } |
| |
| static void thread_entry_t1(void *p1, void *p2, void *p3) |
| { |
| for (int i = 0; i < LENGTH; i++) { |
| databuffer1[i] = 1; |
| } |
| |
| /* Try to write data into the ringbuffer */ |
| data_write(databuffer1); |
| /* Try to get data from the ringbuffer and check */ |
| data_read(output); |
| } |
| |
| static void thread_entry_t2(void *p1, void *p2, void *p3) |
| { |
| for (int i = 0; i < LENGTH; i++) { |
| databuffer2[i] = 2; |
| } |
| /* Try to write data into the ringbuffer */ |
| data_write(databuffer2); |
| /* Try to get data from the ringbuffer and check */ |
| data_read(output); |
| } |
| |
| /** |
| * @brief Test that prevent concurrent writing |
| * operations by using a mutex |
| * |
| * @details Define a ring buffer and a mutex, |
| * and then spawn two threads to read and |
| * write the same buffer at the same time to |
| * check the integrity of data reading and writing. |
| * |
| * @ingroup lib_ringbuffer_tests |
| */ |
| void test_ringbuffer_concurrent(void) |
| { |
| int old_prio = k_thread_priority_get(k_current_get()); |
| int prio = 10; |
| |
| k_thread_priority_set(k_current_get(), prio); |
| |
| k_thread_create(&thread_high_data, thread_high_stack, STACKSIZE, |
| thread_entry_t1, |
| NULL, NULL, NULL, |
| prio + 2, 0, K_NO_WAIT); |
| k_thread_create(&thread_low_data, thread_low_stack, STACKSIZE, |
| thread_entry_t2, |
| NULL, NULL, NULL, |
| prio + 2, 0, K_NO_WAIT); |
| k_sleep(K_MSEC(10)); |
| |
| /* Wait for thread exiting */ |
| k_thread_join(&thread_low_data, K_FOREVER); |
| k_thread_join(&thread_high_data, K_FOREVER); |
| |
| |
| /* 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 = 6000; |
| 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); |
| |
| preempt_cnt = 0; |
| 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); |
| /* Test is tailored for qemu_x86 to generate enough number of preemptions |
| * to validate that ring buffer is safe to be used without any locks in |
| * single producer single consumer scenario. |
| */ |
| if (IS_ENABLED(CONFIG_BOARD_QEMU_X86)) { |
| 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]; |
| |
| if (CONFIG_SYS_CLOCK_TICKS_PER_SEC < 100000) { |
| 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); |
| } |