Skip to content

Commit 4358f3a

Browse files
authored
Merge pull request #350 from ghaug/master
Add util/atomic.h
2 parents 9c1340a + bae7243 commit 4358f3a

File tree

3 files changed

+353
-0
lines changed

3 files changed

+353
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99
### Added
10+
- Add util/atomic.h
1011

1112
### Changed
1213

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include <ArduinoUnitTests.h>
2+
#include <Arduino.h>
3+
#include <util/atomic.h>
4+
5+
6+
unittest(atomic)
7+
{
8+
// The macros don't do anything on the host platform, just make sure
9+
// they compile without error.
10+
11+
int a = 1;
12+
int b = 2;
13+
14+
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
15+
a += b;
16+
b++;
17+
}
18+
19+
ATOMIC_BLOCK(ATOMIC_FORCEON) {
20+
a += b;
21+
b++;
22+
}
23+
24+
NONATOMIC_BLOCK(NONATOMIC_RESTORESTATE) {
25+
a += b;
26+
b++;
27+
}
28+
29+
NONATOMIC_BLOCK(NONATOMIC_FORCEOFF) {
30+
a += b;
31+
b++;
32+
}
33+
34+
assertEqual(a, 15);
35+
assertEqual(b, 6);
36+
}
37+
38+
39+
unittest_main()

cpp/arduino/util/atomic.h

+313
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/* Copyright (c) 2007 Dean Camera
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
10+
* Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in
12+
the documentation and/or other materials provided with the
13+
distribution.
14+
15+
* Neither the name of the copyright holders nor the names of
16+
contributors may be used to endorse or promote products derived
17+
from this software without specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
/* $Id$ */
33+
34+
#ifndef _UTIL_ATOMIC_H_
35+
#define _UTIL_ATOMIC_H_ 1
36+
37+
#include <avr/io.h>
38+
// Not required
39+
//#include <avr/interrupt.h>
40+
41+
#if !defined(__DOXYGEN__)
42+
/* Internal helper functions. */
43+
static __inline__ uint8_t __iSeiRetVal(void)
44+
{
45+
// Just do nothing
46+
//sei();
47+
return 1;
48+
}
49+
50+
static __inline__ uint8_t __iCliRetVal(void)
51+
{
52+
// Just do nothing
53+
// cli();
54+
return 1;
55+
}
56+
57+
static __inline__ void __iSeiParam(const uint8_t *__s)
58+
{
59+
// Just do nothing
60+
// sei();
61+
__asm__ volatile ("" ::: "memory");
62+
(void)__s;
63+
}
64+
65+
static __inline__ void __iCliParam(const uint8_t *__s)
66+
{
67+
// Just do nothing
68+
// cli();
69+
__asm__ volatile ("" ::: "memory");
70+
(void)__s;
71+
}
72+
73+
static __inline__ void __iRestore(const uint8_t *__s)
74+
{
75+
SREG = *__s;
76+
__asm__ volatile ("" ::: "memory");
77+
}
78+
#endif /* !__DOXYGEN__ */
79+
80+
/** \file */
81+
/** \defgroup util_atomic <util/atomic.h> Atomically and Non-Atomically Executed Code Blocks
82+
83+
\code
84+
#include <util/atomic.h>
85+
\endcode
86+
87+
\note The macros in this header file require the ISO/IEC 9899:1999
88+
("ISO C99") feature of for loop variables that are declared inside
89+
the for loop itself. For that reason, this header file can only
90+
be used if the standard level of the compiler (option --std=) is
91+
set to either \c c99 or \c gnu99.
92+
93+
The macros in this header file deal with code blocks that are
94+
guaranteed to be executed Atomically or Non-Atmomically. The term
95+
"Atomic" in this context refers to the unability of the respective
96+
code to be interrupted.
97+
98+
These macros operate via automatic manipulation of the Global
99+
Interrupt Status (I) bit of the SREG register. Exit paths from
100+
both block types are all managed automatically without the need
101+
for special considerations, i. e. the interrupt status will be
102+
restored to the same value it has been when entering the
103+
respective block.
104+
105+
A typical example that requires atomic access is a 16 (or more)
106+
bit variable that is shared between the main execution path and an
107+
ISR. While declaring such a variable as volatile ensures that the
108+
compiler will not optimize accesses to it away, it does not
109+
guarantee atomic access to it. Assuming the following example:
110+
111+
\code
112+
#include <inttypes.h>
113+
#include <avr/interrupt.h>
114+
#include <avr/io.h>
115+
116+
volatile uint16_t ctr;
117+
118+
ISR(TIMER1_OVF_vect)
119+
{
120+
ctr--;
121+
}
122+
123+
...
124+
int
125+
main(void)
126+
{
127+
...
128+
ctr = 0x200;
129+
start_timer();
130+
while (ctr != 0)
131+
// wait
132+
;
133+
...
134+
}
135+
\endcode
136+
137+
There is a chance where the main context will exit its wait loop
138+
when the variable \c ctr just reached the value 0xFF. This happens
139+
because the compiler cannot natively access a 16-bit variable
140+
atomically in an 8-bit CPU. So the variable is for example at
141+
0x100, the compiler then tests the low byte for 0, which succeeds.
142+
It then proceeds to test the high byte, but that moment the ISR
143+
triggers, and the main context is interrupted. The ISR will
144+
decrement the variable from 0x100 to 0xFF, and the main context
145+
proceeds. It now tests the high byte of the variable which is
146+
(now) also 0, so it concludes the variable has reached 0, and
147+
terminates the loop.
148+
149+
Using the macros from this header file, the above code can be
150+
rewritten like:
151+
152+
\code
153+
#include <inttypes.h>
154+
#include <avr/interrupt.h>
155+
#include <avr/io.h>
156+
#include <util/atomic.h>
157+
158+
volatile uint16_t ctr;
159+
160+
ISR(TIMER1_OVF_vect)
161+
{
162+
ctr--;
163+
}
164+
165+
...
166+
int
167+
main(void)
168+
{
169+
...
170+
ctr = 0x200;
171+
start_timer();
172+
sei();
173+
uint16_t ctr_copy;
174+
do
175+
{
176+
ATOMIC_BLOCK(ATOMIC_FORCEON)
177+
{
178+
ctr_copy = ctr;
179+
}
180+
}
181+
while (ctr_copy != 0);
182+
...
183+
}
184+
\endcode
185+
186+
This will install the appropriate interrupt protection before
187+
accessing variable \c ctr, so it is guaranteed to be consistently
188+
tested. If the global interrupt state were uncertain before
189+
entering the ATOMIC_BLOCK, it should be executed with the
190+
parameter ATOMIC_RESTORESTATE rather than ATOMIC_FORCEON.
191+
192+
See \ref optim_code_reorder for things to be taken into account
193+
with respect to compiler optimizations.
194+
*/
195+
196+
/** \def ATOMIC_BLOCK(type)
197+
\ingroup util_atomic
198+
199+
Creates a block of code that is guaranteed to be executed
200+
atomically. Upon entering the block the Global Interrupt Status
201+
flag in SREG is disabled, and re-enabled upon exiting the block
202+
from any exit path.
203+
204+
Two possible macro parameters are permitted, ATOMIC_RESTORESTATE
205+
and ATOMIC_FORCEON.
206+
*/
207+
#if defined(__DOXYGEN__)
208+
#define ATOMIC_BLOCK(type)
209+
#else
210+
#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
211+
__ToDo ; __ToDo = 0 )
212+
#endif /* __DOXYGEN__ */
213+
214+
/** \def NONATOMIC_BLOCK(type)
215+
\ingroup util_atomic
216+
217+
Creates a block of code that is executed non-atomically. Upon
218+
entering the block the Global Interrupt Status flag in SREG is
219+
enabled, and disabled upon exiting the block from any exit
220+
path. This is useful when nested inside ATOMIC_BLOCK sections,
221+
allowing for non-atomic execution of small blocks of code while
222+
maintaining the atomic access of the other sections of the parent
223+
ATOMIC_BLOCK.
224+
225+
Two possible macro parameters are permitted,
226+
NONATOMIC_RESTORESTATE and NONATOMIC_FORCEOFF.
227+
*/
228+
#if defined(__DOXYGEN__)
229+
#define NONATOMIC_BLOCK(type)
230+
#else
231+
#define NONATOMIC_BLOCK(type) for ( type, __ToDo = __iSeiRetVal(); \
232+
__ToDo ; __ToDo = 0 )
233+
#endif /* __DOXYGEN__ */
234+
235+
/** \def ATOMIC_RESTORESTATE
236+
\ingroup util_atomic
237+
238+
This is a possible parameter for ATOMIC_BLOCK. When used, it will
239+
cause the ATOMIC_BLOCK to restore the previous state of the SREG
240+
register, saved before the Global Interrupt Status flag bit was
241+
disabled. The net effect of this is to make the ATOMIC_BLOCK's
242+
contents guaranteed atomic, without changing the state of the
243+
Global Interrupt Status flag when execution of the block
244+
completes.
245+
*/
246+
#if defined(__DOXYGEN__)
247+
#define ATOMIC_RESTORESTATE
248+
#else
249+
#define ATOMIC_RESTORESTATE uint8_t sreg_save \
250+
__attribute__((__cleanup__(__iRestore))) = SREG
251+
#endif /* __DOXYGEN__ */
252+
253+
/** \def ATOMIC_FORCEON
254+
\ingroup util_atomic
255+
256+
This is a possible parameter for ATOMIC_BLOCK. When used, it will
257+
cause the ATOMIC_BLOCK to force the state of the SREG register on
258+
exit, enabling the Global Interrupt Status flag bit. This saves on
259+
flash space as the previous value of the SREG register does not
260+
need to be saved at the start of the block.
261+
262+
Care should be taken that ATOMIC_FORCEON is only used when it is
263+
known that interrupts are enabled before the block's execution or
264+
when the side effects of enabling global interrupts at the block's
265+
completion are known and understood.
266+
*/
267+
#if defined(__DOXYGEN__)
268+
#define ATOMIC_FORCEON
269+
#else
270+
#define ATOMIC_FORCEON uint8_t sreg_save \
271+
__attribute__((__cleanup__(__iSeiParam))) = 0
272+
#endif /* __DOXYGEN__ */
273+
274+
/** \def NONATOMIC_RESTORESTATE
275+
\ingroup util_atomic
276+
277+
This is a possible parameter for NONATOMIC_BLOCK. When used, it
278+
will cause the NONATOMIC_BLOCK to restore the previous state of
279+
the SREG register, saved before the Global Interrupt Status flag
280+
bit was enabled. The net effect of this is to make the
281+
NONATOMIC_BLOCK's contents guaranteed non-atomic, without changing
282+
the state of the Global Interrupt Status flag when execution of
283+
the block completes.
284+
*/
285+
#if defined(__DOXYGEN__)
286+
#define NONATOMIC_RESTORESTATE
287+
#else
288+
#define NONATOMIC_RESTORESTATE uint8_t sreg_save \
289+
__attribute__((__cleanup__(__iRestore))) = SREG
290+
#endif /* __DOXYGEN__ */
291+
292+
/** \def NONATOMIC_FORCEOFF
293+
\ingroup util_atomic
294+
295+
This is a possible parameter for NONATOMIC_BLOCK. When used, it
296+
will cause the NONATOMIC_BLOCK to force the state of the SREG
297+
register on exit, disabling the Global Interrupt Status flag
298+
bit. This saves on flash space as the previous value of the SREG
299+
register does not need to be saved at the start of the block.
300+
301+
Care should be taken that NONATOMIC_FORCEOFF is only used when it
302+
is known that interrupts are disabled before the block's execution
303+
or when the side effects of disabling global interrupts at the
304+
block's completion are known and understood.
305+
*/
306+
#if defined(__DOXYGEN__)
307+
#define NONATOMIC_FORCEOFF
308+
#else
309+
#define NONATOMIC_FORCEOFF uint8_t sreg_save \
310+
__attribute__((__cleanup__(__iCliParam))) = 0
311+
#endif /* __DOXYGEN__ */
312+
313+
#endif

0 commit comments

Comments
 (0)