blob: adf7e934060c5099749c6e1bf3d86147bd7a1a82 [file]
/*
* Copyright (c) 2015 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#include <zephyr/sys/atomic.h>
/* convenience macro - return either 64-bit or 32-bit value */
#define ATOMIC_WORD(val_if_64, val_if_32) \
((atomic_t)((sizeof(void *) == sizeof(uint64_t)) ? (val_if_64) : (val_if_32)))
/* an example of the number of atomic bit in an array */
#define NUM_FLAG_BITS 100
/* set test_cycle 1000us * 20 = 20ms */
#define TEST_CYCLE 20
#define THREADS_NUM 2
#define STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
static K_THREAD_STACK_ARRAY_DEFINE(stack, THREADS_NUM, STACK_SIZE);
static struct k_thread thread[THREADS_NUM];
atomic_t total_atomic;
/**
* @defgroup kernel_atomic_ops_tests Atomic Operations
* @ingroup all_tests
* @{
* @}
*
* @addtogroup kernel_atomic_ops_tests
* @{
*/
/**
* @brief Verify each atomic operation API mutates and returns the expected value.
*
* @ingroup kernel_atomic_ops_tests
*
* @details
* Passing proves that every atomic primitive (compare-and-set, arithmetic,
* load/store, bitwise, and single-bit operations on both scalar and pointer
* targets, plus the ATOMIC_DEFINE bit-array) performs the documented
* read-modify-write semantics: each call returns the prior value of the
* target and leaves the target updated to the expected result, including for
* negative operands and per-bit operations across the full atomic word width.
*
* Test steps:
* - Invoke each atomic API in turn against a known target value.
* - For arithmetic operations, exercise both positive and negative operands.
* - For bit operations, iterate over every bit position of the atomic word.
* - For ATOMIC_DEFINE, set and clear each bit of a multi-word bit array.
*
* Expected result:
* - Each call returns the target's previous value and the target holds the
* expected post-operation value.
*
* @see atomic_cas(), atomic_add(), atomic_sub(),
* atomic_inc(), atomic_dec(), atomic_get(), atomic_set(),
* atomic_clear(), atomic_or(), atomic_and(), atomic_xor(),
* atomic_nand(), atomic_test_bit(), atomic_test_and_clear_bit(),
* atomic_test_and_set_bit(), atomic_clear_bit(), atomic_set_bit(),
* ATOMIC_DEFINE
*
*/
ZTEST_USER(atomic, test_atomic)
{
int i;
atomic_t target, orig;
atomic_ptr_t ptr_target;
atomic_val_t value;
atomic_val_t oldvalue;
void *ptr_value, *old_ptr_value;
ATOMIC_DEFINE(flag_bits, NUM_FLAG_BITS) = {0};
zassert_equal(sizeof(atomic_t), ATOMIC_WORD(sizeof(uint64_t), sizeof(uint32_t)),
"sizeof(atomic_t)");
target = 4;
value = 5;
oldvalue = 6;
/* atomic_cas() */
zassert_false(atomic_cas(&target, oldvalue, value), "atomic_cas");
target = 6;
zassert_true(atomic_cas(&target, oldvalue, value), "atomic_cas");
zassert_true((target == value), "atomic_cas");
/* atomic_ptr_cas() */
ptr_target = ATOMIC_PTR_INIT((void *)4);
ptr_value = (atomic_ptr_val_t)5;
old_ptr_value = (atomic_ptr_val_t)6;
zassert_false(atomic_ptr_cas(&ptr_target, old_ptr_value, ptr_value),
"atomic_ptr_cas");
ptr_target = (atomic_ptr_val_t)6;
zassert_true(atomic_ptr_cas(&ptr_target, old_ptr_value, ptr_value),
"atomic_ptr_cas");
zassert_true((ptr_target == ptr_value), "atomic_ptr_cas");
/* atomic_add() */
target = 1;
value = 2;
zassert_true((atomic_add(&target, value) == 1), "atomic_add");
zassert_true((target == 3), "atomic_add");
/* Test the atomic_add() function parameters can be negative */
target = 2;
value = -4;
zassert_true((atomic_add(&target, value) == 2), "atomic_add");
zassert_true((target == -2), "atomic_add");
/* atomic_sub() */
target = 10;
value = 2;
zassert_true((atomic_sub(&target, value) == 10), "atomic_sub");
zassert_true((target == 8), "atomic_sub");
/* Test the atomic_sub() function parameters can be negative */
target = 5;
value = -4;
zassert_true((atomic_sub(&target, value) == 5), "atomic_sub");
zassert_true((target == 9), "atomic_sub");
/* atomic_inc() */
target = 5;
zassert_true((atomic_inc(&target) == 5), "atomic_inc");
zassert_true((target == 6), "atomic_inc");
/* atomic_dec() */
target = 2;
zassert_true((atomic_dec(&target) == 2), "atomic_dec");
zassert_true((target == 1), "atomic_dec");
/* atomic_get() */
target = 50;
zassert_true((atomic_get(&target) == 50), "atomic_get");
/* atomic_ptr_get() */
ptr_target = ATOMIC_PTR_INIT((void *)50);
zassert_true((atomic_ptr_get(&ptr_target) == (atomic_ptr_val_t)50),
"atomic_ptr_get");
/* atomic_set() */
target = 42;
value = 77;
zassert_true((atomic_set(&target, value) == 42), "atomic_set");
zassert_true((target == value), "atomic_set");
/* atomic_ptr_set() */
ptr_target = ATOMIC_PTR_INIT((void *)42);
ptr_value = (atomic_ptr_val_t)77;
zassert_true((atomic_ptr_set(&ptr_target, ptr_value) == (atomic_ptr_val_t)42),
"atomic_ptr_set");
zassert_true((ptr_target == ptr_value), "atomic_ptr_set");
/* atomic_clear() */
target = 100;
zassert_true((atomic_clear(&target) == 100), "atomic_clear");
zassert_true((target == 0), "atomic_clear");
/* atomic_ptr_clear() */
ptr_target = ATOMIC_PTR_INIT((void *)100);
zassert_true((atomic_ptr_clear(&ptr_target) == (atomic_ptr_val_t)100),
"atomic_ptr_clear");
zassert_true((ptr_target == NULL), "atomic_ptr_clear");
/* atomic_or() */
target = 0xFF00;
value = 0x0F0F;
zassert_true((atomic_or(&target, value) == 0xFF00), "atomic_or");
zassert_true((target == 0xFF0F), "atomic_or");
/* atomic_xor() */
target = 0xFF00;
value = 0x0F0F;
zassert_true((atomic_xor(&target, value) == 0xFF00), "atomic_xor");
zassert_true((target == 0xF00F), "atomic_xor");
/* atomic_and() */
target = 0xFF00;
value = 0x0F0F;
zassert_true((atomic_and(&target, value) == 0xFF00), "atomic_and");
zassert_true((target == 0x0F00), "atomic_and");
/* atomic_nand() */
target = 0xFF00;
value = 0x0F0F;
zassert_true((atomic_nand(&target, value) == 0xFF00), "atomic_nand");
zassert_true((target == ATOMIC_WORD(0xFFFFFFFFFFFFF0FF, 0xFFFFF0FF)), "atomic_nand");
/* atomic_test_bit() */
for (i = 0; i < ATOMIC_BITS; i++) {
target = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
zassert_true(!!(atomic_test_bit(&target, i) == !!(target & BIT(i))),
"atomic_test_bit");
}
/* atomic_test_and_clear_bit() */
for (i = 0; i < ATOMIC_BITS; i++) {
orig = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
target = orig;
zassert_true(!!(atomic_test_and_clear_bit(&target, i)) == !!(orig & BIT(i)),
"atomic_test_and_clear_bit");
zassert_true(target == (orig & ~BIT(i)), "atomic_test_and_clear_bit");
}
/* atomic_test_and_set_bit() */
for (i = 0; i < ATOMIC_BITS; i++) {
orig = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
target = orig;
zassert_true(!!(atomic_test_and_set_bit(&target, i)) == !!(orig & BIT(i)),
"atomic_test_and_set_bit");
zassert_true(target == (orig | BIT(i)), "atomic_test_and_set_bit");
}
for (i = 0; i < ATOMIC_BITS; i++) {
orig = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
target = orig;
zassert_true(atomic_test_and_set_bit_to(&target, i, false) ==
(IS_BIT_SET(orig, i) != IS_BIT_SET(target, i)),
"atomic_test_and_set_bit_to");
zassert_true(target == (orig & ~BIT(i)), "atomic_test_and_set_bit_to");
}
for (i = 0; i < ATOMIC_BITS; i++) {
orig = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
target = orig;
zassert_true(atomic_test_and_set_bit_to(&target, i, true) ==
(IS_BIT_SET(orig, i) != IS_BIT_SET(target, i)),
"atomic_test_and_set_bit_to");
zassert_true(target == (orig | BIT(i)), "atomic_test_and_set_bit_to");
}
/* atomic_clear_bit() */
for (i = 0; i < ATOMIC_BITS; i++) {
orig = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
target = orig;
atomic_clear_bit(&target, i);
zassert_true(target == (orig & ~BIT(i)), "atomic_clear_bit");
}
/* atomic_set_bit() */
for (i = 0; i < ATOMIC_BITS; i++) {
orig = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
target = orig;
atomic_set_bit(&target, i);
zassert_true(target == (orig | BIT(i)), "atomic_set_bit");
}
/* atomic_set_bit_to(&target, i, false) */
for (i = 0; i < ATOMIC_BITS; i++) {
orig = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
target = orig;
atomic_set_bit_to(&target, i, false);
zassert_true(target == (orig & ~BIT(i)), "atomic_set_bit_to");
}
/* atomic_set_bit_to(&target, i, true) */
for (i = 0; i < ATOMIC_BITS; i++) {
orig = ATOMIC_WORD(0x0F0F0F0F0F0F0F0F, 0x0F0F0F0F);
target = orig;
atomic_set_bit_to(&target, i, true);
zassert_true(target == (orig | BIT(i)), "atomic_set_bit_to");
}
/* ATOMIC_DEFINE */
for (i = 0; i < NUM_FLAG_BITS; i++) {
atomic_set_bit(flag_bits, i);
zassert_true(!!atomic_test_bit(flag_bits, i) == !!(1),
"Failed to set a single bit in an array of atomic variables");
atomic_clear_bit(flag_bits, i);
zassert_true(!!atomic_test_bit(flag_bits, i) == !!(0),
"Failed to clear a single bit in an array of atomic variables");
}
}
/* This helper function will run more the one slice */
void atomic_handler(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
for (int i = 0; i < TEST_CYCLE; i++) {
atomic_inc(&total_atomic);
/* Do 1000us busywait to longer the handler execute time */
k_busy_wait(1000);
}
}
/**
* @brief Verify atomic increments from concurrent threads are not lost.
*
* @ingroup kernel_atomic_ops_tests
*
* @details
* Passing proves that atomic_inc() is safe under preemptive concurrency: when
* multiple equal-priority preemptible threads increment a shared atomic_t while
* being time-sliced in and out, no increment is lost to a torn read-modify-write,
* so the final value equals the exact total number of increments performed.
*
* Test steps:
* - Enable time slicing so equal-priority preemptible threads are scheduled fairly.
* - Start THREADS_NUM threads, each incrementing the shared atomic TEST_CYCLE times
* with a busy-wait between increments to force interleaving across slices.
* - Join all threads and disable time slicing.
*
* Expected result:
* - The shared atomic equals TEST_CYCLE * THREADS_NUM.
*
* @see atomic_inc()
*/
ZTEST(atomic, test_threads_access_atomic)
{
k_tid_t tid[THREADS_NUM];
/* enable time slice 1ms at priority 10 */
k_sched_time_slice_set(1, K_PRIO_PREEMPT(10));
for (int i = 0; i < THREADS_NUM; i++) {
tid[i] = k_thread_create(&thread[i], stack[i], STACK_SIZE,
atomic_handler, NULL, NULL, NULL,
K_PRIO_PREEMPT(10), 0, K_NO_WAIT);
}
for (int i = 0; i < THREADS_NUM; i++) {
k_thread_join(tid[i], K_FOREVER);
}
/* disable time slice */
k_sched_time_slice_set(0, K_PRIO_PREEMPT(10));
zassert_true(total_atomic == (TEST_CYCLE * THREADS_NUM),
"atomic counting failure");
}
/**
* @brief Verify atomic increment overflow wraps identically to plain increment.
*
* @ingroup kernel_atomic_ops_tests
*
* @details
* The C standard leaves signed overflow undefined, but Zephyr's atomic_t must
* behave deterministically. Passing proves that incrementing an atomic_t past
* its maximum value produces the same wrapped result as an ordinary increment of
* an equivalently-typed scalar, both at the signed maximum and at the all-ones
* (unsigned wrap to zero) boundary. This guarantees an atomic variable can be
* reasoned about with plain arithmetic where it is logically safe to do so.
*
* Test steps:
* - Seed an atomic_t and a matching scalar at the maximum signed value, increment
* both, and compare.
* - Seed both at the all-ones value (-1), increment both, and compare.
*
* Expected result:
* - After each increment the atomic value matches the plain-increment result and
* the expected wrapped boundary value.
*
* @see atomic_inc()
*/
ZTEST(atomic, test_atomic_overflow)
{
/* Check overflow over max signed value */
uint64_t overflowed_value = (uint64_t)1 << (ATOMIC_BITS - 1);
atomic_val_t atomic_value = overflowed_value - 1;
atomic_t atomic_var = ATOMIC_INIT(atomic_value);
atomic_value++;
atomic_inc(&atomic_var);
zassert_true(atomic_value == atomic_get(&atomic_var),
"max signed overflow mismatch: %lx/%lx",
atomic_value, atomic_get(&atomic_var));
zassert_true(atomic_value == (atomic_val_t)overflowed_value,
"unexpected value after overflow: %lx, expected: %lx",
atomic_value, (atomic_val_t)overflowed_value);
/* Check overflow over max unsigned value */
atomic_value = -1;
atomic_var = ATOMIC_INIT(atomic_value);
atomic_value++;
atomic_inc(&atomic_var);
zassert_true(atomic_value == atomic_get(&atomic_var),
"max unsigned overflow mismatch: %lx/%lx",
atomic_value, atomic_get(&atomic_var));
zassert_true(atomic_value == 0,
"unexpected value after overflow: %lx, expected: 0",
atomic_value);
}
/**
* @}
*/
extern void *common_setup(void);
ZTEST_SUITE(atomic, NULL, common_setup, NULL, NULL, NULL);