Skip to content

Commit

Permalink
tests: Add another test for atomic_utils
Browse files Browse the repository at this point in the history
This tests can be used to test for implementations violating atomic attribute.
  • Loading branch information
maribu committed Nov 10, 2020
1 parent 07fc051 commit 1352ce6
Show file tree
Hide file tree
Showing 6 changed files with 1,694 additions and 0 deletions.
9 changes: 9 additions & 0 deletions tests/sys_atomic_utils/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
include ../Makefile.tests_common

USEMODULE += atomic_utils
USEMODULE += fmt
USEMODULE += random
USEMODULE += shell
USEMODULE += xtimer

include $(RIOTBASE)/Makefile.include
10 changes: 10 additions & 0 deletions tests/sys_atomic_utils/Makefile.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-nano \
arduino-uno \
atmega328p \
nucleo-f031k6 \
nucleo-l011k4 \
stm32f030f4-demo \
#
87 changes: 87 additions & 0 deletions tests/sys_atomic_utils/README.md
Original file line number Diff line number Diff line change
@@ -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.
Loading

0 comments on commit 1352ce6

Please sign in to comment.