From 0e8b8ffa643c1ab877def3dea99572c114350914 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 22 Jun 2020 22:14:40 +0200 Subject: [PATCH 01/12] sys/atomic_utils: Functions for atomic access A set of functions competing with C11 atomics feature-wise, but are implemented more efficient. --- makefiles/pseudomodules.inc.mk | 1 + sys/include/atomic_utils.h | 1137 ++++++++++++++++++++++++++++++++ 2 files changed, 1138 insertions(+) create mode 100644 sys/include/atomic_utils.h diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 4f475711b4..90f7856db2 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -4,6 +4,7 @@ PSEUDOMODULES += at_urc_isr_low PSEUDOMODULES += at_urc_isr_medium PSEUDOMODULES += at_urc_isr_highest PSEUDOMODULES += at24c% +PSEUDOMODULES += atomic_utils PSEUDOMODULES += base64url PSEUDOMODULES += can_mbox PSEUDOMODULES += can_pm diff --git a/sys/include/atomic_utils.h b/sys/include/atomic_utils.h new file mode 100644 index 0000000000..318cc4e538 --- /dev/null +++ b/sys/include/atomic_utils.h @@ -0,0 +1,1137 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @defgroup sys_atomic_utils Utility functions for atomic access + * @ingroup sys + * + * This modules adds some utility functions to perform atomic accesses. + * + * # Usage + * + * The atomic utilitys allow atomic access to regular variables. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * uint32_t global_counter; + * + * void irq_handler(void) + * { + * // No need to use atomic access in IRQ handlers, if other IRQ handlers + * // never touch global_counter: At the beginning and at the end of every + * // ISR a memory barrier is in place, so that at the end of the ISR the + * // memory will be in a state as if all memory accesses within the ISR + * // took place in sequential order. + * // + * // Extra detail only RIOT kernel hackers need to know: If all ISRs + * // accessing the same variable cannot interrupt each other, atomic + * // access is still not needed. (Currently only PendSV on ARM can be + * // interrupted by other IRQs with RIOTs default IRQ priorities. If + * // application developers modifies those, they can be assumed to know + * // what they are doing - or to happily face the consequences otherwise.) + * global_counter++; + * } + * + * void called_by_thread_a(void) { + * if (atomic_load_u32(&global_counter) > THRESHOLD) { + * on_threshold_reached(); + * atomic_store_u32(&global_counter, 0); + * } + * } + * + * void called_by_thread_b(void) { + * atomic_add_u32(&global_counter, 42); + * } + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * # Motivation + * There are some reasons why these functions might be chosen over the + * [C11 Atomic Operations Library](https://en.cppreference.com/w/c/atomic) in + * some advanced use cases: + * + * - The functions allow mixing of atomic and non-atomic accesses. E.g. while + * IRQs are disabled anyway, even plain accesses cannot be interrupted but + * are often more efficient. + * - On platforms not supporting lock-free access, a library call is generated + * instead. The fallback implementation used here is more efficient in terms + * of both CPU instructions and ROM size. + * - On platforms where some operations can be implemented lock free while + * others can't, at least LLVM will use the library call even for those + * accesses that can be implemented lock-free. This is because without + * assuming how the library call implements atomic access for the other + * functions, mixing library calls and lock free accesses could result in data + * corruption. But this implementation resorts to disabling IRQs when + * lock-free implementations are not possible, which mixes well with lock-free + * accesses. Thus, additional overhead for atomic accesses is only spent where + * needed. + * - In some cases the fallback implementation performs better than the lock + * free implementation. E.g. if a specific platform has an atomic compare and + * swap instruction, this could be used to perform a read-modify-write in a + * loop until the value initially read was not changed in between. Just + * disabling IRQs to implement an atomic read-modify-write operation is likely + * more efficient. C11 atomics will however always use the lock free + * implementation (if such exists), assuming that this is more efficient. + * This assumption was made with desktop class hardware in mind, but is not + * generally true for bare metal targets. These function allow to optimize + * for the actual hardware RIOT is running on. + * - This library provides "semi-atomic" read-modify-write operations, which are + * useful when at most one thread is ever writing to memory. In that case, + * only the write part of the read-modify-write operation needs to be + * performed in an atomic fashion in order for the reading threads to perceive + * atomic updates of the variable. This is significantly cheaper than atomic + * read-modify-write operations for many platforms + * + * # Guarantees + * + * - Every utility function here acts as a barrier for code reordering regarding + * - For the `atomic_*()` family of functions: The whole operation will be done + * in an non-interruptible fashion + * - For the `semi_atomic_*()` family of functions: The write part of the + * operation is done atomically. If at most one thread is ever performing + * changes to a variable using the `semi_atomic_()` functions, those changes + * will appear as if they were atomic to all other threads. + * + * # Porting to new CPUs + * + * At the bare minimum, create an empty `atomic_utils_arch.h` file. This will + * result in the fallback implementations being used. + * + * To expose lock-free atomic operations, add an implementation to the + * `atomic_utils_arch.h` file and disable the fallback implementation by + * defining `HAS_`, where `` is the name + * of the function provided in all upper case. E.g. most platforms will be able + * to provide lock-free reads and writes up to their word size and can expose + * this as follows for GCC: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * // All the user header boilerplate + * #define HAS_ATOMIC_LOAD_U8 + * static inline uint8_t atomic_load_u8(const uint8_t *var) + * { + * return __atomic_load_1(var, __ATOMIC_SEQ_CST); + * } + * + * #define HAS_ATOMIC_STORE_U8 + * static inline void atomic_store_u8(uint8_t *dest, uint8_t val) + * { + * __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Note: The `semi_atomic_*()` family of functions is always provided using + * `atomic_*()` functions in the cheapest way possible. + * + * @{ + * + * @file + * @brief API of the utility functions for atomic accesses + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_H +#define ATOMIC_UTILS_H + +#include + +#include "irq.h" +#include "atomic_utils_arch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Declarations and documentation: */ + +#if !defined(HAS_ATOMIC_BIT) || defined(DOXYGEN) +/** + * @name Types used to specify atomic bit access operations + * + * @warning These types are implementation dependent to allow exploiting + * hardware specific features like bit-banding. Use the provided helper + * functions to get or set the bit or destination they refer to. + * + * The motivation of a dedicated type is to allow ahead of time computation of + * e.g. bit-banding addresses, so that actually access can be done in a single + * CPU cycles even if the bit reference was initialized in a separate + * compilation unit and no link time optimization is used. + * + * @{ + */ +/** + * @brief Type specifying a bit in an `uint8_t` + * + * @warning This is an implementation specific type! + */ +typedef struct { + uint8_t *dest; /**< Memory containing the bit to set/clear */ + uint8_t mask; /**< Bitmask used for setting the bit */ +} atomic_bit_u8_t; + +/** + * @brief Type specifying a bit in an `uint16_t` + * + * @warning This is an implementation specific type! + */ +typedef struct { + uint16_t *dest; /**< Memory containing the bit to set/clear */ + uint16_t mask; /**< Bitmask used for setting the bit */ +} atomic_bit_u16_t; + +/** + * @brief Type specifying a bit in an `uint32_t` + * + * @warning This is an implementation specific type! + */ +typedef struct { + uint32_t *dest; /**< Memory containing the bit to set/clear */ + uint32_t mask; /**< Bitmask used for setting the bit */ +} atomic_bit_u32_t; + +/** + * @brief Type specifying a bit in an `uint64_t` + * + * @warning This is an implementation specific type! + */ +typedef struct { + uint64_t *dest; /**< Memory containing the bit to set/clear */ + uint64_t mask; /**< Bitmask used for setting the bit */ +} atomic_bit_u64_t; +/** @} */ +#endif /* HAS_ATOMIC_BIT */ + +/** + * @name Atomic Loads + * @{ + */ +/** + * @brief Load an `uint8_t` atomically + * + * @param[in] var Variable to load atomically + * @return The value stored in @p var + */ +static inline uint8_t atomic_load_u8(const uint8_t *var); +/** + * @brief Load an `uint16_t` atomically + * + * @param[in] var Variable to load atomically + * @return The value stored in @p var + */ +static inline uint16_t atomic_load_u16(const uint16_t *var); +/** + * @brief Load an `uint32_t` atomically + * + * @param[in] var Variable to load atomically + * @return The value stored in @p var + */ +static inline uint32_t atomic_load_u32(const uint32_t *var); +/** + * @brief Load an `uint64_t` atomically + * + * @param[in] var Variable to load atomically + * @return The value stored in @p var + */ +static inline uint64_t atomic_load_u64(const uint64_t *var); +/** @} */ + +/** + * @name Atomic Stores + * @{ + */ +/** + * @brief Store an `uint8_t` atomically + * @param[out] dest Location to atomically write the new value to + * @param[in] val Value to write + */ +static inline void atomic_store_u8(uint8_t *dest, uint8_t val); +/** + * @brief Store an `uint16_t` atomically + * @param[out] dest Location to atomically write the new value to + * @param[in] val Value to write + */ +static inline void atomic_store_u16(uint16_t *dest, uint16_t val); +/** + * @brief Store an `uint32_t` atomically + * @param[out] dest Location to atomically write the new value to + * @param[in] val Value to write + */ +static inline void atomic_store_u32(uint32_t *dest, uint32_t val); +/** + * @brief Store an `uint64_t` atomically + * @param[out] dest Location to atomically write the new value to + * @param[in] val Value to write + */ +static inline void atomic_store_u64(uint64_t *dest, uint64_t val); +/** @} */ + +/** + * @name Atomic In-Place Addition + * @{ + */ +/** + * @brief Atomically add a value onto a given value + * @param[in,out] dest Add @p summand onto this value atomically in-place + * @param[in] summand Value to add onto @p dest + */ +static inline void atomic_fetch_add_u8(uint8_t *dest, uint8_t summand); +/** + * @brief Atomically add a value onto a given value + * @param[in,out] dest Add @p summand onto this value atomically in-place + * @param[in] summand Value to add onto @p dest + */ +static inline void atomic_fetch_add_u16(uint16_t *dest, uint16_t summand); +/** + * @brief Atomically add a value onto a given value + * @param[in,out] dest Add @p summand onto this value atomically in-place + * @param[in] summand Value to add onto @p dest + */ +static inline void atomic_fetch_add_u32(uint32_t *dest, uint32_t summand); +/** + * @brief Atomically add a value onto a given value + * @param[in,out] dest Add @p summand onto this value atomically in-place + * @param[in] summand Value to add onto @p dest + */ +static inline void atomic_fetch_add_u64(uint64_t *dest, uint64_t summand); +/** @} */ + +/** + * @name Atomic In-Place Subtraction + * @{ + */ +/** + * @brief Atomically subtract a value from a given value + * @param[in,out] dest Subtract @p subtrahend from this value + * atomically in-place + * @param[in] subtrahend Value to subtract from @p dest + */ +static inline void atomic_fetch_sub_u8(uint8_t *dest, uint8_t subtrahend); +/** + * @brief Atomically subtract a value from a given value + * @param[in,out] dest Subtract @p subtrahend from this value + * atomically in-place + * @param[in] subtrahend Value to subtract from @p dest + */ +static inline void atomic_fetch_sub_u16(uint16_t *dest, uint16_t subtrahend); +/** + * @brief Atomically subtract a value from a given value + * @param[in,out] dest Subtract @p subtrahend from this value + * atomically in-place + * @param[in] subtrahend Value to subtract from @p dest + */ +static inline void atomic_fetch_sub_u32(uint32_t *dest, uint32_t subtrahend); +/** + * @brief Atomically subtract a value from a given value + * @param[in,out] dest Subtract @p subtrahend from this value + * atomically in-place + * @param[in] subtrahend Value to subtract from @p dest + */ +static inline void atomic_fetch_sub_u64(uint64_t *dest, uint64_t subtrahend); +/** @} */ + +/** + * @name Atomic In-Place Bitwise OR + * @{ + */ +/** + * @brief Atomic version of `*dest |= val` + * @param[in,out] dest Replace this value with the result of + * `*dest | val` + * @param[in] val Value to bitwise or into @p dest in-place + */ +static inline void atomic_fetch_or_u8(uint8_t *dest, uint8_t val); +/** + * @brief Atomic version of `*dest |= val` + * @param[in,out] dest Replace this value with the result of + * `*dest | val` + * @param[in] val Value to bitwise or into @p dest in-place + */ +static inline void atomic_fetch_or_u16(uint16_t *dest, uint16_t val); +/** + * @brief Atomic version of `*dest |= val` + * @param[in,out] dest Replace this value with the result of + * `*dest | val` + * @param[in] val Value to bitwise or into @p dest in-place + */ +static inline void atomic_fetch_or_u32(uint32_t *dest, uint32_t val); +/** + * @brief Atomic version of `*dest |= val` + * @param[in,out] dest Replace this value with the result of + * `*dest | val` + * @param[in] val Value to bitwise or into @p dest in-place + */ +static inline void atomic_fetch_or_u64(uint64_t *dest, uint64_t val); +/** @} */ + +/** + * @name Atomic In-Place Bitwise XOR + * @{ + */ +/** + * @brief Atomic version of `*dest ^= val` + * @param[in,out] dest Replace this value with the result of + * `*dest ^ val` + * @param[in] val Value to bitwise xor into @p dest in-place + */ +static inline void atomic_fetch_xor_u8(uint8_t *dest, uint8_t val); +/** + * @brief Atomic version of `*dest ^= val` + * @param[in,out] dest Replace this value with the result of + * `*dest ^ val` + * @param[in] val Value to bitwise xor into @p dest in-place + */ +static inline void atomic_fetch_xor_u16(uint16_t *dest, uint16_t val); +/** + * @brief Atomic version of `*dest ^= val` + * @param[in,out] dest Replace this value with the result of + * `*dest ^ val` + * @param[in] val Value to bitwise xor into @p dest in-place + */ +static inline void atomic_fetch_xor_u32(uint32_t *dest, uint32_t val); +/** + * @brief Atomic version of `*dest ^= val` + * @param[in,out] dest Replace this value with the result of + * `*dest ^ val` + * @param[in] val Value to bitwise xor into @p dest in-place + */ +static inline void atomic_fetch_xor_u64(uint64_t *dest, uint64_t val); +/** @} */ + +/** + * @name Atomic In-Place Bitwise AND + * @{ + */ +/** + * @brief Atomic version of `*dest &= val` + * @param[in,out] dest Replace this value with the result of + * `*dest & val` + * @param[in] val Value to bitwise and into @p dest in-place + */ +static inline void atomic_fetch_and_u8(uint8_t *dest, uint8_t val); +/** + * @brief Atomic version of `*dest &= val` + * @param[in,out] dest Replace this value with the result of + * `*dest & val` + * @param[in] val Value to bitwise and into @p dest in-place + */ +static inline void atomic_fetch_and_u16(uint16_t *dest, uint16_t val); +/** + * @brief Atomic version of `*dest &= val` + * @param[in,out] dest Replace this value with the result of + * `*dest & val` + * @param[in] val Value to bitwise and into @p dest in-place + */ +static inline void atomic_fetch_and_u32(uint32_t *dest, uint32_t val); +/** + * @brief Atomic version of `*dest &= val` + * @param[in,out] dest Replace this value with the result of + * `*dest & val` + * @param[in] val Value to bitwise and into @p dest in-place + */ +static inline void atomic_fetch_and_u64(uint64_t *dest, uint64_t val); +/** @} */ + +/** + * @name Helper Functions to Handle Atomic Bit References + * @{ + */ +/** + * @brief Create a reference to a bit in an `uint8_t` + * @param[in] dest Memory containing the bit + * @param[in] bit Bit number (`0` refers to the least significant) + */ +static inline atomic_bit_u8_t atomic_bit_u8(uint8_t *dest, uint8_t bit); + +/** + * @brief Create a reference to a bit in an `uint16_t` + * @param[in] dest Memory containing the bit + * @param[in] bit Bit number (`0` refers to the least significant) + */ +static inline atomic_bit_u16_t atomic_bit_u16(uint16_t *dest, uint8_t bit); + +/** + * @brief Create a reference to a bit in an `uint32_t` + * @param[in] dest Memory containing the bit + * @param[in] bit Bit number (`0` refers to the least significant) + */ +static inline atomic_bit_u32_t atomic_bit_u32(uint32_t *dest, uint8_t bit); + +/** + * @brief Create a reference to a bit in an `uint64_t` + * @param[in] dest Memory containing the bit + * @param[in] bit Bit number (`0` refers to the least significant) + */ +static inline atomic_bit_u64_t atomic_bit_u64(uint64_t *dest, uint8_t bit); +/** @} */ + +/** + * @name Atomic Bit Setting + * @{ + */ +/** + * @brief Atomic version of `*dest |= (1 << bit)` + * @param[in,out] bit bit to set + */ +static inline void atomic_set_bit_u8(atomic_bit_u8_t bit); +/** + * @brief Atomic version of `*dest |= (1 << bit)` + * @param[in,out] bit bit to set + */ +static inline void atomic_set_bit_u16(atomic_bit_u16_t bit); +/** + * @brief Atomic version of `*dest |= (1 << bit)` + * @param[in,out] bit bit to set + */ +static inline void atomic_set_bit_u32(atomic_bit_u32_t bit); +/** + * @brief Atomic version of `*dest |= (1 << bit)` + * @param[in,out] bit bit to set + */ +static inline void atomic_set_bit_u64(atomic_bit_u64_t bit); +/** @} */ + +/** + * @name Atomic Bit Clearing + * @{ + */ +/** + * @brief Atomic version of `*dest &= ~(1 << bit)` + * @param[in,out] bit bit to set + */ +static inline void atomic_clear_bit_u8(atomic_bit_u8_t bit); +/** + * @brief Atomic version of `*dest &= ~(1 << bit)` + * @param[in,out] bit bit to set + */ +static inline void atomic_clear_bit_u16(atomic_bit_u16_t bit); +/** + * @brief Atomic version of `*dest &= ~(1 << bit)` + * @param[in,out] bit bit to set + */ +static inline void atomic_clear_bit_u32(atomic_bit_u32_t bit); +/** + * @brief Atomic version of `*dest &= ~(1 << bit)` + * @param[in,out] bit bit to set + */ +static inline void atomic_clear_bit_u64(atomic_bit_u64_t bit); +/** @} */ + +/** + * @name Semi-Atomic In-Place Addition + * @{ + */ +/** + * @brief Semi-atomically add a value onto a given value + * @param[in,out] dest Add @p summand onto this value semi-atomically + * in-place + * @param[in] summand Value to add onto @p dest + */ +static inline void semi_atomic_fetch_add_u8(uint8_t *dest, uint8_t summand); +/** + * @brief Semi-atomically add a value onto a given value + * @param[in,out] dest Add @p summand onto this value semi-atomically + * in-place + * @param[in] summand Value to add onto @p dest + */ +static inline void semi_atomic_fetch_add_u16(uint16_t *dest, uint16_t summand); +/** + * @brief Semi-atomically add a value onto a given value + * @param[in,out] dest Add @p summand onto this value semi-atomically + * in-place + * @param[in] summand Value to add onto @p dest + */ +static inline void semi_atomic_fetch_add_u32(uint32_t *dest, uint32_t summand); +/** + * @brief Semi-atomically add a value onto a given value + * @param[in,out] dest Add @p summand onto this value semi-atomically + * in-place + * @param[in] summand Value to add onto @p dest + */ +static inline void semi_atomic_fetch_add_u64(uint64_t *dest, uint64_t summand); +/** @} */ + +/** + * @name Semi-Atomic In-Place Subtraction + * @{ + */ +/** + * @brief Semi-atomically subtract a value from a given value + * @param[in,out] dest Subtract @p subtrahend from this value + * semi-atomically in-place + * @param[in] subtrahend Value to subtract from @p dest + */ +static inline void semi_atomic_fetch_sub_u8(uint8_t *dest, uint8_t subtrahend); +/** + * @brief Semi-atomically subtract a value from a given value + * @param[in,out] dest Subtract @p subtrahend from this value + * semi-atomically in-place + * @param[in] subtrahend Value to subtract from @p dest + */ +static inline void semi_atomic_fetch_sub_u16(uint16_t *dest, + uint16_t subtrahend); +/** + * @brief Semi-atomically subtract a value from a given value + * @param[in,out] dest Subtract @p subtrahend from this value + * semi-atomically in-place + * @param[in] subtrahend Value to subtract from @p dest + */ +static inline void semi_atomic_fetch_sub_u32(uint32_t *dest, + uint32_t subtrahend); +/** + * @brief Semi-atomically subtract a value from a given value + * @param[in,out] dest Subtract @p subtrahend from this value + * semi-atomically in-place + * @param[in] subtrahend Value to subtract from @p dest + */ +static inline void semi_atomic_fetch_sub_u64(uint64_t *dest, + uint64_t subtrahend); +/** @} */ + +/** + * @name Semi-atomic In-Place Bitwise OR + * @{ + */ +/** + * @brief Semi-atomic version of `*dest |= val` + * @param[in,out] dest Replace this value with the result of + * `*dest | val` + * @param[in] val Value to bitwise or into @p dest in-place + */ +static inline void semi_atomic_fetch_or_u8(uint8_t *dest, uint8_t val); +/** + * @brief Semi-atomic version of `*dest |= val` + * @param[in,out] dest Replace this value with the result of + * `*dest | val` + * @param[in] val Value to bitwise or into @p dest in-place + */ +static inline void semi_atomic_fetch_or_u16(uint16_t *dest, uint16_t val); +/** + * @brief Semi-atomic version of `*dest |= val` + * @param[in,out] dest Replace this value with the result of + * `*dest | val` + * @param[in] val Value to bitwise or into @p dest in-place + */ +static inline void semi_atomic_fetch_or_u32(uint32_t *dest, uint32_t val); +/** + * @brief Semi-atomic version of `*dest |= val` + * @param[in,out] dest Replace this value with the result of + * `*dest | val` + * @param[in] val Value to bitwise or into @p dest in-place + */ +static inline void semi_atomic_fetch_or_u64(uint64_t *dest, uint64_t val); +/** @} */ + +/** + * @name Semi-Atomic In-Place Bitwise XOR + * @{ + */ +/** + * @brief Semi-atomic version of `*dest ^= val` + * @param[in,out] dest Replace this value with the result of + * `*dest ^ val` + * @param[in] val Value to bitwise xor into @p dest in-place + */ +static inline void semi_atomic_fetch_xor_u8(uint8_t *dest, uint8_t val); +/** + * @brief Semi-atomic version of `*dest ^= val` + * @param[in,out] dest Replace this value with the result of + * `*dest ^ val` + * @param[in] val Value to bitwise xor into @p dest in-place + */ +static inline void semi_atomic_fetch_xor_u16(uint16_t *dest, uint16_t val); +/** + * @brief Semi-atomic version of `*dest ^= val` + * @param[in,out] dest Replace this value with the result of + * `*dest ^ val` + * @param[in] val Value to bitwise xor into @p dest in-place + */ +static inline void semi_atomic_fetch_xor_u32(uint32_t *dest, uint32_t val); +/** + * @brief Semi-atomic version of `*dest ^= val` + * @param[in,out] dest Replace this value with the result of + * `*dest ^ val` + * @param[in] val Value to bitwise xor into @p dest in-place + */ +static inline void semi_atomic_fetch_xor_u64(uint64_t *dest, uint64_t val); +/** @} */ + +/** + * @name Semi-Atomic In-Place Bitwise AND + * @{ + */ +/** + * @brief Semi-atomic version of `*dest &= val` + * @param[in,out] dest Replace this value with the result of + * `*dest & val` + * @param[in] val Value to bitwise and into @p dest in-place + */ +static inline void semi_atomic_fetch_and_u8(uint8_t *dest, uint8_t val); +/** + * @brief Semi-atomic version of `*dest &= val` + * @param[in,out] dest Replace this value with the result of + * `*dest & val` + * @param[in] val Value to bitwise and into @p dest in-place + */ +static inline void semi_atomic_fetch_and_u16(uint16_t *dest, uint16_t val); +/** + * @brief Semi-atomic version of `*dest &= val` + * @param[in,out] dest Replace this value with the result of + * `*dest & val` + * @param[in] val Value to bitwise and into @p dest in-place + */ +static inline void semi_atomic_fetch_and_u32(uint32_t *dest, uint32_t val); +/** + * @brief Semi-atomic version of `*dest &= val` + * @param[in,out] dest Replace this value with the result of + * `*dest & val` + * @param[in] val Value to bitwise and into @p dest in-place + */ +static inline void semi_atomic_fetch_and_u64(uint64_t *dest, uint64_t val); +/** @} */ + +/* Fallback implementations of atomic utility functions: */ + +/** + * @brief Concatenate two tokens + */ +#define CONCAT(a, b) a ## b + +/** + * @brief Concatenate four tokens + */ +#define CONCAT4(a, b, c, d) a ## b ## c ## d + +/** + * @brief Generates a static inline function implementing + * `atomic_load_u()` + * + * @param name Name of the variable type, e.g. "u8" + * @param type Variable type, e.g. `uint8_t` + */ +#define ATOMIC_LOAD_IMPL(name, type) \ + static inline type CONCAT(atomic_load_, name)(const type *var) \ + { \ + unsigned state = irq_disable(); \ + /* var can be register allocated, hence the memory barrier of \ + * irq_disable() and irq_restore() may not apply here. Using volatile \ + * ensures that the compiler allocates it in memory and that the \ + * memory access is not optimized out. */ \ + type result = *((const volatile type *)var); \ + irq_restore(state); \ + return result; \ + } + +#ifndef HAS_ATOMIC_LOAD_U8 +ATOMIC_LOAD_IMPL(u8, uint8_t) +#endif +#ifndef HAS_ATOMIC_LOAD_U16 +ATOMIC_LOAD_IMPL(u16, uint16_t) +#endif +#ifndef HAS_ATOMIC_LOAD_U32 +ATOMIC_LOAD_IMPL(u32, uint32_t) +#endif +#ifndef HAS_ATOMIC_LOAD_U64 +ATOMIC_LOAD_IMPL(u64, uint64_t) +#endif + +/** + * @brief Generates a static inline function implementing + * `atomic_store_u()` + * + * @param name Name of the variable type, e.g. "u8" + * @param type Variable type, e.g. `uint8_t` + */ +#define ATOMIC_STORE_IMPL(name, type) \ + static inline void CONCAT(atomic_store_, name)(type *dest, type val) \ + { \ + unsigned state = irq_disable(); \ + /* dest can be register allocated, hence the memory barrier of \ + * irq_disable() and irq_restore() may not apply here. Using volatile \ + * ensures that the compiler allocates it in memory and that the \ + * memory access is not optimized out. */ \ + *((volatile type *)dest) = val; \ + irq_restore(state); \ + } + +#ifndef HAS_ATOMIC_STORE_U8 +ATOMIC_STORE_IMPL(u8, uint8_t) +#endif +#ifndef HAS_ATOMIC_STORE_U16 +ATOMIC_STORE_IMPL(u16, uint16_t) +#endif +#ifndef HAS_ATOMIC_STORE_U32 +ATOMIC_STORE_IMPL(u32, uint32_t) +#endif +#ifndef HAS_ATOMIC_STORE_U64 +ATOMIC_STORE_IMPL(u64, uint64_t) +#endif + +/** + * @brief Generates a static inline function implementing + * `atomic_fecth__u()` + * + * @param opname Name of the operator in @p op, e.g. "and" for `+` + * @param op Operator to implement atomically, e.g. `+` + * @param name Name of the variable type, e.g. "u8" + * @param type Variable type, e.g. `uint8_t` + */ +#define ATOMIC_FETCH_OP_IMPL(opname, op, name, type) \ + static inline void CONCAT4(atomic_fetch_, opname, _, name)(type *dest, \ + type val) \ + { \ + unsigned state = irq_disable(); \ + /* dest can be register allocated, hence the memory barrier of \ + * irq_disable() and irq_restore() may not apply here. Using volatile \ + * ensures that the compiler allocates it in memory and that the \ + * memory access is not optimized out. */ \ + volatile type *tmp = dest; \ + *tmp = *tmp op val; \ + irq_restore(state); \ + } + +#ifndef HAS_ATOMIC_FETCH_ADD_U8 +ATOMIC_FETCH_OP_IMPL(add, +, u8, uint8_t) +#endif +#ifndef HAS_ATOMIC_FETCH_ADD_U16 +ATOMIC_FETCH_OP_IMPL(add, +, u16, uint16_t) +#endif +#ifndef HAS_ATOMIC_FETCH_ADD_U32 +ATOMIC_FETCH_OP_IMPL(add, +, u32, uint32_t) +#endif +#ifndef HAS_ATOMIC_FETCH_ADD_U64 +ATOMIC_FETCH_OP_IMPL(add, +, u64, uint64_t) +#endif + +#ifndef HAS_ATOMIC_FETCH_SUB_U8 +ATOMIC_FETCH_OP_IMPL(sub, -, u8, uint8_t) +#endif +#ifndef HAS_ATOMIC_FETCH_SUB_U16 +ATOMIC_FETCH_OP_IMPL(sub, -, u16, uint16_t) +#endif +#ifndef HAS_ATOMIC_FETCH_SUB_U32 +ATOMIC_FETCH_OP_IMPL(sub, -, u32, uint32_t) +#endif +#ifndef HAS_ATOMIC_FETCH_SUB_U64 +ATOMIC_FETCH_OP_IMPL(sub, -, u64, uint64_t) +#endif + +#ifndef HAS_ATOMIC_FETCH_OR_U8 +ATOMIC_FETCH_OP_IMPL(or, |, u8, uint8_t) +#endif +#ifndef HAS_ATOMIC_FETCH_OR_U16 +ATOMIC_FETCH_OP_IMPL(or, |, u16, uint16_t) +#endif +#ifndef HAS_ATOMIC_FETCH_OR_U32 +ATOMIC_FETCH_OP_IMPL(or, |, u32, uint32_t) +#endif +#ifndef HAS_ATOMIC_FETCH_OR_U64 +ATOMIC_FETCH_OP_IMPL(or, |, u64, uint64_t) +#endif + +#ifndef HAS_ATOMIC_FETCH_XOR_U8 +ATOMIC_FETCH_OP_IMPL(xor, ^, u8, uint8_t) +#endif +#ifndef HAS_ATOMIC_FETCH_XOR_U16 +ATOMIC_FETCH_OP_IMPL(xor, ^, u16, uint16_t) +#endif +#ifndef HAS_ATOMIC_FETCH_XOR_U32 +ATOMIC_FETCH_OP_IMPL(xor, ^, u32, uint32_t) +#endif +#ifndef HAS_ATOMIC_FETCH_XOR_U64 +ATOMIC_FETCH_OP_IMPL(xor, ^, u64, uint64_t) +#endif + +#ifndef HAS_ATOMIC_FETCH_AND_U8 +ATOMIC_FETCH_OP_IMPL(and, &, u8, uint8_t) +#endif +#ifndef HAS_ATOMIC_FETCH_AND_U16 +ATOMIC_FETCH_OP_IMPL(and, &, u16, uint16_t) +#endif +#ifndef HAS_ATOMIC_FETCH_AND_U32 +ATOMIC_FETCH_OP_IMPL(and, &, u32, uint32_t) +#endif +#ifndef HAS_ATOMIC_FETCH_AND_U64 +ATOMIC_FETCH_OP_IMPL(and, &, u64, uint64_t) +#endif + +#ifndef HAS_ATOMIC_BIT +static inline atomic_bit_u8_t atomic_bit_u8(uint8_t *dest, uint8_t bit) +{ + atomic_bit_u8_t result = { .dest = dest, .mask = 1U << bit }; + return result; +} +static inline atomic_bit_u16_t atomic_bit_u16(uint16_t *dest, uint8_t bit) +{ + atomic_bit_u16_t result = { .dest = dest, .mask = 1U << bit }; + return result; +} +static inline atomic_bit_u32_t atomic_bit_u32(uint32_t *dest, uint8_t bit) +{ + atomic_bit_u32_t result = { .dest = dest, .mask = 1UL << bit }; + return result; +} +static inline atomic_bit_u64_t atomic_bit_u64(uint64_t *dest, uint8_t bit) +{ + atomic_bit_u64_t result = { .dest = dest, .mask = 1ULL << bit }; + return result; +} +static inline void atomic_set_bit_u8(atomic_bit_u8_t bit) +{ + atomic_fetch_or_u8(bit.dest, bit.mask); +} +static inline void atomic_set_bit_u16(atomic_bit_u16_t bit) +{ + atomic_fetch_or_u16(bit.dest, bit.mask); +} +static inline void atomic_set_bit_u32(atomic_bit_u32_t bit) +{ + atomic_fetch_or_u32(bit.dest, bit.mask); +} +static inline void atomic_set_bit_u64(atomic_bit_u64_t bit) +{ + atomic_fetch_or_u64(bit.dest, bit.mask); +} +static inline void atomic_clear_bit_u8(atomic_bit_u8_t bit) +{ + atomic_fetch_and_u8(bit.dest, ~bit.mask); +} +static inline void atomic_clear_bit_u16(atomic_bit_u16_t bit) +{ + atomic_fetch_and_u16(bit.dest, ~bit.mask); +} +static inline void atomic_clear_bit_u32(atomic_bit_u32_t bit) +{ + atomic_fetch_and_u32(bit.dest, ~bit.mask); +} +static inline void atomic_clear_bit_u64(atomic_bit_u64_t bit) +{ + atomic_fetch_and_u64(bit.dest, ~bit.mask); +} +#endif + +/* Provide semi_atomic_*() functions on top. + * + * - If atomic_() is provided: Use this for semi_atomic_() as well + * - Else: + * - If matching `atomic_store_u()` is provided: Only make final + * store atomic, as we can avoid touching the IRQ state register that + * way + * - Else: We need to disable and re-enable IRQs anyway, we just use the + * fallback implementation of `atomic_()` for `semi_atomic()` + * as well + */ + +/* FETCH_ADD */ +#if defined(HAS_ATOMIC_FETCH_ADD_U8) || !defined(HAS_ATOMIC_STORE_U8) +static inline void semi_atomic_fetch_add_u8(uint8_t *dest, uint8_t val) { + atomic_fetch_add_u8(dest, val); +} +#else +static inline void semi_atomic_fetch_add_u8(uint8_t *dest, uint8_t val) { + atomic_store_u8(dest, *dest + val); +} +#endif /* HAS_ATOMIC_FETCH_ADD_U8 || !HAS_ATOMIC_STORE_U8 */ + +#if defined(HAS_ATOMIC_FETCH_ADD_U16) || !defined(HAS_ATOMIC_STORE_U16) +static inline void semi_atomic_fetch_add_u16(uint16_t *dest, uint16_t val) { + atomic_fetch_add_u16(dest, val); +} +#else +static inline void semi_atomic_fetch_add_u16(uint16_t *dest, uint16_t val) { + atomic_store_u16(dest, *dest + val); +} +#endif /* HAS_ATOMIC_FETCH_ADD_U16 || !HAS_ATOMIC_STORE_U16 */ + +#if defined(HAS_ATOMIC_FETCH_ADD_U32) || !defined(HAS_ATOMIC_STORE_U32) +static inline void semi_atomic_fetch_add_u32(uint32_t *dest, uint32_t val) { + atomic_fetch_add_u32(dest, val); +} +#else +static inline void semi_atomic_fetch_add_u32(uint32_t *dest, uint32_t val) { + atomic_store_u32(dest, *dest + val); +} +#endif /* HAS_ATOMIC_FETCH_ADD_U32 || !HAS_ATOMIC_STORE_U32 */ + +#if defined(HAS_ATOMIC_FETCH_ADD_U64) || !defined(HAS_ATOMIC_STORE_U64) +static inline void semi_atomic_fetch_add_u64(uint64_t *dest, uint64_t val) { + atomic_fetch_add_u64(dest, val); +} +#else +static inline void semi_atomic_fetch_add_u64(uint64_t *dest, uint64_t val) { + atomic_store_u64(dest, *dest + val); +} +#endif /* HAS_ATOMIC_FETCH_ADD_U32 || !HAS_ATOMIC_STORE_U32 */ + +/* FETCH_SUB */ +#if defined(HAS_ATOMIC_FETCH_SUB_U8) || !defined(HAS_ATOMIC_STORE_U8) +static inline void semi_atomic_fetch_sub_u8(uint8_t *dest, uint8_t val) { + atomic_fetch_sub_u8(dest, val); +} +#else +static inline void semi_atomic_fetch_sub_u8(uint8_t *dest, uint8_t val) { + atomic_store_u8(dest, *dest - val); +} +#endif /* HAS_ATOMIC_FETCH_SUB_U8 || !HAS_ATOMIC_STORE_U8 */ + +#if defined(HAS_ATOMIC_FETCH_SUB_U16) || !defined(HAS_ATOMIC_STORE_U16) +static inline void semi_atomic_fetch_sub_u16(uint16_t *dest, uint16_t val) { + atomic_fetch_sub_u16(dest, val); +} +#else +static inline void semi_atomic_fetch_sub_u16(uint16_t *dest, uint16_t val) { + atomic_store_u16(dest, *dest - val); +} +#endif /* HAS_ATOMIC_FETCH_SUB_U16 || !HAS_ATOMIC_STORE_U16 */ + +#if defined(HAS_ATOMIC_FETCH_SUB_U32) || !defined(HAS_ATOMIC_STORE_U32) +static inline void semi_atomic_fetch_sub_u32(uint32_t *dest, uint32_t val) { + atomic_fetch_sub_u32(dest, val); +} +#else +static inline void semi_atomic_fetch_sub_u32(uint32_t *dest, uint32_t val) { + atomic_store_u32(dest, *dest - val); +} +#endif /* HAS_ATOMIC_FETCH_SUB_U32 || !HAS_ATOMIC_STORE_U64 */ + +#if defined(HAS_ATOMIC_FETCH_SUB_U64) || !defined(HAS_ATOMIC_STORE_U64) +static inline void semi_atomic_fetch_sub_u64(uint64_t *dest, uint64_t val) { + atomic_fetch_sub_u64(dest, val); +} +#else +static inline void semi_atomic_fetch_sub_u64(uint64_t *dest, uint64_t val) { + atomic_store_u64(dest, *dest - val); +} +#endif /* HAS_ATOMIC_FETCH_SUB_U64 || !HAS_ATOMIC_STORE_U64 */ + +/* FETCH_OR */ +#if defined(HAS_ATOMIC_FETCH_OR_U8) || !defined(HAS_ATOMIC_STORE_U8) +static inline void semi_atomic_fetch_or_u8(uint8_t *dest, uint8_t val) { + atomic_fetch_or_u8(dest, val); +} +#else +static inline void semi_atomic_fetch_or_u8(uint8_t *dest, uint8_t val) { + atomic_store_u8(dest, *dest | val); +} +#endif /* HAS_ATOMIC_FETCH_OR_U8 || !HAS_ATOMIC_STORE_U8 */ + +#if defined(HAS_ATOMIC_FETCH_OR_U16) || !defined(HAS_ATOMIC_STORE_U16) +static inline void semi_atomic_fetch_or_u16(uint16_t *dest, uint16_t val) { + atomic_fetch_or_u16(dest, val); +} +#else +static inline void semi_atomic_fetch_or_u16(uint16_t *dest, uint16_t val) { + atomic_store_u16(dest, *dest | val); +} +#endif /* HAS_ATOMIC_FETCH_OR_U16 || !HAS_ATOMIC_STORE_U16 */ + +#if defined(HAS_ATOMIC_FETCH_OR_U32) || !defined(HAS_ATOMIC_STORE_U32) +static inline void semi_atomic_fetch_or_u32(uint32_t *dest, uint32_t val) { + atomic_fetch_or_u32(dest, val); +} +#else +static inline void semi_atomic_fetch_or_u32(uint32_t *dest, uint32_t val) { + atomic_store_u32(dest, *dest | val); +} +#endif /* HAS_ATOMIC_FETCH_OR_U32 || !HAS_ATOMIC_STORE_U32 */ + +#if defined(HAS_ATOMIC_FETCH_OR_U64) || !defined(HAS_ATOMIC_STORE_U64) +static inline void semi_atomic_fetch_or_u64(uint64_t *dest, uint64_t val) { + atomic_fetch_or_u64(dest, val); +} +#else +static inline void semi_atomic_fetch_or_u64(uint64_t *dest, uint64_t val) { + atomic_store_u64(dest, *dest | val); +} +#endif /* HAS_ATOMIC_FETCH_OR_U64 || !HAS_ATOMIC_STORE_U64 */ + +/* FETCH_XOR */ +#if defined(HAS_ATOMIC_FETCH_XOR_U8) || !defined(HAS_ATOMIC_STORE_U8) +static inline void semi_atomic_fetch_xor_u8(uint8_t *dest, uint8_t val) { + atomic_fetch_xor_u8(dest, val); +} +#else +static inline void semi_atomic_fetch_xor_u8(uint8_t *dest, uint8_t val) { + atomic_store_u8(dest, *dest ^ val); +} +#endif /* HAS_ATOMIC_FETCH_XOR_U8 || !HAS_ATOMIC_STORE_U8 */ + +#if defined(HAS_ATOMIC_FETCH_XOR_U16) || !defined(HAS_ATOMIC_STORE_U16) +static inline void semi_atomic_fetch_xor_u16(uint16_t *dest, uint16_t val) { + atomic_fetch_xor_u16(dest, val); +} +#else +static inline void semi_atomic_fetch_xor_u16(uint16_t *dest, uint16_t val) { + atomic_store_u16(dest, *dest ^ val); +} +#endif /* HAS_ATOMIC_FETCH_XOR_U16 || !HAS_ATOMIC_STORE_U16 */ + +#if defined(HAS_ATOMIC_FETCH_XOR_U32) || !defined(HAS_ATOMIC_STORE_U32) +static inline void semi_atomic_fetch_xor_u32(uint32_t *dest, uint32_t val) { + atomic_fetch_xor_u32(dest, val); +} +#else +static inline void semi_atomic_fetch_xor_u32(uint32_t *dest, uint32_t val) { + atomic_store_u32(dest, *dest ^ val); +} +#endif /* HAS_ATOMIC_FETCH_XOR_U32 || !HAS_ATOMIC_STORE_U32 */ + +#if defined(HAS_ATOMIC_FETCH_XOR_U64) || !defined(HAS_ATOMIC_STORE_U64) +static inline void semi_atomic_fetch_xor_u64(uint64_t *dest, uint64_t val) { + atomic_fetch_xor_u64(dest, val); +} +#else +static inline void semi_atomic_fetch_xor_u64(uint64_t *dest, uint64_t val) { + atomic_store_u64(dest, *dest ^ val); +} +#endif /* HAS_ATOMIC_FETCH_XOR_U64 || !HAS_ATOMIC_STORE_U64 */ + +/* FETCH_AND */ +#if defined(HAS_ATOMIC_FETCH_AND_U8) || !defined(HAS_ATOMIC_STORE_U8) +static inline void semi_atomic_fetch_and_u8(uint8_t *dest, uint8_t val) { + atomic_fetch_and_u8(dest, val); +} +#else +static inline void semi_atomic_fetch_and_u8(uint8_t *dest, uint8_t val) { + atomic_store_u8(dest, *dest & val); +} +#endif /* HAS_ATOMIC_FETCH_AND_U8 || !HAS_ATOMIC_STORE_U8 */ + +#if defined(HAS_ATOMIC_FETCH_AND_U16) || !defined(HAS_ATOMIC_STORE_U16) +static inline void semi_atomic_fetch_and_u16(uint16_t *dest, uint16_t val) { + atomic_fetch_and_u16(dest, val); +} +#else +static inline void semi_atomic_fetch_and_u16(uint16_t *dest, uint16_t val) { + atomic_store_u16(dest, *dest & val); +} +#endif /* HAS_ATOMIC_FETCH_AND_U16 || !HAS_ATOMIC_STORE_U16 */ + +#if defined(HAS_ATOMIC_FETCH_AND_U32) || !defined(HAS_ATOMIC_STORE_U32) +static inline void semi_atomic_fetch_and_u32(uint32_t *dest, uint32_t val) { + atomic_fetch_and_u32(dest, val); +} +#else +static inline void semi_atomic_fetch_and_u32(uint32_t *dest, uint32_t val) { + atomic_store_u32(dest, *dest & val); +} +#endif /* HAS_ATOMIC_FETCH_AND_U32 || !HAS_ATOMIC_STORE_U32 */ + +#if defined(HAS_ATOMIC_FETCH_AND_U64) || !defined(HAS_ATOMIC_STORE_U64) +static inline void semi_atomic_fetch_and_u64(uint64_t *dest, uint64_t val) { + atomic_fetch_and_u64(dest, val); +} +#else +static inline void semi_atomic_fetch_and_u64(uint64_t *dest, uint64_t val) { + atomic_store_u64(dest, *dest & val); +} +#endif /* HAS_ATOMIC_FETCH_AND_U64 || !HAS_ATOMIC_STORE_U64 */ + +#ifdef __cplusplus +} +#endif + +#endif /* ATOMIC_UTILS_H */ +/** @} */ From a3e2d277994dfcd5e90e80e381cf1a1c96b51449 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 22 Jun 2020 22:17:52 +0200 Subject: [PATCH 02/12] cpu/cortexm_common: Add atomic_utils_arch.h --- .../include/atomic_utils_arch.h | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 cpu/cortexm_common/include/atomic_utils_arch.h diff --git a/cpu/cortexm_common/include/atomic_utils_arch.h b/cpu/cortexm_common/include/atomic_utils_arch.h new file mode 100644 index 0000000000..0045445b53 --- /dev/null +++ b/cpu/cortexm_common/include/atomic_utils_arch.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_cortexm_common + * + * @{ + * + * @file + * @brief Implementation of fast atomic utility functions + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_ARCH_H +#define ATOMIC_UTILS_ARCH_H +#ifndef DOXYGEN + +#include "bit.h" +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* clang provides no built-in atomic access to regular variables */ +#ifndef __clang__ + +#define HAS_ATOMIC_LOAD_U8 +static inline uint8_t atomic_load_u8(const uint8_t *var) +{ + return __atomic_load_1(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U16 +static inline uint16_t atomic_load_u16(const uint16_t *var) +{ + return __atomic_load_2(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U32 +static inline uint32_t atomic_load_u32(const uint32_t *var) +{ + return __atomic_load_4(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U8 +static inline void atomic_store_u8(uint8_t *dest, uint8_t val) +{ + __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U16 +static inline void atomic_store_u16(uint16_t *dest, uint16_t val) +{ + __atomic_store_2(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U32 +static inline void atomic_store_u32(uint32_t *dest, uint32_t val) +{ + __atomic_store_4(dest, val, __ATOMIC_SEQ_CST); +} + +#endif /* __clang__ */ + +#if CPU_HAS_BITBAND +#define HAS_ATOMIC_BIT + +typedef volatile uint32_t *atomic_bit_u8_t; +typedef volatile uint32_t *atomic_bit_u16_t; +typedef volatile uint32_t *atomic_bit_u32_t; +typedef volatile uint32_t *atomic_bit_u64_t; + +static inline void __attribute__((always_inline)) _bit_barrier_pre(void) +{ + __asm__ volatile ("" : : : "memory"); +} + +static inline void __attribute__((always_inline)) _bit_barrier_post(void) +{ + __asm__ volatile ("" : : : "memory"); +} + +static inline bool _is_addr_valid_for_bitbanding(void *_addr) +{ + /* SRAM bit-band region goes from 0x20000000 to 0x200fffff, + * peripheral bit-band region goes from 0x40000000 to 0x400fffff */ + uintptr_t addr = (uintptr_t)_addr; + if ((addr < 0x20000000UL) || (addr > 0x400fffffUL)) { + return false; + } + + if ((addr >= 0x200fffffUL) && (addr < 0x40000000UL)) { + return false; + } + + return true; +} + +static inline atomic_bit_u8_t atomic_bit_u8(uint8_t *dest, uint8_t bit) +{ + assert(_is_addr_valid_for_bitbanding(dest)); + return bitband_addr(dest, bit); +} + +static inline atomic_bit_u16_t atomic_bit_u16(uint16_t *dest, uint8_t bit) +{ + assert(_is_addr_valid_for_bitbanding(dest)); + return bitband_addr(dest, bit); +} + +static inline atomic_bit_u32_t atomic_bit_u32(uint32_t *dest, uint8_t bit) +{ + assert(_is_addr_valid_for_bitbanding(dest)); + return bitband_addr(dest, bit); +} + +static inline atomic_bit_u64_t atomic_bit_u64(uint64_t *dest, uint8_t bit) +{ + assert(_is_addr_valid_for_bitbanding(dest)); + return bitband_addr(dest, bit); +} + +static inline void atomic_set_bit_u8(atomic_bit_u8_t bit) +{ + _bit_barrier_pre(); + *bit = 1; + _bit_barrier_post(); +} + +static inline void atomic_set_bit_u16(atomic_bit_u16_t bit) +{ + _bit_barrier_pre(); + *bit = 1; + _bit_barrier_post(); +} + +static inline void atomic_set_bit_u32(atomic_bit_u32_t bit) +{ + _bit_barrier_pre(); + *bit = 1; + _bit_barrier_post(); +} + +static inline void atomic_set_bit_u64(atomic_bit_u64_t bit) +{ + _bit_barrier_pre(); + *bit = 1; + _bit_barrier_post(); +} + +static inline void atomic_clear_bit_u8(atomic_bit_u8_t bit) +{ + _bit_barrier_pre(); + *bit = 0; + _bit_barrier_post(); +} +static inline void atomic_clear_bit_u16(atomic_bit_u16_t bit) +{ + _bit_barrier_pre(); + *bit = 0; + _bit_barrier_post(); +} + +static inline void atomic_clear_bit_u32(atomic_bit_u32_t bit) +{ + _bit_barrier_pre(); + *bit = 0; + _bit_barrier_post(); +} + +static inline void atomic_clear_bit_u64(atomic_bit_u64_t bit) +{ + _bit_barrier_pre(); + *bit = 0; + _bit_barrier_post(); +} + + +#endif /* CPU_HAS_BITBAND */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ATOMIC_UTILS_ARCH_H */ +/** @} */ From 9c6aed75e65b1baa6c656aa5ca81f24c9f3d2ec1 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 28 Sep 2020 16:09:57 +0200 Subject: [PATCH 03/12] cpu/arm7_common: Add atomic_utils_arch.h --- cpu/arm7_common/include/atomic_utils_arch.h | 76 +++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 cpu/arm7_common/include/atomic_utils_arch.h diff --git a/cpu/arm7_common/include/atomic_utils_arch.h b/cpu/arm7_common/include/atomic_utils_arch.h new file mode 100644 index 0000000000..e31fab8012 --- /dev/null +++ b/cpu/arm7_common/include/atomic_utils_arch.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_arm7_common + * + * @{ + * + * @file + * @brief Implementation of fast atomic utility functions + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_ARCH_H +#define ATOMIC_UTILS_ARCH_H +#ifndef DOXYGEN + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* clang provides no built-in atomic access to regular variables */ +#ifndef __clang__ + +#define HAS_ATOMIC_LOAD_U8 +static inline uint8_t atomic_load_u8(const uint8_t *var) +{ + return __atomic_load_1(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U16 +static inline uint16_t atomic_load_u16(const uint16_t *var) +{ + return __atomic_load_2(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U32 +static inline uint32_t atomic_load_u32(const uint32_t *var) +{ + return __atomic_load_4(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U8 +static inline void atomic_store_u8(uint8_t *dest, uint8_t val) +{ + __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U16 +static inline void atomic_store_u16(uint16_t *dest, uint16_t val) +{ + __atomic_store_2(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U32 +static inline void atomic_store_u32(uint32_t *dest, uint32_t val) +{ + __atomic_store_4(dest, val, __ATOMIC_SEQ_CST); +} + +#endif /* __clang__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ATOMIC_UTILS_ARCH_H */ +/** @} */ From ed6b88d5c467a0992eeb2349e8e735d915091f8f Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 28 Sep 2020 16:11:05 +0200 Subject: [PATCH 04/12] cpu/esp_common: Add atomic_utils_arch.h --- cpu/esp_common/include/atomic_utils_arch.h | 76 ++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 cpu/esp_common/include/atomic_utils_arch.h diff --git a/cpu/esp_common/include/atomic_utils_arch.h b/cpu/esp_common/include/atomic_utils_arch.h new file mode 100644 index 0000000000..99520a0351 --- /dev/null +++ b/cpu/esp_common/include/atomic_utils_arch.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_esp_common + * + * @{ + * + * @file + * @brief Implementation of fast atomic utility functions + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_ARCH_H +#define ATOMIC_UTILS_ARCH_H +#ifndef DOXYGEN + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* clang provides no built-in atomic access to regular variables */ +#ifndef __clang__ + +#define HAS_ATOMIC_LOAD_U8 +static inline uint8_t atomic_load_u8(const uint8_t *var) +{ + return __atomic_load_1(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U16 +static inline uint16_t atomic_load_u16(const uint16_t *var) +{ + return __atomic_load_2(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U32 +static inline uint32_t atomic_load_u32(const uint32_t *var) +{ + return __atomic_load_4(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U8 +static inline void atomic_store_u8(uint8_t *dest, uint8_t val) +{ + __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U16 +static inline void atomic_store_u16(uint16_t *dest, uint16_t val) +{ + __atomic_store_2(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U32 +static inline void atomic_store_u32(uint32_t *dest, uint32_t val) +{ + __atomic_store_4(dest, val, __ATOMIC_SEQ_CST); +} + +#endif /* __clang__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ATOMIC_UTILS_ARCH_H */ +/** @} */ From 1d2e0592d328e6c7623e675c6898ac04e258a345 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 28 Sep 2020 16:12:31 +0200 Subject: [PATCH 05/12] cpu/mips32r2_common: Add atomic_utils_arch.h --- .../include/atomic_utils_arch.h | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 cpu/mips32r2_common/include/atomic_utils_arch.h diff --git a/cpu/mips32r2_common/include/atomic_utils_arch.h b/cpu/mips32r2_common/include/atomic_utils_arch.h new file mode 100644 index 0000000000..e603ad2184 --- /dev/null +++ b/cpu/mips32r2_common/include/atomic_utils_arch.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_mips32r2_common + * + * @{ + * + * @file + * @brief Implementation of fast atomic utility functions + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_ARCH_H +#define ATOMIC_UTILS_ARCH_H +#ifndef DOXYGEN + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* clang provides no built-in atomic access to regular variables */ +#ifndef __clang__ + +#define HAS_ATOMIC_LOAD_U8 +static inline uint8_t atomic_load_u8(const uint8_t *var) +{ + return __atomic_load_1(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U16 +static inline uint16_t atomic_load_u16(const uint16_t *var) +{ + return __atomic_load_2(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U32 +static inline uint32_t atomic_load_u32(const uint32_t *var) +{ + return __atomic_load_4(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U8 +static inline void atomic_store_u8(uint8_t *dest, uint8_t val) +{ + __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U16 +static inline void atomic_store_u16(uint16_t *dest, uint16_t val) +{ + __atomic_store_2(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U32 +static inline void atomic_store_u32(uint32_t *dest, uint32_t val) +{ + __atomic_store_4(dest, val, __ATOMIC_SEQ_CST); +} + +#endif /* __clang__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ATOMIC_UTILS_ARCH_H */ +/** @} */ From ce0982485d0f710eb62d15b22465c4ef3be57370 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 28 Sep 2020 16:13:16 +0200 Subject: [PATCH 06/12] cpu/fe310: Add atomic_utils_arch.h --- cpu/fe310/include/atomic_utils_arch.h | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 cpu/fe310/include/atomic_utils_arch.h diff --git a/cpu/fe310/include/atomic_utils_arch.h b/cpu/fe310/include/atomic_utils_arch.h new file mode 100644 index 0000000000..cb7f1bdcb8 --- /dev/null +++ b/cpu/fe310/include/atomic_utils_arch.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_fe310 + * + * @{ + * + * @file + * @brief Implementation of fast atomic utility functions + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_ARCH_H +#define ATOMIC_UTILS_ARCH_H +#ifndef DOXYGEN + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* clang provides no built-in atomic access to regular variables */ +#ifndef __clang__ + +#define HAS_ATOMIC_LOAD_U8 +static inline uint8_t atomic_load_u8(const uint8_t *var) +{ + return __atomic_load_1(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U16 +static inline uint16_t atomic_load_u16(const uint16_t *var) +{ + return __atomic_load_2(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U32 +static inline uint32_t atomic_load_u32(const uint32_t *var) +{ + return __atomic_load_4(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U8 +static inline void atomic_store_u8(uint8_t *dest, uint8_t val) +{ + __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U16 +static inline void atomic_store_u16(uint16_t *dest, uint16_t val) +{ + __atomic_store_2(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U32 +static inline void atomic_store_u32(uint32_t *dest, uint32_t val) +{ + __atomic_store_4(dest, val, __ATOMIC_SEQ_CST); +} + +#endif /* __clang__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ATOMIC_UTILS_ARCH_H */ +/** @} */ From 3f4577d430cdd05dabb4899e1477b21b04280f3c Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 28 Sep 2020 16:14:25 +0200 Subject: [PATCH 07/12] cpu/msp430_common: Add atomic_utils_arch.h --- cpu/msp430_common/include/atomic_utils_arch.h | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 cpu/msp430_common/include/atomic_utils_arch.h diff --git a/cpu/msp430_common/include/atomic_utils_arch.h b/cpu/msp430_common/include/atomic_utils_arch.h new file mode 100644 index 0000000000..59c5c8ca36 --- /dev/null +++ b/cpu/msp430_common/include/atomic_utils_arch.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_msp430_common + * + * @{ + * + * @file + * @brief Implementation of fast atomic utility functions + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_ARCH_H +#define ATOMIC_UTILS_ARCH_H +#ifndef DOXYGEN + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* clang provides no built-in atomic access to regular variables */ +#ifndef __clang__ + +#define HAS_ATOMIC_LOAD_U8 +static inline uint8_t atomic_load_u8(const uint8_t *var) +{ + return __atomic_load_1(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U16 +static inline uint16_t atomic_load_u16(const uint16_t *var) +{ + return __atomic_load_2(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U8 +static inline void atomic_store_u8(uint8_t *dest, uint8_t val) +{ + __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U16 +static inline void atomic_store_u16(uint16_t *dest, uint16_t val) +{ + __atomic_store_2(dest, val, __ATOMIC_SEQ_CST); +} + +#endif /* __clang__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ATOMIC_UTILS_ARCH_H */ +/** @} */ From a892c1aa23c32b95af9d3c8aca1af9ad77dacdf9 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 28 Sep 2020 16:15:10 +0200 Subject: [PATCH 08/12] cpu/atmega_common: Add atomic_utils_arch.h --- cpu/atmega_common/include/atomic_utils_arch.h | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 cpu/atmega_common/include/atomic_utils_arch.h diff --git a/cpu/atmega_common/include/atomic_utils_arch.h b/cpu/atmega_common/include/atomic_utils_arch.h new file mode 100644 index 0000000000..7582ce4adb --- /dev/null +++ b/cpu/atmega_common/include/atomic_utils_arch.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_atmega_common + * + * @{ + * + * @file + * @brief Implementation of fast atomic utility functions + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_ARCH_H +#define ATOMIC_UTILS_ARCH_H +#ifndef DOXYGEN + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* clang provides no built-in atomic access to regular variables */ +#ifndef __clang__ + +#define HAS_ATOMIC_LOAD_U8 +static inline uint8_t atomic_load_u8(const uint8_t *var) +{ + return __atomic_load_1(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U8 +static inline void atomic_store_u8(uint8_t *dest, uint8_t val) +{ + __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); +} + +#endif /* __clang__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ATOMIC_UTILS_ARCH_H */ +/** @} */ From 56a54a773ea2150a2612bc40a4c36b956570097a Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 28 Sep 2020 16:38:35 +0200 Subject: [PATCH 09/12] cpu/native: Add atomic_utils_arch.h --- cpu/native/include/atomic_utils_arch.h | 76 ++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 cpu/native/include/atomic_utils_arch.h diff --git a/cpu/native/include/atomic_utils_arch.h b/cpu/native/include/atomic_utils_arch.h new file mode 100644 index 0000000000..6991fb8fd4 --- /dev/null +++ b/cpu/native/include/atomic_utils_arch.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_native + * + * @{ + * + * @file + * @brief Implementation of fast atomic utility functions + * @author Marian Buschsieweke + */ + +#ifndef ATOMIC_UTILS_ARCH_H +#define ATOMIC_UTILS_ARCH_H +#ifndef DOXYGEN + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* clang provides no built-in atomic access to regular variables */ +#ifndef __clang__ + +#define HAS_ATOMIC_LOAD_U8 +static inline uint8_t atomic_load_u8(const uint8_t *var) +{ + return __atomic_load_1(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U16 +static inline uint16_t atomic_load_u16(const uint16_t *var) +{ + return __atomic_load_2(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_LOAD_U32 +static inline uint32_t atomic_load_u32(const uint32_t *var) +{ + return __atomic_load_4(var, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U8 +static inline void atomic_store_u8(uint8_t *dest, uint8_t val) +{ + __atomic_store_1(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U16 +static inline void atomic_store_u16(uint16_t *dest, uint16_t val) +{ + __atomic_store_2(dest, val, __ATOMIC_SEQ_CST); +} + +#define HAS_ATOMIC_STORE_U32 +static inline void atomic_store_u32(uint32_t *dest, uint32_t val) +{ + __atomic_store_4(dest, val, __ATOMIC_SEQ_CST); +} + +#endif /* __clang__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ATOMIC_UTILS_ARCH_H */ +/** @} */ From 23c91db0b5de3b1817b432c7c88cf10bed52dffb Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 22 Jun 2020 22:16:42 +0200 Subject: [PATCH 10/12] tests: Add benchmark for sys/atomic_utils --- tests/bench_sys_atomic_utils/Makefile | 7 + tests/bench_sys_atomic_utils/Makefile.ci | 3 + tests/bench_sys_atomic_utils/README.md | 18 + tests/bench_sys_atomic_utils/main.c | 504 +++++++++++++++++++++++ 4 files changed, 532 insertions(+) create mode 100644 tests/bench_sys_atomic_utils/Makefile create mode 100644 tests/bench_sys_atomic_utils/Makefile.ci create mode 100644 tests/bench_sys_atomic_utils/README.md create mode 100644 tests/bench_sys_atomic_utils/main.c diff --git a/tests/bench_sys_atomic_utils/Makefile b/tests/bench_sys_atomic_utils/Makefile new file mode 100644 index 0000000000..dc9d10e24a --- /dev/null +++ b/tests/bench_sys_atomic_utils/Makefile @@ -0,0 +1,7 @@ +include ../Makefile.tests_common + +USEMODULE += xtimer +USEMODULE += atomic_utils +USEMODULE += test_utils_interactive_sync + +include $(RIOTBASE)/Makefile.include diff --git a/tests/bench_sys_atomic_utils/Makefile.ci b/tests/bench_sys_atomic_utils/Makefile.ci new file mode 100644 index 0000000000..518b330a9e --- /dev/null +++ b/tests/bench_sys_atomic_utils/Makefile.ci @@ -0,0 +1,3 @@ +BOARD_INSUFFICIENT_MEMORY := \ + stm32f030f4-demo \ + # diff --git a/tests/bench_sys_atomic_utils/README.md b/tests/bench_sys_atomic_utils/README.md new file mode 100644 index 0000000000..3c29cdd6d8 --- /dev/null +++ b/tests/bench_sys_atomic_utils/README.md @@ -0,0 +1,18 @@ +# Benchmark for `sys/atomic_utils` + +This application will perform 100.000 repetitions (or 1.000.000 on +Cortex-M7 and ESP32) for each atomic operation and will print the total time it +took in a table. For comparison, the speed of C11 atomics and plain `volatile` +accesses are also printed. + +## Expectations + +Lower is better! + +Plain `volatile` accesses are not atomic, and therefore should perform faster +than actual atomic operations. If atomic operations turn out to be faster +(by more than rounding errors and noise), something is odd. + +The `atomic_utils` aim to be at least as fast as C11 atomics and often faster. +If the `atomic_utils` implementation performs slower than C11 atomics, you have +found potential for further optimization. diff --git a/tests/bench_sys_atomic_utils/main.c b/tests/bench_sys_atomic_utils/main.c new file mode 100644 index 0000000000..e15d4fb231 --- /dev/null +++ b/tests/bench_sys_atomic_utils/main.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Atomic util benchmark + * + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include +#include + +#include "atomic_utils.h" +#include "xtimer.h" + +/* On fast CPUs: 1.000.000 loops */ +#if defined(CPU_CORE_CORTEX_M7) || defined(CPU_ESP32) +#define LOOPS 1000000 +#else +/* Else 100.000 loops */ +#define LOOPS 100000 +#endif + +#define CONCAT(a, b) a ## b +#define CONCAT3(a, b, c) a ## b ## c +#define CONCAT4(a, b, c, d) a ## b ## c ## d + +enum { + IMPL_VOLATILE, + IMPL_ATOMIC_UTIL, + IMPL_C11_ATOMIC, + IMPL_NUMOF +}; + +#define BENCH_ATOMIC_STORE(name, type, c11type) \ + static void CONCAT(bench_atomic_store_, name)(uint32_t *result_us) \ + { \ + uint32_t start, stop; \ + \ + { \ + volatile type val; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + val = 42; \ + } \ + (void)val; \ + stop = xtimer_now_usec(); \ + result_us[IMPL_VOLATILE] = stop - start; \ + } \ + \ + { \ + type val; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + CONCAT(atomic_store_, name)(&val, 42); \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_ATOMIC_UTIL] = stop - start; \ + } \ + \ + { \ + c11type val; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + atomic_store(&val, 42); \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_C11_ATOMIC] = stop - start; \ + } \ + } +BENCH_ATOMIC_STORE(u8, uint8_t, atomic_uint_least8_t) +BENCH_ATOMIC_STORE(u16, uint16_t, atomic_uint_least16_t) +BENCH_ATOMIC_STORE(u32, uint32_t, atomic_uint_least32_t) +BENCH_ATOMIC_STORE(u64, uint64_t, atomic_uint_least64_t) + +#define BENCH_ATOMIC_LOAD(name, type, c11type) \ + static void CONCAT(bench_atomic_load_, name)(uint32_t *result_us) \ + { \ + uint32_t start, stop; \ + \ + { \ + volatile type val = 0; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + type tmp = val; \ + (void)tmp; \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_VOLATILE] = stop - start; \ + } \ + \ + { \ + type val = 0; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + type tmp = CONCAT(atomic_load_, name)(&val); \ + (void)tmp; \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_ATOMIC_UTIL] = stop - start; \ + } \ + \ + { \ + c11type val = ATOMIC_VAR_INIT(0); \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + type tmp = atomic_load(&val); \ + (void)tmp; \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_C11_ATOMIC] = stop - start; \ + } \ + } +BENCH_ATOMIC_LOAD(u8, uint8_t, atomic_uint_least8_t) +BENCH_ATOMIC_LOAD(u16, uint16_t, atomic_uint_least16_t) +BENCH_ATOMIC_LOAD(u32, uint32_t, atomic_uint_least32_t) +BENCH_ATOMIC_LOAD(u64, uint64_t, atomic_uint_least64_t) + +#define BENCH_ATOMIC_FETCH_OP(opname, op, name, type, c11type) \ + static void CONCAT4(bench_atomic_fetch_, opname, _, name)(uint32_t *result_us) \ + { \ + uint32_t start, stop; \ + \ + { \ + volatile type val = 0; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + val = val op 1; \ + } \ + (void)val; \ + stop = xtimer_now_usec(); \ + result_us[IMPL_VOLATILE] = stop - start; \ + } \ + \ + { \ + type val = 0; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + CONCAT4(atomic_fetch_, opname, _, name)(&val, 1); \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_ATOMIC_UTIL] = stop - start; \ + } \ + \ + { \ + c11type val = ATOMIC_VAR_INIT(0); \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + CONCAT(atomic_fetch_, opname)(&val, 1); \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_C11_ATOMIC] = stop - start; \ + } \ + } +BENCH_ATOMIC_FETCH_OP(add, +, u8, uint8_t, atomic_uint_least8_t) +BENCH_ATOMIC_FETCH_OP(add, +, u16, uint16_t, atomic_uint_least16_t) +BENCH_ATOMIC_FETCH_OP(add, +, u32, uint32_t, atomic_uint_least32_t) +BENCH_ATOMIC_FETCH_OP(add, +, u64, uint64_t, atomic_uint_least64_t) +BENCH_ATOMIC_FETCH_OP(sub, -, u8, uint8_t, atomic_uint_least8_t) +BENCH_ATOMIC_FETCH_OP(sub, -, u16, uint16_t, atomic_uint_least16_t) +BENCH_ATOMIC_FETCH_OP(sub, -, u32, uint32_t, atomic_uint_least32_t) +BENCH_ATOMIC_FETCH_OP(sub, -, u64, uint64_t, atomic_uint_least64_t) +BENCH_ATOMIC_FETCH_OP(or, |, u8, uint8_t, atomic_uint_least8_t) +BENCH_ATOMIC_FETCH_OP(or, |, u16, uint16_t, atomic_uint_least16_t) +BENCH_ATOMIC_FETCH_OP(or, |, u32, uint32_t, atomic_uint_least32_t) +BENCH_ATOMIC_FETCH_OP(or, |, u64, uint64_t, atomic_uint_least64_t) +BENCH_ATOMIC_FETCH_OP(xor, ^, u8, uint8_t, atomic_uint_least8_t) +BENCH_ATOMIC_FETCH_OP(xor, ^, u16, uint16_t, atomic_uint_least16_t) +BENCH_ATOMIC_FETCH_OP(xor, ^, u32, uint32_t, atomic_uint_least32_t) +BENCH_ATOMIC_FETCH_OP(xor, ^, u64, uint64_t, atomic_uint_least64_t) +BENCH_ATOMIC_FETCH_OP(and, &, u8, uint8_t, atomic_uint_least8_t) +BENCH_ATOMIC_FETCH_OP(and, &, u16, uint16_t, atomic_uint_least16_t) +BENCH_ATOMIC_FETCH_OP(and, &, u32, uint32_t, atomic_uint_least32_t) +BENCH_ATOMIC_FETCH_OP(and, &, u64, uint64_t, atomic_uint_least64_t) + +#define BENCH_ATOMIC_SET_CLEAR_BIT(name, type, c11type, opname, set_or_clear) \ + static void CONCAT4(bench_atomic_, opname, _bit_, name)(uint32_t *result_us) \ + { \ + uint32_t start, stop; \ + static const uint8_t _bit = 5; \ + type mask = ((type)1) << _bit; \ + \ + { \ + volatile type val = 0; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + if (set_or_clear) { \ + val |= mask; \ + } \ + else { \ + val &= ~(mask); \ + } \ + } \ + (void)val; \ + stop = xtimer_now_usec(); \ + result_us[IMPL_VOLATILE] = stop - start; \ + } \ + \ + { \ + static type val = 0; \ + CONCAT3(atomic_bit_, name, _t) bit = \ + CONCAT(atomic_bit_, name)(&val, _bit); \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + CONCAT4(atomic_, opname, _bit_, name)(bit); \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_ATOMIC_UTIL] = stop - start; \ + } \ + \ + { \ + c11type val = ATOMIC_VAR_INIT(0); \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + if (set_or_clear) { \ + atomic_fetch_or(&val, mask); \ + } \ + else { \ + atomic_fetch_and(&val, ~(mask)); \ + } \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_C11_ATOMIC] = stop - start; \ + } \ + } +BENCH_ATOMIC_SET_CLEAR_BIT(u8, uint8_t, atomic_uint_least8_t, set, 1) +BENCH_ATOMIC_SET_CLEAR_BIT(u16, uint16_t, atomic_uint_least16_t, set, 1) +BENCH_ATOMIC_SET_CLEAR_BIT(u32, uint32_t, atomic_uint_least32_t, set, 1) +BENCH_ATOMIC_SET_CLEAR_BIT(u64, uint64_t, atomic_uint_least64_t, set, 1) +BENCH_ATOMIC_SET_CLEAR_BIT(u8, uint8_t, atomic_uint_least8_t, clear, 0) +BENCH_ATOMIC_SET_CLEAR_BIT(u16, uint16_t, atomic_uint_least16_t, clear, 0) +BENCH_ATOMIC_SET_CLEAR_BIT(u32, uint32_t, atomic_uint_least32_t, clear, 0) +BENCH_ATOMIC_SET_CLEAR_BIT(u64, uint64_t, atomic_uint_least64_t, clear, 0) + +#define BENCH_SEMI_ATOMIC_FETCH_OP(opname, op, name, type, c11type) \ + static void CONCAT4(bench_semi_atomic_fetch_, opname, _, name)(uint32_t *result_us) \ + { \ + uint32_t start, stop; \ + \ + { \ + volatile type val = 0; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + val = val op 1; \ + } \ + (void)val; \ + stop = xtimer_now_usec(); \ + result_us[IMPL_VOLATILE] = stop - start; \ + } \ + \ + { \ + type val = 0; \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + CONCAT4(semi_atomic_fetch_, opname, _, name)(&val, 1); \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_ATOMIC_UTIL] = stop - start; \ + } \ + \ + { \ + c11type val = ATOMIC_VAR_INIT(0); \ + start = xtimer_now_usec(); \ + for (uint32_t i = 0; i < LOOPS; i++) { \ + CONCAT(atomic_fetch_, opname)(&val, 1); \ + } \ + stop = xtimer_now_usec(); \ + result_us[IMPL_C11_ATOMIC] = stop - start; \ + } \ + } +BENCH_SEMI_ATOMIC_FETCH_OP(add, +, u8, uint8_t, atomic_uint_least8_t) +BENCH_SEMI_ATOMIC_FETCH_OP(add, +, u16, uint16_t, atomic_uint_least16_t) +BENCH_SEMI_ATOMIC_FETCH_OP(add, +, u32, uint32_t, atomic_uint_least32_t) +BENCH_SEMI_ATOMIC_FETCH_OP(add, +, u64, uint64_t, atomic_uint_least64_t) +BENCH_SEMI_ATOMIC_FETCH_OP(sub, -, u8, uint8_t, atomic_uint_least8_t) +BENCH_SEMI_ATOMIC_FETCH_OP(sub, -, u16, uint16_t, atomic_uint_least16_t) +BENCH_SEMI_ATOMIC_FETCH_OP(sub, -, u32, uint32_t, atomic_uint_least32_t) +BENCH_SEMI_ATOMIC_FETCH_OP(sub, -, u64, uint64_t, atomic_uint_least64_t) +BENCH_SEMI_ATOMIC_FETCH_OP(or, |, u8, uint8_t, atomic_uint_least8_t) +BENCH_SEMI_ATOMIC_FETCH_OP(or, |, u16, uint16_t, atomic_uint_least16_t) +BENCH_SEMI_ATOMIC_FETCH_OP(or, |, u32, uint32_t, atomic_uint_least32_t) +BENCH_SEMI_ATOMIC_FETCH_OP(or, |, u64, uint64_t, atomic_uint_least64_t) +BENCH_SEMI_ATOMIC_FETCH_OP(xor, ^, u8, uint8_t, atomic_uint_least8_t) +BENCH_SEMI_ATOMIC_FETCH_OP(xor, ^, u16, uint16_t, atomic_uint_least16_t) +BENCH_SEMI_ATOMIC_FETCH_OP(xor, ^, u32, uint32_t, atomic_uint_least32_t) +BENCH_SEMI_ATOMIC_FETCH_OP(xor, ^, u64, uint64_t, atomic_uint_least64_t) +BENCH_SEMI_ATOMIC_FETCH_OP(and, &, u8, uint8_t, atomic_uint_least8_t) +BENCH_SEMI_ATOMIC_FETCH_OP(and, &, u16, uint16_t, atomic_uint_least16_t) +BENCH_SEMI_ATOMIC_FETCH_OP(and, &, u32, uint32_t, atomic_uint_least32_t) +BENCH_SEMI_ATOMIC_FETCH_OP(and, &, u64, uint64_t, atomic_uint_least64_t) + +#define LINE "+------+----------+------+------------------+" \ + "------------------+------------------+" +#define FMT "| %4s | %8s | %4u | %13" PRIu32 " µs | %13" PRIu32 " µs | " \ + "%13" PRIu32 " µs |\n" +int main(void) +{ + uint32_t results[IMPL_NUMOF]; + + puts("Note: LOWER IS BETTER!\n"); + puts(LINE); + printf("| %4s | %8s | %4s | %16s | %16s | %16s |\n", + "mode", "op", "bits", "volatile", "atomic util", "c11 atomic"); + puts(LINE); + + bench_atomic_store_u8(results); + printf(FMT, "atom", "store", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_store_u16(results); + printf(FMT, "atom", "store", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_store_u32(results); + printf(FMT, "atom", "store", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_store_u64(results); + printf(FMT, "atom", "store", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_atomic_load_u8(results); + printf(FMT, "atom", "load", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_load_u16(results); + printf(FMT, "atom", "load", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_load_u32(results); + printf(FMT, "atom", "load", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_load_u64(results); + printf(FMT, "atom", "load", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + /* atomic read-modify-write operations */ + bench_atomic_fetch_add_u8(results); + printf(FMT, "atom", "add", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_add_u16(results); + printf(FMT, "atom", "add", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_add_u32(results); + printf(FMT, "atom", "add", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_add_u64(results); + printf(FMT, "atom", "add", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_atomic_fetch_sub_u8(results); + printf(FMT, "atom", "sub", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_sub_u16(results); + printf(FMT, "atom", "sub", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_sub_u32(results); + printf(FMT, "atom", "sub", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_sub_u64(results); + printf(FMT, "atom", "sub", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_atomic_fetch_or_u8(results); + printf(FMT, "atom", "or", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_or_u16(results); + printf(FMT, "atom", "or", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_or_u32(results); + printf(FMT, "atom", "or", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_or_u64(results); + printf(FMT, "atom", "or", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_atomic_fetch_xor_u8(results); + printf(FMT, "atom", "xor", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_xor_u16(results); + printf(FMT, "atom", "xor", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_xor_u32(results); + printf(FMT, "atom", "xor", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_xor_u64(results); + printf(FMT, "atom", "xor", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_atomic_fetch_and_u8(results); + printf(FMT, "atom", "and", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_and_u16(results); + printf(FMT, "atom", "and", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_and_u32(results); + printf(FMT, "atom", "and", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_fetch_and_u64(results); + printf(FMT, "atom", "and", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + /* atomic bit setting and clearing */ + bench_atomic_set_bit_u8(results); + printf(FMT, "atom", "set", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_set_bit_u16(results); + printf(FMT, "atom", "set", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_set_bit_u32(results); + printf(FMT, "atom", "set", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_set_bit_u64(results); + printf(FMT, "atom", "set", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_atomic_clear_bit_u8(results); + printf(FMT, "atom", "clear", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_clear_bit_u16(results); + printf(FMT, "atom", "clear", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_clear_bit_u32(results); + printf(FMT, "atom", "clear", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_atomic_clear_bit_u64(results); + printf(FMT, "atom", "clear", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + /* semi-atomic read-modify-write operations */ + bench_semi_atomic_fetch_add_u8(results); + printf(FMT, "semi", "add", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_add_u16(results); + printf(FMT, "semi", "add", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_add_u32(results); + printf(FMT, "semi", "add", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_add_u64(results); + printf(FMT, "semi", "add", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_semi_atomic_fetch_sub_u8(results); + printf(FMT, "semi", "sub", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_sub_u16(results); + printf(FMT, "semi", "sub", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_sub_u32(results); + printf(FMT, "semi", "sub", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_sub_u64(results); + printf(FMT, "semi", "sub", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_semi_atomic_fetch_or_u8(results); + printf(FMT, "semi", "or", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_or_u16(results); + printf(FMT, "semi", "or", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_or_u32(results); + printf(FMT, "semi", "or", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_or_u64(results); + printf(FMT, "semi", "or", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_semi_atomic_fetch_xor_u8(results); + printf(FMT, "semi", "xor", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_xor_u16(results); + printf(FMT, "semi", "xor", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_xor_u32(results); + printf(FMT, "semi", "xor", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_xor_u64(results); + printf(FMT, "semi", "xor", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + + bench_semi_atomic_fetch_and_u8(results); + printf(FMT, "semi", "and", 8, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_and_u16(results); + printf(FMT, "semi", "and", 16, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_and_u32(results); + printf(FMT, "semi", "and", 32, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + bench_semi_atomic_fetch_and_u64(results); + printf(FMT, "semi", "and", 64, results[IMPL_VOLATILE], + results[IMPL_ATOMIC_UTIL], results[IMPL_C11_ATOMIC]); + puts(LINE); + return 0; +} From 07fc051e979149a0187c1fba1e874d37e4df0a3c Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Wed, 24 Jun 2020 21:32:32 +0200 Subject: [PATCH 11/12] tests/unittests: Added unit test for atomic_utils --- tests/unittests/Makefile.ci | 3 + tests/unittests/tests-atomic_utils/Makefile | 1 + .../tests-atomic_utils/Makefile.include | 2 + .../tests-atomic_utils/tests-atomic_utils.c | 286 ++++++++++++++++++ .../tests-atomic_utils/tests-atomic_utils.h | 38 +++ 5 files changed, 330 insertions(+) create mode 100644 tests/unittests/tests-atomic_utils/Makefile create mode 100644 tests/unittests/tests-atomic_utils/Makefile.include create mode 100644 tests/unittests/tests-atomic_utils/tests-atomic_utils.c create mode 100644 tests/unittests/tests-atomic_utils/tests-atomic_utils.h diff --git a/tests/unittests/Makefile.ci b/tests/unittests/Makefile.ci index d1bcfc3b7b..72e4481ef3 100644 --- a/tests/unittests/Makefile.ci +++ b/tests/unittests/Makefile.ci @@ -18,6 +18,9 @@ BOARD_INSUFFICIENT_MEMORY := \ bluepill \ bluepill-128kib \ calliope-mini \ + cc1312-launchpad \ + cc1352-launchpad \ + cc1352p-launchpad \ cc2650-launchpad \ cc2650stk \ derfmega128 \ diff --git a/tests/unittests/tests-atomic_utils/Makefile b/tests/unittests/tests-atomic_utils/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/tests/unittests/tests-atomic_utils/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-atomic_utils/Makefile.include b/tests/unittests/tests-atomic_utils/Makefile.include new file mode 100644 index 0000000000..527faa4e06 --- /dev/null +++ b/tests/unittests/tests-atomic_utils/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE += atomic_utils +USEMODULE += random # <-- used for input data to operate on diff --git a/tests/unittests/tests-atomic_utils/tests-atomic_utils.c b/tests/unittests/tests-atomic_utils/tests-atomic_utils.c new file mode 100644 index 0000000000..ab1a6cb559 --- /dev/null +++ b/tests/unittests/tests-atomic_utils/tests-atomic_utils.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @brief Unittests for the atomic utils module + * + * @author Marian Buschsieweke + * + * @} + */ + +#include + +#include "embUnit.h" +#include "tests-atomic_utils.h" + +#include "atomic_utils.h" +#include "random.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +typedef void (*fetch_op_u8_t)(uint8_t *dest, uint8_t val); +typedef void (*fetch_op_u16_t)(uint16_t *dest, uint16_t val); +typedef void (*fetch_op_u32_t)(uint32_t *dest, uint32_t val); +typedef void (*fetch_op_u64_t)(uint64_t *dest, uint64_t val); + +static void fetch_add_u8(uint8_t *dest, uint8_t val){ *dest += val; } +static void fetch_add_u16(uint16_t *dest, uint16_t val){ *dest += val; } +static void fetch_add_u32(uint32_t *dest, uint32_t val){ *dest += val; } +static void fetch_add_u64(uint64_t *dest, uint64_t val){ *dest += val; } + +static void fetch_sub_u8(uint8_t *dest, uint8_t val){ *dest -= val; } +static void fetch_sub_u16(uint16_t *dest, uint16_t val){ *dest -= val; } +static void fetch_sub_u32(uint32_t *dest, uint32_t val){ *dest -= val; } +static void fetch_sub_u64(uint64_t *dest, uint64_t val){ *dest -= val; } + +static void fetch_or_u8(uint8_t *dest, uint8_t val){ *dest |= val; } +static void fetch_or_u16(uint16_t *dest, uint16_t val){ *dest |= val; } +static void fetch_or_u32(uint32_t *dest, uint32_t val){ *dest |= val; } +static void fetch_or_u64(uint64_t *dest, uint64_t val){ *dest |= val; } + +static void fetch_xor_u8(uint8_t *dest, uint8_t val){ *dest ^= val; } +static void fetch_xor_u16(uint16_t *dest, uint16_t val){ *dest ^= val; } +static void fetch_xor_u32(uint32_t *dest, uint32_t val){ *dest ^= val; } +static void fetch_xor_u64(uint64_t *dest, uint64_t val){ *dest ^= val; } + +static void fetch_and_u8(uint8_t *dest, uint8_t val){ *dest &= val; } +static void fetch_and_u16(uint16_t *dest, uint16_t val){ *dest &= val; } +static void fetch_and_u32(uint32_t *dest, uint32_t val){ *dest &= val; } +static void fetch_and_u64(uint64_t *dest, uint64_t val){ *dest &= val; } + +static void test_load_store(void) +{ + uint8_t u8 = 42; + atomic_store_u8(&u8, 13); + TEST_ASSERT_EQUAL_INT(atomic_load_u8(&u8), 13); + + uint16_t u16 = 42; + atomic_store_u16(&u16, 1337); + TEST_ASSERT_EQUAL_INT(atomic_load_u16(&u16), 1337); + + uint32_t u32 = 42; + atomic_store_u32(&u32, 0x13371337); + TEST_ASSERT_EQUAL_INT(atomic_load_u32(&u32), 0x13371337); + + uint64_t u64 = 42; + atomic_store_u64(&u64, 0x1337133713371337); + TEST_ASSERT_EQUAL_INT(atomic_load_u64(&u64), 0x1337133713371337); +} + +static void test_fetch_op_u8(fetch_op_u8_t atomic_op, fetch_op_u8_t op) +{ + uint8_t state1 = 0, state2 = 0; + uint8_t i = 0; + do { + atomic_op(&state1, i); + op(&state2, i); + TEST_ASSERT_EQUAL_INT(state1, state2); + i++; + } while (i); +} + +static void test_fetch_ops_u8(void) +{ + test_fetch_op_u8(atomic_fetch_add_u8, fetch_add_u8); + test_fetch_op_u8(atomic_fetch_sub_u8, fetch_sub_u8); + test_fetch_op_u8(atomic_fetch_or_u8, fetch_or_u8); + test_fetch_op_u8(atomic_fetch_xor_u8, fetch_xor_u8); + test_fetch_op_u8(atomic_fetch_and_u8, fetch_and_u8); + test_fetch_op_u8(semi_atomic_fetch_add_u8, fetch_add_u8); + test_fetch_op_u8(semi_atomic_fetch_sub_u8, fetch_sub_u8); + test_fetch_op_u8(semi_atomic_fetch_or_u8, fetch_or_u8); + test_fetch_op_u8(semi_atomic_fetch_xor_u8, fetch_xor_u8); + test_fetch_op_u8(semi_atomic_fetch_and_u8, fetch_and_u8); +} + +static void test_fetch_op_u16(fetch_op_u16_t atomic_op, fetch_op_u16_t op) +{ + uint16_t state1 = 0, state2 = 0; + uint8_t i = 0; + do { + uint16_t num = random_uint32(); + atomic_op(&state1, num); + op(&state2, num); + TEST_ASSERT_EQUAL_INT(state1, state2); + i++; + } while (i); +} + +static void test_fetch_ops_u16(void) +{ + test_fetch_op_u16(atomic_fetch_add_u16, fetch_add_u16); + test_fetch_op_u16(atomic_fetch_sub_u16, fetch_sub_u16); + test_fetch_op_u16(atomic_fetch_or_u16, fetch_or_u16); + test_fetch_op_u16(atomic_fetch_xor_u16, fetch_xor_u16); + test_fetch_op_u16(atomic_fetch_and_u16, fetch_and_u16); + test_fetch_op_u16(semi_atomic_fetch_add_u16, fetch_add_u16); + test_fetch_op_u16(semi_atomic_fetch_sub_u16, fetch_sub_u16); + test_fetch_op_u16(semi_atomic_fetch_or_u16, fetch_or_u16); + test_fetch_op_u16(semi_atomic_fetch_xor_u16, fetch_xor_u16); + test_fetch_op_u16(semi_atomic_fetch_and_u16, fetch_and_u16); +} + +static void test_fetch_op_u32(fetch_op_u32_t atomic_op, fetch_op_u32_t op) +{ + uint32_t state1 = 0, state2 = 0; + uint8_t i = 0; + do { + uint32_t num = random_uint32(); + atomic_op(&state1, num); + op(&state2, num); + TEST_ASSERT_EQUAL_INT(state1, state2); + i++; + } while (i); +} + +static void test_fetch_ops_u32(void) +{ + test_fetch_op_u32(atomic_fetch_add_u32, fetch_add_u32); + test_fetch_op_u32(atomic_fetch_sub_u32, fetch_sub_u32); + test_fetch_op_u32(atomic_fetch_or_u32, fetch_or_u32); + test_fetch_op_u32(atomic_fetch_xor_u32, fetch_xor_u32); + test_fetch_op_u32(atomic_fetch_and_u32, fetch_and_u32); + test_fetch_op_u32(semi_atomic_fetch_add_u32, fetch_add_u32); + test_fetch_op_u32(semi_atomic_fetch_sub_u32, fetch_sub_u32); + test_fetch_op_u32(semi_atomic_fetch_or_u32, fetch_or_u32); + test_fetch_op_u32(semi_atomic_fetch_xor_u32, fetch_xor_u32); + test_fetch_op_u32(semi_atomic_fetch_and_u32, fetch_and_u32); +} + +static void test_fetch_op_u64(fetch_op_u64_t atomic_op, fetch_op_u64_t op) +{ + uint64_t state1 = 0, state2 = 0; + uint8_t i = 0; + do { + uint64_t num; + random_bytes((void *)&num, sizeof(num)); + atomic_op(&state1, num); + op(&state2, num); + TEST_ASSERT_EQUAL_INT(state1, state2); + i++; + } while (i); +} + +static void test_fetch_ops_u64(void) +{ + test_fetch_op_u64(atomic_fetch_add_u64, fetch_add_u64); + test_fetch_op_u64(atomic_fetch_sub_u64, fetch_sub_u64); + test_fetch_op_u64(atomic_fetch_or_u64, fetch_or_u64); + test_fetch_op_u64(atomic_fetch_xor_u64, fetch_xor_u64); + test_fetch_op_u64(atomic_fetch_and_u64, fetch_and_u64); + test_fetch_op_u64(semi_atomic_fetch_add_u64, fetch_add_u64); + test_fetch_op_u64(semi_atomic_fetch_sub_u64, fetch_sub_u64); + test_fetch_op_u64(semi_atomic_fetch_or_u64, fetch_or_u64); + test_fetch_op_u64(semi_atomic_fetch_xor_u64, fetch_xor_u64); + test_fetch_op_u64(semi_atomic_fetch_and_u64, fetch_and_u64); +} + +static void test_atomic_set_bit(void) +{ + { + uint8_t val1 = 0, val2 = 0; + for (uint8_t i = 0; i < 8; i++) { + atomic_set_bit_u8(atomic_bit_u8(&val1, i)); + val2 |= 1ULL << i; + TEST_ASSERT_EQUAL_INT(val2, val1); + } + } + + { + uint16_t val1 = 0, val2 = 0; + for (uint8_t i = 0; i < 16; i++) { + atomic_set_bit_u16(atomic_bit_u16(&val1, i)); + val2 |= 1ULL << i; + TEST_ASSERT_EQUAL_INT(val2, val1); + } + } + + { + uint32_t val1 = 0, val2 = 0; + for (uint8_t i = 0; i < 32; i++) { + atomic_set_bit_u32(atomic_bit_u32(&val1, i)); + val2 |= 1ULL << i; + TEST_ASSERT_EQUAL_INT(val2, val1); + } + } + + { + uint64_t val1 = 0, val2 = 0; + for (uint8_t i = 0; i < 32; i++) { + atomic_set_bit_u64(atomic_bit_u64(&val1, i)); + val2 |= 1ULL << i; + TEST_ASSERT(val2 == val1); + } + } +} + +static void test_atomic_clear_bit(void) +{ + { + uint8_t val1 = 0xff, val2 = 0xff; + for (uint8_t i = 0; i < 8; i++) { + atomic_clear_bit_u8(atomic_bit_u8(&val1, i)); + val2 &= ~(1ULL << i); + TEST_ASSERT_EQUAL_INT(val2, val1); + } + } + + { + uint16_t val1 = 0xffff, val2 = 0xffff; + for (uint8_t i = 0; i < 16; i++) { + atomic_clear_bit_u16(atomic_bit_u16(&val1, i)); + val2 &= ~(1ULL << i); + TEST_ASSERT_EQUAL_INT(val2, val1); + } + } + + { + uint32_t val1 = 0xffffffff, val2 = 0xffffffff; + for (uint8_t i = 0; i < 32; i++) { + atomic_clear_bit_u32(atomic_bit_u32(&val1, i)); + val2 &= ~(1ULL << i); + TEST_ASSERT_EQUAL_INT(val2, val1); + } + } + + { + uint64_t val1 = 0xffffffffffffffff, val2 = 0xffffffffffffffff; + for (uint8_t i = 0; i < 32; i++) { + atomic_clear_bit_u64(atomic_bit_u64(&val1, i)); + val2 &= ~(1ULL << i); + TEST_ASSERT(val2 == val1); + } + } +} + +Test *tests_atomic_utils_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_load_store), + new_TestFixture(test_fetch_ops_u8), + new_TestFixture(test_fetch_ops_u16), + new_TestFixture(test_fetch_ops_u32), + new_TestFixture(test_fetch_ops_u64), + new_TestFixture(test_atomic_set_bit), + new_TestFixture(test_atomic_clear_bit), + }; + + EMB_UNIT_TESTCALLER(atomic_utils_tests, NULL, NULL, fixtures); + + return (Test *)&atomic_utils_tests; +} + +void tests_atomic_utils(void) +{ + TESTS_RUN(tests_atomic_utils_tests()); +} diff --git a/tests/unittests/tests-atomic_utils/tests-atomic_utils.h b/tests/unittests/tests-atomic_utils/tests-atomic_utils.h new file mode 100644 index 0000000000..eaacee1ae3 --- /dev/null +++ b/tests/unittests/tests-atomic_utils/tests-atomic_utils.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for the atomic util module + * + * @author Marian Buschsieweke + */ + +#ifndef TESTS_ATOMIC_UTILS_H +#define TESTS_ATOMIC_UTILS_H + +#include "embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_atomic_util(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_ATOMIC_UTILS_H */ +/** @} */ From 1352ce667bc199a3a97b2aab0eb7db37c940d083 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Sun, 28 Jun 2020 00:02:28 +0200 Subject: [PATCH 12/12] tests: Add another test for atomic_utils This tests can be used to test for implementations violating atomic attribute. --- tests/sys_atomic_utils/Makefile | 9 + tests/sys_atomic_utils/Makefile.ci | 10 + tests/sys_atomic_utils/README.md | 87 ++ tests/sys_atomic_utils/main.c | 1361 +++++++++++++++++++++++ tests/sys_atomic_utils/tests/01-run.py | 35 + tests/sys_atomic_utils/volatile_utils.h | 192 ++++ 6 files changed, 1694 insertions(+) create mode 100644 tests/sys_atomic_utils/Makefile create mode 100644 tests/sys_atomic_utils/Makefile.ci create mode 100644 tests/sys_atomic_utils/README.md create mode 100644 tests/sys_atomic_utils/main.c create mode 100755 tests/sys_atomic_utils/tests/01-run.py create mode 100644 tests/sys_atomic_utils/volatile_utils.h diff --git a/tests/sys_atomic_utils/Makefile b/tests/sys_atomic_utils/Makefile new file mode 100644 index 0000000000..f23bc3972a --- /dev/null +++ b/tests/sys_atomic_utils/Makefile @@ -0,0 +1,9 @@ +include ../Makefile.tests_common + +USEMODULE += atomic_utils +USEMODULE += fmt +USEMODULE += random +USEMODULE += shell +USEMODULE += xtimer + +include $(RIOTBASE)/Makefile.include diff --git a/tests/sys_atomic_utils/Makefile.ci b/tests/sys_atomic_utils/Makefile.ci new file mode 100644 index 0000000000..ffa12570d1 --- /dev/null +++ b/tests/sys_atomic_utils/Makefile.ci @@ -0,0 +1,10 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + nucleo-f031k6 \ + nucleo-l011k4 \ + stm32f030f4-demo \ + # diff --git a/tests/sys_atomic_utils/README.md b/tests/sys_atomic_utils/README.md new file mode 100644 index 0000000000..98cf1c863a --- /dev/null +++ b/tests/sys_atomic_utils/README.md @@ -0,0 +1,87 @@ +# Test Application for `sys/atomic_utils` + +## Design of the Test + +This test application will launch one worker and one tester thread. The +worker thread will perform a specific operation (such as +`atomic_fetch_add_u32()`) over and over again on a single target variable, and +the tester thread occasionally interrupts to verify the target variable's value +is valid. If the variable has corrupted, this is reported. + +This test works only *statistically*. Absence of detected corruption does not +guarantee, that a specific function indeed works correctly. (But a detected +corruptions guarantees that something is indeed broken.) However, the longer +the tests runs the higher the odds are that a malfunctioning implementation is +indeed caught corrupting memory. + +## Types of Corruptions Tested For + +### Lost Update + +In the lost update the worker thread will perform an operation that has no +effect (addition, subtraction, binary or, and binary xor with `0` as +second parameter, or binary and with `0xff...`). The checker thread will +atomically increment the target value. If in the next iteration the of the +checker thread the value has changed, a corruption is detected. This could +happen e.g. by + +``` +Worker Thread | Checker Thread | Value of t + | | + | t++ | 1 +reg1 = t | | 1 +reg1 += 0 | | 1 + | t++ | 2 +t = reg1 | | 1 +``` + +Here, the read-modify-write sequence (`reg1 = t; reg1 += 0; t = reg1;`) has +been interrupted by the Checker Thread. The update of `t` (the atomic `t++` +operation) is afterwards lost, when the Worker Thread writes `reg1` into +`t`. Such a lost update proves that the read-modify-write operation was not +atomic. + +Note: Only the `atomic__u` family of functions must pass this test. + A failure for the other families does ***not*** indicate an issue. + +### Store Tearing + +In the tearing test the worker thread will first initialize the target variable, +e.g. with zero. Then, a sequence of read-modify-write operations is performed, +e.g. 3 times `atomic_fetch_add_u16(&target, 0x5555)`. During this sequence, only +the target variable should contain on of the following values: +`0x0000`, `0x5555`, `0xaaaa`, and `0xffff`. + +After each sequence is complete, the target variable will be atomically +re-initialized and the next sequence starts. If e.g. on AVR the write is +interrupted after only one byte is written (AVR is an 8-bit platform and only +can write 8 bits per store), e.g. a value of `0x55aa` or `0xaa55` might be +stored in the target variable. If such an value is observed, an atomic store +operation was torn apart into two parts and a memory corruption was detected. + +Note: Both the `atomic__u` and `semi_atomic__u` families + of functions must pass this test. A failure of the + `volatile__u` family of functions does ***not*** indicate an + issue. + +## Usage + +The test will drop you into a shell. The welcome message and the help command +contain all information on how to use the test. In addition, `make test` will +run all tests that are expected to pass for one second each. This is hopefully +long enough to detect any issues. It is certainly not possible to run the test +longer in automated tests. + +## Test Self Check + +The test brings an alternative implementation of the `atomic__u` +family of functions called `volatile__u`. This implementation +incorrectly assumes that `volatile` provides atomic access. Thus, checking +this implementation should result in failures: + +- The lost update test is expected to (eventually) fail for every platform +- The tearing test is expected for width bigger than the word size + - Cortex-M7 is one exception: Due to instruction fusion two 32 bit writes + can be issued in one CPU cycle, so that a 64 bit write can indeed be + atomic for this platform. Thus, it could happen that no tearing failure + is detected for the `volatile` implementation on Cortex-M7 at all. diff --git a/tests/sys_atomic_utils/main.c b/tests/sys_atomic_utils/main.c new file mode 100644 index 0000000000..3120973346 --- /dev/null +++ b/tests/sys_atomic_utils/main.c @@ -0,0 +1,1361 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Atomic util benchmark + * + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include +#include +#include + +#include "atomic_utils.h" +#include "fmt.h" +#include "mutex.h" +#include "random.h" +#include "shell.h" +#include "thread.h" +#include "xtimer.h" + +#include "volatile_utils.h" + +typedef enum { + TEST_TYPE_TEARING, + TEST_TYPE_LOST_UPDATE, + TEST_TYPE_NUMOF +} test_type_t; + +typedef enum { + TEST_WIDTH_8_BIT, + TEST_WIDTH_16_BIT, + TEST_WIDTH_32_BIT, + TEST_WIDTH_64_BIT, + TEST_WIDTH_NUMOF +} test_width_t; + +typedef void (*fetch_op_u8_t)(uint8_t *dest, uint8_t val); +typedef void (*fetch_op_u16_t)(uint16_t *dest, uint16_t val); +typedef void (*fetch_op_u32_t)(uint32_t *dest, uint32_t val); +typedef void (*fetch_op_u64_t)(uint64_t *dest, uint64_t val); + +typedef struct { + const char *name; + fetch_op_u8_t op; + uint8_t operand; + uint8_t noop_operand; + uint8_t init; + uint8_t allowed[4]; + uint8_t allowed_numof; + uint8_t reinit_every; +} fetch_op_test_u8_t; + +typedef struct { + const char *name; + fetch_op_u16_t op; + uint16_t operand; + uint16_t noop_operand; + uint16_t init; + uint16_t allowed[4]; + uint8_t allowed_numof; + uint8_t reinit_every; +} fetch_op_test_u16_t; + +typedef struct { + const char *name; + fetch_op_u32_t op; + uint32_t operand; + uint32_t noop_operand; + uint32_t init; + uint32_t allowed[4]; + uint8_t allowed_numof; + uint8_t reinit_every; +} fetch_op_test_u32_t; + +typedef struct { + const char *name; + fetch_op_u64_t op; + uint64_t operand; + uint64_t noop_operand; + uint64_t init; + uint64_t allowed[4]; + uint8_t allowed_numof; + uint8_t reinit_every; +} fetch_op_test_u64_t; + +typedef struct { + test_type_t type; + test_width_t width; + unsigned idx; +} test_conf_t; + +typedef struct { + test_conf_t conf; + uint8_t counter; +} test_state_t; + +static const fetch_op_test_u8_t fetch_op_tests_u8[] = { + /* atomic_*() */ + { + .name = "atomic_fetch_add_u8", + .op = atomic_fetch_add_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { + 0x00, 0x55, + 0xaa, 0xff + }, + .allowed_numof = 4, + .init = 0x00, + .reinit_every = 3, + }, + { + .name = "atomic_fetch_sub_u8", + .op = atomic_fetch_sub_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { + 0x00, 0x55, + 0xaa, 0xff + }, + .allowed_numof = 4, + .init = 0xff, + .reinit_every = 3, + }, + { + .name = "atomic_fetch_or_u8", + .op = atomic_fetch_or_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { 0x00, 0x55 }, + .allowed_numof = 2, + .init = 0x00, + .reinit_every = 1, + }, + { + .name = "atomic_fetch_xor_u8", + .op = atomic_fetch_xor_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { 0x00, 0x55 }, + .allowed_numof = 2, + .init = 0x00, + }, + { + .name = "atomic_fetch_and_u8", + .op = atomic_fetch_and_u8, + .operand = 0x55, + .noop_operand = 0xff, + .allowed = { 0xff, 0x55 }, + .allowed_numof = 2, + .init = 0xff, + .reinit_every = 1, + }, + /* semi_atomic_*() */ + { + .name = "semi_atomic_fetch_add_u8", + .op = semi_atomic_fetch_add_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { + 0x00, 0x55, + 0xaa, 0xff + }, + .allowed_numof = 4, + .init = 0x00, + .reinit_every = 3, + }, + { + .name = "semi_atomic_fetch_sub_u8", + .op = semi_atomic_fetch_sub_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { + 0x00, 0x55, + 0xaa, 0xff + }, + .allowed_numof = 4, + .init = 0xff, + .reinit_every = 3, + }, + { + .name = "semi_atomic_fetch_or_u8", + .op = semi_atomic_fetch_or_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { 0x00, 0x55 }, + .allowed_numof = 2, + .init = 0x00, + .reinit_every = 1, + }, + { + .name = "semi_atomic_fetch_xor_u8", + .op = semi_atomic_fetch_xor_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { 0x00, 0x55 }, + .allowed_numof = 2, + .init = 0x00, + }, + { + .name = "semi_atomic_fetch_and_u8", + .op = semi_atomic_fetch_and_u8, + .operand = 0x55, + .noop_operand = 0xff, + .allowed = { 0xff, 0x55 }, + .allowed_numof = 2, + .init = 0xff, + .reinit_every = 1, + }, + /* volatile_*() */ + { + .name = "volatile_fetch_add_u8", + .op = volatile_fetch_add_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { + 0x00, 0x55, + 0xaa, 0xff + }, + .allowed_numof = 4, + .init = 0x00, + .reinit_every = 3, + }, + { + .name = "volatile_fetch_sub_u8", + .op = volatile_fetch_sub_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { + 0x00, 0x55, + 0xaa, 0xff + }, + .allowed_numof = 4, + .init = 0xff, + .reinit_every = 3, + }, + { + .name = "volatile_fetch_or_u8", + .op = volatile_fetch_or_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { 0x00, 0x55 }, + .allowed_numof = 2, + .init = 0x00, + .reinit_every = 1, + }, + { + .name = "volatile_fetch_xor_u8", + .op = volatile_fetch_xor_u8, + .operand = 0x55, + .noop_operand = 0, + .allowed = { 0x00, 0x55 }, + .allowed_numof = 2, + .init = 0x00, + }, + { + .name = "volatile_fetch_and_u8", + .op = volatile_fetch_and_u8, + .operand = 0x55, + .noop_operand = 0xff, + .allowed = { 0xff, 0x55 }, + .allowed_numof = 2, + .init = 0xff, + .reinit_every = 1, + }, +}; + +static const fetch_op_test_u16_t fetch_op_tests_u16[] = { + /* atomic_*() */ + { + .name = "atomic_fetch_add_u16", + .op = atomic_fetch_add_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { + 0x0000, 0x5555, + 0xaaaa, 0xffff + }, + .allowed_numof = 4, + .init = 0x0000, + .reinit_every = 3, + }, + { + .name = "atomic_fetch_sub_u16", + .op = atomic_fetch_sub_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { + 0x0000, 0x5555, + 0xaaaa, 0xffff + }, + .allowed_numof = 4, + .init = 0xffff, + .reinit_every = 3, + }, + { + .name = "atomic_fetch_or_u16", + .op = atomic_fetch_or_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { 0x0000, 0x5555 }, + .allowed_numof = 2, + .init = 0x0000, + .reinit_every = 1, + }, + { + .name = "atomic_fetch_xor_u16", + .op = atomic_fetch_xor_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { 0x0000, 0x5555 }, + .allowed_numof = 2, + .init = 0x0000, + }, + { + .name = "atomic_fetch_and_u16", + .op = atomic_fetch_and_u16, + .operand = 0x5555, + .noop_operand = 0xffff, + .allowed = { 0xffff, 0x5555 }, + .allowed_numof = 2, + .init = 0xffff, + .reinit_every = 1, + }, + /* semi_atomic_*() */ + { + .name = "semi_atomic_fetch_add_u16", + .op = semi_atomic_fetch_add_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { + 0x0000, 0x5555, + 0xaaaa, 0xffff + }, + .allowed_numof = 4, + .init = 0x0000, + .reinit_every = 3, + }, + { + .name = "semi_atomic_fetch_sub_u16", + .op = semi_atomic_fetch_sub_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { + 0x0000, 0x5555, + 0xaaaa, 0xffff + }, + .allowed_numof = 4, + .init = 0xffff, + .reinit_every = 3, + }, + { + .name = "semi_atomic_fetch_or_u16", + .op = semi_atomic_fetch_or_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { 0x0000, 0x5555 }, + .allowed_numof = 2, + .init = 0x0000, + .reinit_every = 1, + }, + { + .name = "semi_atomic_fetch_xor_u16", + .op = semi_atomic_fetch_xor_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { 0x0000, 0x5555 }, + .allowed_numof = 2, + .init = 0x0000, + }, + { + .name = "semi_atomic_fetch_and_u16", + .op = semi_atomic_fetch_and_u16, + .operand = 0x5555, + .noop_operand = 0xffff, + .allowed = { 0xffff, 0x5555 }, + .allowed_numof = 2, + .init = 0xffff, + .reinit_every = 1, + }, + /* volatile_*() */ + { + .name = "volatile_fetch_add_u16", + .op = volatile_fetch_add_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { + 0x0000, 0x5555, + 0xaaaa, 0xffff + }, + .allowed_numof = 4, + .init = 0x0000, + .reinit_every = 3, + }, + { + .name = "volatile_fetch_sub_u16", + .op = volatile_fetch_sub_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { + 0x0000, 0x5555, + 0xaaaa, 0xffff + }, + .allowed_numof = 4, + .init = 0xffff, + .reinit_every = 3, + }, + { + .name = "volatile_fetch_or_u16", + .op = volatile_fetch_or_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { 0x0000, 0x5555 }, + .allowed_numof = 2, + .init = 0x0000, + .reinit_every = 1, + }, + { + .name = "volatile_fetch_xor_u16", + .op = volatile_fetch_xor_u16, + .operand = 0x5555, + .noop_operand = 0, + .allowed = { 0x0000, 0x5555 }, + .allowed_numof = 2, + .init = 0x0000, + }, + { + .name = "volatile_fetch_and_u16", + .op = volatile_fetch_and_u16, + .operand = 0x5555, + .noop_operand = 0xffff, + .allowed = { 0xffff, 0x5555 }, + .allowed_numof = 2, + .init = 0xffff, + .reinit_every = 1, + }, +}; + +static const fetch_op_test_u32_t fetch_op_tests_u32[] = { + /* atomic_*() */ + { + .name = "atomic_fetch_add_u32", + .op = atomic_fetch_add_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { + 0x00000000, 0x55555555, + 0xaaaaaaaa, 0xffffffff + }, + .allowed_numof = 4, + .init = 0x00000000, + .reinit_every = 3, + }, + { + .name = "atomic_fetch_sub_u32", + .op = atomic_fetch_sub_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { + 0x00000000, 0x55555555, + 0xaaaaaaaa, 0xffffffff + }, + .allowed_numof = 4, + .init = 0xffffffff, + .reinit_every = 3, + }, + { + .name = "atomic_fetch_or_u32", + .op = atomic_fetch_or_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { 0x00000000, 0x55555555 }, + .allowed_numof = 2, + .init = 0x00000000, + .reinit_every = 1, + }, + { + .name = "atomic_fetch_xor_u32", + .op = atomic_fetch_xor_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { 0x00000000, 0x55555555 }, + .allowed_numof = 2, + .init = 0x00000000, + }, + { + .name = "atomic_fetch_and_u32", + .op = atomic_fetch_and_u32, + .operand = 0x55555555, + .noop_operand = 0xffffffff, + .allowed = { 0xffffffff, 0x55555555 }, + .allowed_numof = 2, + .init = 0xffffffff, + .reinit_every = 1, + }, + /* semi_atomic_*() */ + { + .name = "semi_atomic_fetch_add_u32", + .op = semi_atomic_fetch_add_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { + 0x00000000, 0x55555555, + 0xaaaaaaaa, 0xffffffff + }, + .allowed_numof = 4, + .init = 0x00000000, + .reinit_every = 3, + }, + { + .name = "semi_atomic_fetch_sub_u32", + .op = semi_atomic_fetch_sub_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { + 0x00000000, 0x55555555, + 0xaaaaaaaa, 0xffffffff + }, + .allowed_numof = 4, + .init = 0xffffffff, + .reinit_every = 3, + }, + { + .name = "semi_atomic_fetch_or_u32", + .op = semi_atomic_fetch_or_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { 0x00000000, 0x55555555 }, + .allowed_numof = 2, + .init = 0x00000000, + .reinit_every = 1, + }, + { + .name = "semi_atomic_fetch_xor_u32", + .op = semi_atomic_fetch_xor_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { 0x00000000, 0x55555555 }, + .allowed_numof = 2, + .init = 0x00000000, + }, + { + .name = "semi_atomic_fetch_and_u32", + .op = semi_atomic_fetch_and_u32, + .operand = 0x55555555, + .noop_operand = 0xffffffff, + .allowed = { 0xffffffff, 0x55555555 }, + .allowed_numof = 2, + .init = 0xffffffff, + .reinit_every = 1, + }, + /* volatile_*() */ + { + .name = "volatile_fetch_add_u32", + .op = volatile_fetch_add_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { + 0x00000000, 0x55555555, + 0xaaaaaaaa, 0xffffffff + }, + .allowed_numof = 4, + .init = 0x00000000, + .reinit_every = 3, + }, + { + .name = "volatile_fetch_sub_u32", + .op = volatile_fetch_sub_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { + 0x00000000, 0x55555555, + 0xaaaaaaaa, 0xffffffff + }, + .allowed_numof = 4, + .init = 0xffffffff, + .reinit_every = 3, + }, + { + .name = "volatile_fetch_or_u32", + .op = volatile_fetch_or_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { 0x00000000, 0x55555555 }, + .allowed_numof = 2, + .init = 0x00000000, + .reinit_every = 1, + }, + { + .name = "volatile_fetch_xor_u32", + .op = volatile_fetch_xor_u32, + .operand = 0x55555555, + .noop_operand = 0, + .allowed = { 0x00000000, 0x55555555 }, + .allowed_numof = 2, + .init = 0x00000000, + }, + { + .name = "volatile_fetch_and_u32", + .op = volatile_fetch_and_u32, + .operand = 0x55555555, + .noop_operand = 0xffffffff, + .allowed = { 0xffffffff, 0x55555555 }, + .allowed_numof = 2, + .init = 0xffffffff, + .reinit_every = 1, + }, +}; + +static const fetch_op_test_u64_t fetch_op_tests_u64[] = { + /* atomic_*() */ + { + .name = "atomic_fetch_add_u64", + .op = atomic_fetch_add_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { + 0x0000000000000000, 0x5555555555555555, + 0xaaaaaaaaaaaaaaaa, 0xffffffffffffffff + }, + .allowed_numof = 4, + .init = 0x0000000000000000, + .reinit_every = 3, + }, + { + .name = "atomic_fetch_sub_u64", + .op = atomic_fetch_sub_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { + 0x0000000000000000, 0x5555555555555555, + 0xaaaaaaaaaaaaaaaa, 0xffffffffffffffff + }, + .allowed_numof = 4, + .init = 0xffffffffffffffff, + .reinit_every = 3, + }, + { + .name = "atomic_fetch_or_u64", + .op = atomic_fetch_or_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { 0x0000000000000000, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0x0000000000000000, + .reinit_every = 1, + }, + { + .name = "atomic_fetch_xor_u64", + .op = atomic_fetch_xor_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { 0x0000000000000000, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0x0000000000000000, + }, + { + .name = "atomic_fetch_and_u64", + .op = atomic_fetch_and_u64, + .operand = 0x5555555555555555, + .noop_operand = 0xffffffffffffffff, + .allowed = { 0xffffffffffffffff, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0xffffffffffffffff, + .reinit_every = 1, + }, + /* semi_atomic_*() */ + { + .name = "semi_atomic_fetch_add_u64", + .op = semi_atomic_fetch_add_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { + 0x0000000000000000, 0x5555555555555555, + 0xaaaaaaaaaaaaaaaa, 0xffffffffffffffff + }, + .allowed_numof = 4, + .init = 0x0000000000000000, + .reinit_every = 3, + }, + { + .name = "semi_atomic_fetch_sub_u64", + .op = semi_atomic_fetch_sub_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { + 0x0000000000000000, 0x5555555555555555, + 0xaaaaaaaaaaaaaaaa, 0xffffffffffffffff + }, + .allowed_numof = 4, + .init = 0xffffffffffffffff, + .reinit_every = 3, + }, + { + .name = "semi_atomic_fetch_or_u64", + .op = semi_atomic_fetch_or_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { 0x0000000000000000, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0x0000000000000000, + .reinit_every = 1, + }, + { + .name = "semi_atomic_fetch_xor_u64", + .op = semi_atomic_fetch_xor_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { 0x0000000000000000, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0x0000000000000000, + }, + { + .name = "semi_atomic_fetch_and_u64", + .op = semi_atomic_fetch_and_u64, + .operand = 0x5555555555555555, + .noop_operand = 0xffffffffffffffff, + .allowed = { 0xffffffffffffffff, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0xffffffffffffffff, + .reinit_every = 1, + }, + /* volatile_*() */ + { + .name = "volatile_fetch_add_u64", + .op = volatile_fetch_add_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { + 0x0000000000000000, 0x5555555555555555, + 0xaaaaaaaaaaaaaaaa, 0xffffffffffffffff + }, + .allowed_numof = 4, + .init = 0x0000000000000000, + .reinit_every = 3, + }, + { + .name = "volatile_fetch_sub_u64", + .op = volatile_fetch_sub_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { + 0x0000000000000000, 0x5555555555555555, + 0xaaaaaaaaaaaaaaaa, 0xffffffffffffffff + }, + .allowed_numof = 4, + .init = 0xffffffffffffffff, + .reinit_every = 3, + }, + { + .name = "volatile_fetch_or_u64", + .op = volatile_fetch_or_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { 0x0000000000000000, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0x0000000000000000, + .reinit_every = 1, + }, + { + .name = "volatile_fetch_xor_u64", + .op = volatile_fetch_xor_u64, + .operand = 0x5555555555555555, + .noop_operand = 0, + .allowed = { 0x0000000000000000, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0x0000000000000000, + }, + { + .name = "volatile_fetch_and_u64", + .op = volatile_fetch_and_u64, + .operand = 0x5555555555555555, + .noop_operand = 0xffffffffffffffff, + .allowed = { 0xffffffffffffffff, 0x5555555555555555 }, + .allowed_numof = 2, + .init = 0xffffffffffffffff, + .reinit_every = 1, + }, +}; + +static char thread_worker_stack[THREAD_STACKSIZE_SMALL]; +static char thread_checker_stack[THREAD_STACKSIZE_SMALL]; +static char thread_timeout_stack[THREAD_STACKSIZE_SMALL]; + +static test_conf_t conf; +static mutex_t conf_mutex = MUTEX_INIT_LOCKED; +static mutex_t stop_mutex = MUTEX_INIT_LOCKED; + +static int testing_active = 0; + +/* Testing values to operate on */ +static uint8_t val_u8; +static uint16_t val_u16; +static uint32_t val_u32; +static uint64_t val_u64; + +static uint64_t stats_ops; +static uint64_t stats_tests; +static uint64_t stats_failures; + +static int sc_tearing_test(int argc, char **argv); +static int sc_lost_update_test(int argc, char **argv); +static int sc_stats(int argc, char **argv); +static int sc_stop(int argc, char **argv); +static int sc_list(int argc, char **argv); +static const shell_command_t shell_commands[] = { + { "tearing_test", "Run a store/load tearing test", sc_tearing_test }, + { "lost_update_test", "Run a lost update test", sc_lost_update_test }, + { "stats", "Show stats of current test", sc_stats }, + { "stop", "Stop running test", sc_stop }, + { "list", "List functions that can be tested", sc_list }, + { NULL, NULL, NULL } +}; + +static void tearing_test_worker(test_state_t *state) +{ + switch (state->conf.width) { + default: + break; + case TEST_WIDTH_8_BIT: + { + const fetch_op_test_u8_t *t = &fetch_op_tests_u8[state->conf.idx]; + if (state->counter >= t->reinit_every) { + atomic_store_u8(&val_u8, t->init); + state->counter = 0; + } + t->op(&val_u8, t->operand); + state->counter++; + } + break; + case TEST_WIDTH_16_BIT: + { + const fetch_op_test_u16_t *t = &fetch_op_tests_u16[state->conf.idx]; + if (state->counter >= t->reinit_every) { + atomic_store_u16(&val_u16, t->init); + state->counter = 0; + } + t->op(&val_u16, t->operand); + state->counter++; + } + break; + case TEST_WIDTH_32_BIT: + { + const fetch_op_test_u32_t *t = &fetch_op_tests_u32[state->conf.idx]; + if (state->counter >= t->reinit_every) { + atomic_store_u32(&val_u32, t->init); + state->counter = 0; + } + t->op(&val_u32, t->operand); + state->counter++; + } + break; + case TEST_WIDTH_64_BIT: + { + const fetch_op_test_u64_t *t = &fetch_op_tests_u64[state->conf.idx]; + if (state->counter >= t->reinit_every) { + atomic_store_u64(&val_u64, t->init); + state->counter = 0; + } + t->op(&val_u64, t->operand); + state->counter++; + } + break; + } +} + +static void tearing_test_checker(test_state_t *state) +{ + switch (state->conf.width) { + default: + break; + case TEST_WIDTH_8_BIT: + { + const fetch_op_test_u8_t *t = &fetch_op_tests_u8[state->conf.idx]; + uint8_t val = atomic_load_u8(&val_u8); + for (uint8_t i = 0; i < t->allowed_numof; i++) { + if (t->allowed[i] == val) { + return; + } + } + print_str(t->name); + print_str(": Load/store tearing detected. (Value was "); + print_u32_hex(val); + print_str(")\n"); + stats_failures++; + } + break; + case TEST_WIDTH_16_BIT: + { + const fetch_op_test_u16_t *t = &fetch_op_tests_u16[state->conf.idx]; + uint16_t val = atomic_load_u16(&val_u16); + for (uint8_t i = 0; i < t->allowed_numof; i++) { + if (t->allowed[i] == val) { + return; + } + } + print_str(t->name); + print_str(": Load/store tearing detected. (Value was "); + print_u32_hex(val); + print_str(")\n"); + stats_failures++; + } + break; + case TEST_WIDTH_32_BIT: + { + const fetch_op_test_u32_t *t = &fetch_op_tests_u32[state->conf.idx]; + uint32_t val = atomic_load_u32(&val_u32); + for (uint8_t i = 0; i < t->allowed_numof; i++) { + if (t->allowed[i] == val) { + return; + } + } + print_str(t->name); + print_str(": Load/store tearing detected. (Value was "); + print_u32_hex(val); + print_str(")\n"); + stats_failures++; + } + break; + case TEST_WIDTH_64_BIT: + { + const fetch_op_test_u64_t *t = &fetch_op_tests_u64[state->conf.idx]; + uint64_t val = atomic_load_u64(&val_u64); + for (uint8_t i = 0; i < t->allowed_numof; i++) { + if (t->allowed[i] == val) { + return; + } + } + print_str(t->name); + print_str(": Load/store tearing detected. (Value was "); + print_u64_hex(val); + print_str(")\n"); + stats_failures++; + } + break; + } +} + +static void lost_update_test_worker(test_state_t *state) +{ + switch (state->conf.width) { + default: + break; + case TEST_WIDTH_8_BIT: + { + const fetch_op_test_u8_t *t = &fetch_op_tests_u8[state->conf.idx]; + t->op(&val_u8, t->noop_operand); + } + break; + case TEST_WIDTH_16_BIT: + { + const fetch_op_test_u16_t *t = &fetch_op_tests_u16[state->conf.idx]; + t->op(&val_u16, t->noop_operand); + } + break; + case TEST_WIDTH_32_BIT: + { + const fetch_op_test_u32_t *t = &fetch_op_tests_u32[state->conf.idx]; + t->op(&val_u32, t->noop_operand); + } + break; + case TEST_WIDTH_64_BIT: + { + const fetch_op_test_u64_t *t = &fetch_op_tests_u64[state->conf.idx]; + t->op(&val_u64, t->noop_operand); + } + break; + } +} + +static void lost_update_test_checker(test_state_t *state) +{ + switch (state->conf.width) { + default: + break; + case TEST_WIDTH_8_BIT: + { + const fetch_op_test_u8_t *t = &fetch_op_tests_u8[state->conf.idx]; + uint8_t val = atomic_load_u8(&val_u8); + if (val != state->counter) { + print_str(t->name); + print_str(": Lost update detected.\n"); + stats_failures++; + } + atomic_store_u8(&val_u8, ++state->counter); + } + break; + case TEST_WIDTH_16_BIT: + { + const fetch_op_test_u16_t *t = &fetch_op_tests_u16[state->conf.idx]; + uint16_t val = atomic_load_u16(&val_u16); + if (val != state->counter) { + print_str(t->name); + print_str(": Lost update detected.\n"); + stats_failures++; + } + atomic_store_u16(&val_u16, ++state->counter); + } + break; + case TEST_WIDTH_32_BIT: + { + const fetch_op_test_u32_t *t = &fetch_op_tests_u32[state->conf.idx]; + uint32_t val = atomic_load_u32(&val_u32); + if (val != state->counter) { + print_str(t->name); + print_str(": Lost update detected.\n"); + stats_failures++; + } + atomic_store_u32(&val_u32, ++state->counter); + } + break; + case TEST_WIDTH_64_BIT: + { + const fetch_op_test_u64_t *t = &fetch_op_tests_u64[state->conf.idx]; + uint64_t val = atomic_load_u64(&val_u64); + if (val != state->counter) { + print_str(t->name); + print_str(": Lost update detected.\n"); + stats_failures++; + } + atomic_store_u64(&val_u64, ++state->counter); + } + break; + } +} + +static void *thread_worker_func(void *arg) +{ + (void)arg; + static test_state_t state = { .conf = { .idx = UINT8_MAX } }; + + while (1) { + mutex_lock(&conf_mutex); + test_conf_t c = conf; + stats_ops++; + mutex_unlock(&conf_mutex); + if (memcmp(&c, &state.conf, sizeof(c))) { + state.conf = c; + state.counter = UINT8_MAX; + } + + switch (state.conf.type) { + default: + break; + case TEST_TYPE_TEARING: + tearing_test_worker(&state); + break; + case TEST_TYPE_LOST_UPDATE: + lost_update_test_worker(&state); + break; + } + } + + return NULL; +} + +static void *thread_checker_func(void *arg) +{ + (void)arg; + static test_state_t state = { .conf = { .idx = 0 } }; + + while (1) { + mutex_lock(&conf_mutex); + test_conf_t c = conf; + stats_tests++; + mutex_unlock(&conf_mutex); + if (memcmp(&c, &state.conf, sizeof(c))) { + state.conf = c; + state.counter = 0; + } + + switch (state.conf.type) { + default: + break; + case TEST_TYPE_TEARING: + tearing_test_checker(&state); + break; + case TEST_TYPE_LOST_UPDATE: + lost_update_test_checker(&state); + break; + } + + xtimer_usleep((random_uint32() & 0x3ff) + XTIMER_BACKOFF); + } + + return NULL; +} + +static void *thread_timeout_func(void *arg) +{ + (void)arg; + while (1) { + mutex_lock(&stop_mutex); + sc_stop(0, NULL); + } + return NULL; +} + +static void test_timeout_callback(void *arg) +{ + (void)arg; + mutex_unlock(&stop_mutex); +} + +static int start_test(test_width_t width, size_t fn_index, int timeout) +{ + conf.width = width; + conf.idx = fn_index; + testing_active = 1; + stats_ops = 0; + stats_tests = 0; + stats_failures = 0; + + /* Initialize values. Doing so for every width safes ROM and lines of code + * but wastes a few CPU cycles */ + if (conf.type == TEST_TYPE_LOST_UPDATE) { + atomic_store_u8(&val_u8, 0); + atomic_store_u16(&val_u16, 0); + atomic_store_u32(&val_u32, 0); + atomic_store_u64(&val_u64, 0); + } + else { + const fetch_op_test_u8_t *t8 = &fetch_op_tests_u8[fn_index]; + const fetch_op_test_u16_t *thread_worker6 = &fetch_op_tests_u16[fn_index]; + const fetch_op_test_u32_t *t32 = &fetch_op_tests_u32[fn_index]; + const fetch_op_test_u64_t *t64 = &fetch_op_tests_u64[fn_index]; + atomic_store_u8(&val_u8, t8->init); + atomic_store_u16(&val_u16, thread_worker6->init); + atomic_store_u32(&val_u32, t32->init); + atomic_store_u64(&val_u64, t64->init); + } + + if (timeout) { + static xtimer_t xt = { .callback = test_timeout_callback }; + xtimer_set(&xt, US_PER_SEC * timeout); + } + mutex_unlock(&conf_mutex); + return 0; +} + +static int select_func_and_start_test(const char *funcname, int timeout) +{ + size_t fn_len = strlen(funcname); + + /* Valid function names end with *_u8, *_u16, *_u32, or *_u64. Thus, the + * last char is already sufficient to determine the width. We do not need + * to search all test specs for the given name, but only those of + * matching width + */ + switch (funcname[fn_len - 1]) { + case '8': + for (size_t i = 0; i < ARRAY_SIZE(fetch_op_tests_u8); i++) { + if (!strcmp(fetch_op_tests_u8[i].name, funcname)) { + return start_test(TEST_WIDTH_8_BIT, i, timeout); + } + } + break; + case '6': + for (size_t i = 0; i < ARRAY_SIZE(fetch_op_tests_u16); i++) { + if (!strcmp(fetch_op_tests_u16[i].name, funcname)) { + return start_test(TEST_WIDTH_16_BIT, i, timeout); + } + } + break; + case '2': + for (size_t i = 0; i < ARRAY_SIZE(fetch_op_tests_u32); i++) { + if (!strcmp(fetch_op_tests_u32[i].name, funcname)) { + return start_test(TEST_WIDTH_32_BIT, i, timeout); + } + } + break; + case '4': + for (size_t i = 0; i < ARRAY_SIZE(fetch_op_tests_u64); i++) { + if (!strcmp(fetch_op_tests_u64[i].name, funcname)) { + return start_test(TEST_WIDTH_64_BIT, i, timeout); + } + } + break; + } + + print_str("Function \""); + print_str(funcname); + print_str("\" not found\n"); + return 1; + +} + +static int sc_tearing_test(int argc, char **argv) +{ + if ((argc != 2) && (argc != 3)) { + print_str("Usage: "); + print_str(argv[0]); + print_str(" \n"); + return 1; + } + + int timeout = 0; + if (argc == 3) { + timeout = atoi(argv[2]); + if (timeout <= 0) { + print_str("Invalid timeout\n"); + return 1; + } + } + + if (testing_active) { + mutex_lock(&conf_mutex); + testing_active = 0; + } + + conf.type = TEST_TYPE_TEARING; + return select_func_and_start_test(argv[1], timeout); +} + +static int sc_lost_update_test(int argc, char **argv) +{ + if ((argc != 2) && (argc != 3)) { + print_str("Usage: "); + print_str(argv[0]); + print_str(" [TIMEOUT_IN_SECONDS]\n"); + return 1; + } + + int timeout = 0; + if (argc == 3) { + timeout = atoi(argv[2]); + if (timeout <= 0) { + print_str("Invalid timeout\n"); + return 1; + } + } + + if (testing_active) { + mutex_lock(&conf_mutex); + testing_active = 0; + } + + conf.type = TEST_TYPE_LOST_UPDATE; + return select_func_and_start_test(argv[1], timeout); +} + +static int sc_stats(int argc, char **argv) +{ + (void)argc; + (void)argv; + if (!testing_active) { + print_str("No test active\n"); + return 0; + } + mutex_lock(&conf_mutex); + print_u64_dec(stats_ops); + print_str(" operations performed\n"); + print_u64_dec(stats_tests); + print_str(" tests performed\n"); + print_u64_dec(stats_failures); + print_str(" corruptions detected\n"); + mutex_unlock(&conf_mutex); + return 0; +} + +static int sc_stop(int argc, char **argv) +{ + (void)argc; + (void)argv; + if (testing_active) { + mutex_lock(&conf_mutex); + testing_active = 0; + if (stats_failures) { + print_str("ERROR: Detected "); + print_u64_dec(stats_failures); + print_str(" corruptions\n"); + } + else { + print_str("OK\n"); + } + } + return 0; +} + +static int sc_list(int argc, char **argv) +{ + (void)argc; + (void)argv; + + for (size_t i = 0; i < ARRAY_SIZE(fetch_op_tests_u8); i++) { + print_str(fetch_op_tests_u8[i].name); + print_str("\n"); + } + + for (size_t i = 0; i < ARRAY_SIZE(fetch_op_tests_u16); i++) { + print_str(fetch_op_tests_u16[i].name); + print_str("\n"); + } + + for (size_t i = 0; i < ARRAY_SIZE(fetch_op_tests_u32); i++) { + print_str(fetch_op_tests_u32[i].name); + print_str("\n"); + } + + for (size_t i = 0; i < ARRAY_SIZE(fetch_op_tests_u64); i++) { + print_str(fetch_op_tests_u64[i].name); + print_str("\n"); + } + + return 0; +} + +int main(void) +{ + thread_create(thread_worker_stack, sizeof(thread_worker_stack), + THREAD_PRIORITY_MAIN + 2, THREAD_CREATE_STACKTEST, + thread_worker_func, NULL, "worker"); + thread_create(thread_checker_stack, sizeof(thread_checker_stack), + THREAD_PRIORITY_MAIN + 1, THREAD_CREATE_STACKTEST, + thread_checker_func, NULL, "checker"); + thread_create(thread_timeout_stack, sizeof(thread_timeout_stack), + THREAD_PRIORITY_MAIN - 1, THREAD_CREATE_STACKTEST, + thread_timeout_func, NULL, "timeout"); + + print_str( + "Test Application for sys/atomic_utils\n" + "=====================================\n" + "\n" + "Use the shell commands \"tearing_test\" and \"lost_update_test\" to\n" + "test the various _fetch__u functions for lost\n" + "updates and store tearing. The \"list\" shell commands lists\n" + "functions to test. See below which function families should\n" + "pass which tests.\n" + "\n" + "The atomic_fetch__u family must pass all tests.\n" + "\n" + "The semi_atomic_fetch__u family must pass the tearing\n" + "test, but may fail the lost update test.\n" + "\n" + "The volatile_fetch__u family should fail the lost update\n" + "tests for all platforms. On most platforms they should fail the\n" + "tearing tests for widths greater than the word size. (One exception\n" + "is the Cortex-M7 family, which can by using instruction fusion issue\n" + "two 32 bit writes in a single CPU cycle.). The volatile family is\n" + "provided to verify that the test actually can detect issues. Any\n" + "failure here is not an indication of an issue, but indicates the.\n" + "test is working as expected.\n" + ); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +} diff --git a/tests/sys_atomic_utils/tests/01-run.py b/tests/sys_atomic_utils/tests/01-run.py new file mode 100755 index 0000000000..3a468acefd --- /dev/null +++ b/tests/sys_atomic_utils/tests/01-run.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +# @author Marian Buschsieweke + +import sys +from testrunner import run + + +def testfunc(child): + fns = ["fetch_add", "fetch_sub", "fetch_or", "fetch_xor", "fetch_and"] + postfixes = ["_u8", "_u16", "_u32", "_u64"] + tests = ["tearing_test", "lost_update_test"] + prefixes = { + "tearing_test": ["atomic_", "semi_atomic_"], + "lost_update_test": ["atomic_"] + } + timeout = "1" + + for test in tests: + for prefix in prefixes[test]: + for postfix in postfixes: + for fn in fns: + child.sendline(test + " " + prefix + fn + postfix + " " + + timeout) + child.expect("OK") + + +if __name__ == "__main__": + sys.exit(run(testfunc)) diff --git a/tests/sys_atomic_utils/volatile_utils.h b/tests/sys_atomic_utils/volatile_utils.h new file mode 100644 index 0000000000..dd9eefa1e4 --- /dev/null +++ b/tests/sys_atomic_utils/volatile_utils.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief For comparison: "Atomic" accesses using volatile + * @author Marian Buschsieweke + * + * This header implements the `volatile_*()` family of functions of + * @ref sys_volatile_utils with `volatile_` instead of `volatile_` as prefix. + * These implementation rely on the `volatile` type qualifier for implementing + * "atomic" accesses; which in many cases will not result in atomic operations. + * So this is a known to be ***BROKEN*** implementation. Its sole purpose is + * to verify that the tests does detect broken implementations. Do not use + * these functions for anything else but testing ;-) + */ + +#ifndef VOLATILE_UTILS_H +#define VOLATILE_UTILS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static inline uint8_t volatile_load_u8(const uint8_t *var) +{ + return *((const volatile uint8_t *)var); +} +static inline uint16_t volatile_load_u16(const uint16_t *var) +{ + return *((const volatile uint16_t *)var); +} +static inline uint32_t volatile_load_u32(const uint32_t *var) +{ + return *((const volatile uint32_t *)var); +} +static inline uint64_t volatile_load_u64(const uint64_t *var) +{ + return *((const volatile uint64_t *)var); +} + +static inline void volatile_store_u8(uint8_t *dest, uint8_t val) +{ + *((volatile uint8_t *)dest) = val; +} +static inline void volatile_store_u16(uint16_t *dest, uint16_t val) +{ + *((volatile uint16_t *)dest) = val; +} +static inline void volatile_store_u32(uint32_t *dest, uint32_t val) +{ + *((volatile uint32_t *)dest) = val; +} +static inline void volatile_store_u64(uint64_t *dest, uint64_t val) +{ + *((volatile uint64_t *)dest) = val; +} + +static inline void volatile_fetch_add_u8(uint8_t *dest, uint8_t val) +{ + *((volatile uint8_t *)dest) += val; +} +static inline void volatile_fetch_sub_u8(uint8_t *dest, uint8_t val) +{ + *((volatile uint8_t *)dest) -= val; +} +static inline void volatile_fetch_or_u8(uint8_t *dest, uint8_t val) +{ + *((volatile uint8_t *)dest) |= val; +} +static inline void volatile_fetch_xor_u8(uint8_t *dest, uint8_t val) +{ + *((volatile uint8_t *)dest) ^= val; +} +static inline void volatile_fetch_and_u8(uint8_t *dest, uint8_t val) +{ + *((volatile uint8_t *)dest) &= val; +} + +static inline void volatile_fetch_add_u16(uint16_t *dest, uint16_t val) +{ + *((volatile uint16_t *)dest) += val; +} +static inline void volatile_fetch_sub_u16(uint16_t *dest, uint16_t val) +{ + *((volatile uint16_t *)dest) -= val; +} +static inline void volatile_fetch_or_u16(uint16_t *dest, uint16_t val) +{ + *((volatile uint16_t *)dest) |= val; +} +static inline void volatile_fetch_xor_u16(uint16_t *dest, uint16_t val) +{ + *((volatile uint16_t *)dest) ^= val; +} +static inline void volatile_fetch_and_u16(uint16_t *dest, uint16_t val) +{ + *((volatile uint16_t *)dest) &= val; +} + +static inline void volatile_fetch_add_u32(uint32_t *dest, uint32_t val) +{ + *((volatile uint32_t *)dest) += val; +} +static inline void volatile_fetch_sub_u32(uint32_t *dest, uint32_t val) +{ + *((volatile uint32_t *)dest) -= val; +} +static inline void volatile_fetch_or_u32(uint32_t *dest, uint32_t val) +{ + *((volatile uint32_t *)dest) |= val; +} +static inline void volatile_fetch_xor_u32(uint32_t *dest, uint32_t val) +{ + *((volatile uint32_t *)dest) ^= val; +} +static inline void volatile_fetch_and_u32(uint32_t *dest, uint32_t val) +{ + *((volatile uint32_t *)dest) &= val; +} + +static inline void volatile_fetch_add_u64(uint64_t *dest, uint64_t val) +{ + *((volatile uint64_t *)dest) += val; +} +static inline void volatile_fetch_sub_u64(uint64_t *dest, uint64_t val) +{ + *((volatile uint64_t *)dest) -= val; +} +static inline void volatile_fetch_or_u64(uint64_t *dest, uint64_t val) +{ + *((volatile uint64_t *)dest) |= val; +} +static inline void volatile_fetch_xor_u64(uint64_t *dest, uint64_t val) +{ + *((volatile uint64_t *)dest) ^= val; +} +static inline void volatile_fetch_and_u64(uint64_t *dest, uint64_t val) +{ + *((volatile uint64_t *)dest) &= val; +} + +static inline void volatile_set_bit_u8(uint8_t *mask, uint8_t bit) +{ + *((volatile uint8_t *)mask) |= 1 << bit; +} +static inline void volatile_set_bit_u16(uint16_t *mask, uint8_t bit) +{ + *((volatile uint16_t *)mask) |= 1 << bit; +} +static inline void volatile_set_bit_u32(uint32_t *mask, uint8_t bit) +{ + *((volatile uint32_t *)mask) |= 1UL << bit; +} +static inline void volatile_set_bit_u64(uint64_t *mask, uint8_t bit) +{ + *((volatile uint64_t *)mask) |= 1ULL << bit; +} + +static inline void volatile_clear_bit_u8(uint8_t *mask, uint8_t bit) +{ + *((volatile uint8_t *)mask) &= ~(1 << bit); +} +static inline void volatile_clear_bit_u16(uint16_t *mask, uint8_t bit) +{ + *((volatile uint16_t *)mask) &= ~(1 << bit); +} +static inline void volatile_clear_bit_u32(uint32_t *mask, uint8_t bit) +{ + *((volatile uint32_t *)mask) &= ~(1UL << bit); +} +static inline void volatile_clear_bit_u64(uint64_t *mask, uint8_t bit) +{ + *((volatile uint64_t *)mask) &= ~(1ULL << bit); +} + +#ifdef __cplusplus +} +#endif + +#endif /* VOLATILE_UTILS_H */ +/** @} */