1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #14331 from maribu/atomic_utils

sys/atomic_utils: Functions for atomic access
This commit is contained in:
Alexandre Abadie 2020-11-12 21:44:53 +01:00 committed by GitHub
commit 792e031a95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 4380 additions and 0 deletions

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */

View File

@ -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

1137
sys/include/atomic_utils.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
include ../Makefile.tests_common
USEMODULE += xtimer
USEMODULE += atomic_utils
USEMODULE += test_utils_interactive_sync
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,3 @@
BOARD_INSUFFICIENT_MEMORY := \
stm32f030f4-demo \
#

View File

@ -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.

View File

@ -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 <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <stdint.h>
#include <stdatomic.h>
#include <stdio.h>
#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;
}

View File

@ -0,0 +1,9 @@
include ../Makefile.tests_common
USEMODULE += atomic_utils
USEMODULE += fmt
USEMODULE += random
USEMODULE += shell
USEMODULE += xtimer
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,10 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-nano \
arduino-uno \
atmega328p \
nucleo-f031k6 \
nucleo-l011k4 \
stm32f030f4-demo \
#

View File

@ -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_<op>_u<width>` 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_<op>_u<width>` and `semi_atomic_<op>_u<width>` families
of functions must pass this test. A failure of the
`volatile_<op>_u<width>` 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_<op>_u<width>`
family of functions called `volatile_<op>_u<width>`. 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.

File diff suppressed because it is too large Load Diff

View File

@ -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 <marian.buschsieweke@ovgu.de>
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))

View File

@ -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 <marian.buschsieweke@ovgu.de>
*
* 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 <stdint.h>
#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 */
/** @} */

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,2 @@
USEMODULE += atomic_utils
USEMODULE += random # <-- used for input data to operate on

View File

@ -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 <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <string.h>
#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());
}

View File

@ -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 <marian.buschsieweke@ovgu.de>
*/
#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 */
/** @} */