| /* |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file |
| * |
| * @brief Offload to the Kernel workqueue |
| * |
| * This test verifies that the kernel workqueue operates as |
| * expected. |
| * |
| * This test has two threads that increment a counter. The routine that |
| * increments the counter is invoked from workqueue due to the two threads |
| * calling using it. The final result of the counter is expected |
| * to be the the number of times work item was called to increment |
| * the counter. |
| * |
| * This is done with time slicing both disabled and enabled to ensure that the |
| * result always matches the number of times the workqueue is called. |
| * |
| * @{ |
| * @} |
| */ |
| #include <zephyr/zephyr.h> |
| #include <zephyr/linker/sections.h> |
| #include <zephyr/ztest.h> |
| |
| #define NUM_MILLISECONDS 50 |
| #define TEST_TIMEOUT 200 |
| |
| #ifdef CONFIG_COVERAGE |
| #define OFFLOAD_WORKQUEUE_STACK_SIZE 4096 |
| #else |
| #define OFFLOAD_WORKQUEUE_STACK_SIZE 1024 |
| #endif |
| |
| |
| static uint32_t critical_var; |
| static uint32_t alt_thread_iterations; |
| |
| static struct k_work_q offload_work_q; |
| static K_THREAD_STACK_DEFINE(offload_work_q_stack, |
| OFFLOAD_WORKQUEUE_STACK_SIZE); |
| |
| #define STACK_SIZE (1024 + CONFIG_TEST_EXTRA_STACK_SIZE) |
| |
| static K_THREAD_STACK_DEFINE(stack1, STACK_SIZE); |
| static K_THREAD_STACK_DEFINE(stack2, STACK_SIZE); |
| |
| static struct k_thread thread1; |
| static struct k_thread thread2; |
| |
| K_SEM_DEFINE(ALT_SEM, 0, UINT_MAX); |
| K_SEM_DEFINE(REGRESS_SEM, 0, UINT_MAX); |
| K_SEM_DEFINE(TEST_SEM, 0, UINT_MAX); |
| |
| /** |
| * @brief Routine to be called from a workqueue |
| * |
| * This routine increments the global variable @a critical_var. |
| */ |
| void critical_rtn(struct k_work *unused) |
| { |
| volatile uint32_t x; |
| |
| ARG_UNUSED(unused); |
| |
| x = critical_var; |
| critical_var = x + 1; |
| } |
| |
| /** |
| * @brief Common code for invoking work |
| * |
| * @param tag text identifying the invocation context |
| * @param count number of critical section calls made thus far |
| * |
| * @return number of critical section calls made by a thread |
| */ |
| uint32_t critical_loop(const char *tag, uint32_t count) |
| { |
| int64_t now; |
| int64_t last; |
| int64_t mseconds; |
| |
| last = mseconds = k_uptime_get(); |
| TC_PRINT("Start %s at %u\n", tag, (uint32_t)last); |
| while (((now = k_uptime_get())) < mseconds + NUM_MILLISECONDS) { |
| struct k_work work_item; |
| |
| if (now < last) { |
| TC_PRINT("Time went backwards: %u < %u\n", |
| (uint32_t)now, (uint32_t)last); |
| } |
| last = now; |
| |
| k_work_init(&work_item, critical_rtn); |
| k_work_submit_to_queue(&offload_work_q, &work_item); |
| count++; |
| #if defined(CONFIG_ARCH_POSIX) |
| k_busy_wait(50); |
| /* |
| * For the POSIX arch this loop and critical_rtn would otherwise |
| * run in 0 time and therefore would never finish. |
| * => We purposely waste 50us per loop |
| */ |
| #endif |
| } |
| TC_PRINT("End %s at %u\n", tag, (uint32_t)now); |
| |
| return count; |
| } |
| |
| /** |
| * @brief Alternate thread |
| * |
| * This routine invokes the workqueue many times. |
| */ |
| void alternate_thread(void *arg1, void *arg2, void *arg3) |
| { |
| ARG_UNUSED(arg1); |
| ARG_UNUSED(arg2); |
| ARG_UNUSED(arg3); |
| |
| k_sem_take(&ALT_SEM, K_FOREVER); /* Wait to be activated */ |
| |
| alt_thread_iterations = critical_loop("alt1", alt_thread_iterations); |
| |
| k_sem_give(®RESS_SEM); |
| |
| k_sem_take(&ALT_SEM, K_FOREVER); /* Wait to be re-activated */ |
| |
| alt_thread_iterations = critical_loop("alt2", alt_thread_iterations); |
| |
| k_sem_give(®RESS_SEM); |
| } |
| |
| /** |
| * @brief Regression thread |
| * |
| * This routine invokes the workqueue many times. It also checks to |
| * ensure that the number of times it is called matches the global variable |
| * @a critical_var. |
| */ |
| |
| void regression_thread(void *arg1, void *arg2, void *arg3) |
| { |
| uint32_t ncalls = 0U; |
| |
| ARG_UNUSED(arg1); |
| ARG_UNUSED(arg2); |
| ARG_UNUSED(arg3); |
| |
| k_sem_give(&ALT_SEM); /* Activate alternate_thread() */ |
| |
| ncalls = critical_loop("reg1", ncalls); |
| |
| /* Wait for alternate_thread() to complete */ |
| zassert_true(k_sem_take(®RESS_SEM, K_MSEC(TEST_TIMEOUT)) == 0, |
| "Timed out waiting for REGRESS_SEM"); |
| |
| zassert_equal(critical_var, ncalls + alt_thread_iterations, |
| "Unexpected value for <critical_var>"); |
| |
| TC_PRINT("Enable timeslicing at %u\n", k_uptime_get_32()); |
| k_sched_time_slice_set(20, 10); |
| |
| k_sem_give(&ALT_SEM); /* Re-activate alternate_thread() */ |
| |
| ncalls = critical_loop("reg2", ncalls); |
| |
| /* Wait for alternate_thread() to finish */ |
| zassert_true(k_sem_take(®RESS_SEM, K_MSEC(TEST_TIMEOUT)) == 0, |
| "Timed out waiting for REGRESS_SEM"); |
| |
| zassert_equal(critical_var, ncalls + alt_thread_iterations, |
| "Unexpected value for <critical_var>"); |
| |
| k_sem_give(&TEST_SEM); |
| |
| } |
| |
| /** |
| * @brief Verify thread context |
| * |
| * @details Check whether variable value per-thread is saved |
| * during context switch |
| * |
| * @ingroup kernel_workqueue_tests |
| */ |
| void test_offload_workqueue(void) |
| { |
| critical_var = 0U; |
| alt_thread_iterations = 0U; |
| |
| k_work_queue_start(&offload_work_q, |
| offload_work_q_stack, |
| K_THREAD_STACK_SIZEOF(offload_work_q_stack), |
| CONFIG_MAIN_THREAD_PRIORITY, NULL); |
| |
| k_thread_create(&thread1, stack1, STACK_SIZE, |
| alternate_thread, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(12), 0, K_NO_WAIT); |
| |
| k_thread_create(&thread2, stack2, STACK_SIZE, |
| regression_thread, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(12), 0, K_NO_WAIT); |
| |
| zassert_true(k_sem_take(&TEST_SEM, K_MSEC(TEST_TIMEOUT * 2)) == 0, |
| "Timed out waiting for TEST_SEM"); |
| } |
| |
| void test_main(void) |
| { |
| ztest_test_suite(kernel_offload_wq, |
| ztest_1cpu_unit_test(test_offload_workqueue) |
| ); |
| ztest_run_test_suite(kernel_offload_wq); |
| } |