/* * Copyright (c) 2023 Syntacore. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 */ #include "zephyr/ztest_test.h" #include #include #include #ifdef CONFIG_SCHED_CPU_MASK #define STACK_SIZE (8 * 1024) #define CORES_NUM CONFIG_MP_MAX_NUM_CPUS #define FAIRNESS_TEST_CYCLES_PER_CORE 1000 BUILD_ASSERT(CONFIG_MP_MAX_NUM_CPUS > 1); static K_THREAD_STACK_ARRAY_DEFINE(tstack, CORES_NUM, STACK_SIZE); static struct k_thread tthread[CORES_NUM]; static uint32_t spinlock_grabbed[CORES_NUM]; static atomic_t fairness_test_cycles; static struct k_spinlock lock; static atomic_t start_sync; static inline struct k_thread *get_thread(uint8_t core_id) { return &tthread[core_id]; } /** * @brief Execution thread which runs concurrently on each CPU in the system * * @param [in] arg1 - thread argument 1 * @param [in] arg2 - thread argument 2 * @param [in] arg3 - thread argument 3 */ static void test_thread(void *arg1, void *arg2, void *arg3) { int core_id = (uintptr_t)arg1; /* Synchronize all the cores as much as possible */ int key = arch_irq_lock(); atomic_dec(&start_sync); while (atomic_get(&start_sync) != 0) ; /* * Run the test: let the cores contend for the spinlock and * collect the spinlock acquisition statistics */ do { k_spinlock_key_t spinlock_key = k_spin_lock(&lock); if (atomic_get(&fairness_test_cycles) == 0) { k_spin_unlock(&lock, spinlock_key); arch_irq_unlock(key); return; } spinlock_grabbed[core_id]++; /* Imitate some work which takes time */ volatile uint32_t countdown = 10000; while (countdown--) ; atomic_dec(&fairness_test_cycles); k_spin_unlock(&lock, spinlock_key); } while (atomic_get(&fairness_test_cycles) != 0); arch_irq_unlock(key); } static void test_init(void) { memset(tthread, 0x00, sizeof(tthread)); memset(tstack, 0x00, sizeof(tstack)); atomic_set(&start_sync, CORES_NUM); atomic_set(&fairness_test_cycles, FAIRNESS_TEST_CYCLES_PER_CORE * CORES_NUM); for (uintptr_t core_id = 0; core_id < CORES_NUM; core_id++) { struct k_thread *thread = get_thread(core_id); k_thread_create(thread, tstack[core_id], STACK_SIZE, (k_thread_entry_t)test_thread, (void *)core_id, NULL, NULL, K_PRIO_COOP(10), 0, K_FOREVER); /* * Pin each thread to a particular CPU core. * The larger the core's memory access latency in comparison to the * other cores - the less chances to win a contention for the spinlock * this core will have in case the spinlock implementation doesn't * provide acquisition fairness. */ k_thread_cpu_pin(thread, core_id); } } /** * @brief Test spinlock acquisition fairness * * @details This test verifies a spinlock acquisition fairness in relation * to the cores contending for the spinlock. Memory access latency may * vary between the CPU cores, so that some CPUs reach the spinlock faster * than the others and depending on spinlock implementation may get * higher chance to win the contention for the spinlock than the other * cores, making them to starve. * This effect may become critical for some real-life platforms * (e.g. NUMA) resulting in performance loss or even a live-lock, * when a single CPU is continuously winning the contention. * This test ensures that the probability to win the contention for a * spinlock is evenly distributed between all of the contending cores. * * @ingroup kernel_spinlock_tests * * @see k_spin_lock(), k_spin_unlock() */ ZTEST(spinlock, test_spinlock_fairness) { test_init(); /* Launching all the threads */ for (uint8_t core_id = 0; core_id < CORES_NUM; core_id++) { struct k_thread *thread = get_thread(core_id); k_thread_start(thread); } /* Waiting for all the threads to complete */ for (uint8_t core_id = 0; core_id < CORES_NUM; core_id++) { struct k_thread *thread = get_thread(core_id); k_thread_join(thread, K_FOREVER); } /* Print statistics */ for (uint8_t core_id = 0; core_id < CORES_NUM; core_id++) { printk("CPU%u acquired spinlock %u times, expected %u\n", core_id, spinlock_grabbed[core_id], FAIRNESS_TEST_CYCLES_PER_CORE); } /* Verify spinlock acquisition fairness */ for (uint8_t core_id = 0; core_id < CORES_NUM; core_id++) { zassert_false(spinlock_grabbed[core_id] < FAIRNESS_TEST_CYCLES_PER_CORE, "CPU%d starved on a spinlock: acquired %u times, expected %u\n", core_id, spinlock_grabbed[core_id], FAIRNESS_TEST_CYCLES_PER_CORE); } } #endif /* CONFIG_SCHED_CPU_MASK */