|
| 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 | + sei(); |
| 46 | + return 1; |
| 47 | +} |
| 48 | + |
| 49 | +static __inline__ uint8_t __iCliRetVal(void) |
| 50 | +{ |
| 51 | + // Just do nothing |
| 52 | + // cli(); |
| 53 | + return 1; |
| 54 | +} |
| 55 | + |
| 56 | +static __inline__ void __iSeiParam(const uint8_t *__s) |
| 57 | +{ |
| 58 | + // Just do nothing |
| 59 | + // sei(); |
| 60 | + __asm__ volatile ("" ::: "memory"); |
| 61 | + (void)__s; |
| 62 | +} |
| 63 | + |
| 64 | +static __inline__ void __iCliParam(const uint8_t *__s) |
| 65 | +{ |
| 66 | + // Just do nothing |
| 67 | + // cli(); |
| 68 | + __asm__ volatile ("" ::: "memory"); |
| 69 | + (void)__s; |
| 70 | +} |
| 71 | + |
| 72 | +static __inline__ void __iRestore(const uint8_t *__s) |
| 73 | +{ |
| 74 | + SREG = *__s; |
| 75 | + __asm__ volatile ("" ::: "memory"); |
| 76 | +} |
| 77 | +#endif /* !__DOXYGEN__ */ |
| 78 | + |
| 79 | +/** \file */ |
| 80 | +/** \defgroup util_atomic <util/atomic.h> Atomically and Non-Atomically Executed Code Blocks |
| 81 | +
|
| 82 | + \code |
| 83 | + #include <util/atomic.h> |
| 84 | + \endcode |
| 85 | +
|
| 86 | + \note The macros in this header file require the ISO/IEC 9899:1999 |
| 87 | + ("ISO C99") feature of for loop variables that are declared inside |
| 88 | + the for loop itself. For that reason, this header file can only |
| 89 | + be used if the standard level of the compiler (option --std=) is |
| 90 | + set to either \c c99 or \c gnu99. |
| 91 | +
|
| 92 | + The macros in this header file deal with code blocks that are |
| 93 | + guaranteed to be excuted Atomically or Non-Atmomically. The term |
| 94 | + "Atomic" in this context refers to the unability of the respective |
| 95 | + code to be interrupted. |
| 96 | +
|
| 97 | + These macros operate via automatic manipulation of the Global |
| 98 | + Interrupt Status (I) bit of the SREG register. Exit paths from |
| 99 | + both block types are all managed automatically without the need |
| 100 | + for special considerations, i. e. the interrupt status will be |
| 101 | + restored to the same value it has been when entering the |
| 102 | + respective block. |
| 103 | +
|
| 104 | + A typical example that requires atomic access is a 16 (or more) |
| 105 | + bit variable that is shared between the main execution path and an |
| 106 | + ISR. While declaring such a variable as volatile ensures that the |
| 107 | + compiler will not optimize accesses to it away, it does not |
| 108 | + guarantee atomic access to it. Assuming the following example: |
| 109 | +
|
| 110 | + \code |
| 111 | +#include <inttypes.h> |
| 112 | +#include <avr/interrupt.h> |
| 113 | +#include <avr/io.h> |
| 114 | +
|
| 115 | +volatile uint16_t ctr; |
| 116 | +
|
| 117 | +ISR(TIMER1_OVF_vect) |
| 118 | +{ |
| 119 | + ctr--; |
| 120 | +} |
| 121 | +
|
| 122 | +... |
| 123 | +int |
| 124 | +main(void) |
| 125 | +{ |
| 126 | + ... |
| 127 | + ctr = 0x200; |
| 128 | + start_timer(); |
| 129 | + while (ctr != 0) |
| 130 | + // wait |
| 131 | + ; |
| 132 | + ... |
| 133 | +} |
| 134 | + \endcode |
| 135 | +
|
| 136 | + There is a chance where the main context will exit its wait loop |
| 137 | + when the variable \c ctr just reached the value 0xFF. This happens |
| 138 | + because the compiler cannot natively access a 16-bit variable |
| 139 | + atomically in an 8-bit CPU. So the variable is for example at |
| 140 | + 0x100, the compiler then tests the low byte for 0, which succeeds. |
| 141 | + It then proceeds to test the high byte, but that moment the ISR |
| 142 | + triggers, and the main context is interrupted. The ISR will |
| 143 | + decrement the variable from 0x100 to 0xFF, and the main context |
| 144 | + proceeds. It now tests the high byte of the variable which is |
| 145 | + (now) also 0, so it concludes the variable has reached 0, and |
| 146 | + terminates the loop. |
| 147 | +
|
| 148 | + Using the macros from this header file, the above code can be |
| 149 | + rewritten like: |
| 150 | +
|
| 151 | + \code |
| 152 | +#include <inttypes.h> |
| 153 | +#include <avr/interrupt.h> |
| 154 | +#include <avr/io.h> |
| 155 | +#include <util/atomic.h> |
| 156 | +
|
| 157 | +volatile uint16_t ctr; |
| 158 | +
|
| 159 | +ISR(TIMER1_OVF_vect) |
| 160 | +{ |
| 161 | + ctr--; |
| 162 | +} |
| 163 | +
|
| 164 | +... |
| 165 | +int |
| 166 | +main(void) |
| 167 | +{ |
| 168 | + ... |
| 169 | + ctr = 0x200; |
| 170 | + start_timer(); |
| 171 | + sei(); |
| 172 | + uint16_t ctr_copy; |
| 173 | + do |
| 174 | + { |
| 175 | + ATOMIC_BLOCK(ATOMIC_FORCEON) |
| 176 | + { |
| 177 | + ctr_copy = ctr; |
| 178 | + } |
| 179 | + } |
| 180 | + while (ctr_copy != 0); |
| 181 | + ... |
| 182 | +} |
| 183 | + \endcode |
| 184 | +
|
| 185 | + This will install the appropriate interrupt protection before |
| 186 | + accessing variable \c ctr, so it is guaranteed to be consistently |
| 187 | + tested. If the global interrupt state were uncertain before |
| 188 | + entering the ATOMIC_BLOCK, it should be executed with the |
| 189 | + parameter ATOMIC_RESTORESTATE rather than ATOMIC_FORCEON. |
| 190 | +
|
| 191 | + See \ref optim_code_reorder for things to be taken into account |
| 192 | + with respect to compiler optimizations. |
| 193 | +*/ |
| 194 | + |
| 195 | +/** \def ATOMIC_BLOCK(type) |
| 196 | + \ingroup util_atomic |
| 197 | +
|
| 198 | + Creates a block of code that is guaranteed to be executed |
| 199 | + atomically. Upon entering the block the Global Interrupt Status |
| 200 | + flag in SREG is disabled, and re-enabled upon exiting the block |
| 201 | + from any exit path. |
| 202 | +
|
| 203 | + Two possible macro parameters are permitted, ATOMIC_RESTORESTATE |
| 204 | + and ATOMIC_FORCEON. |
| 205 | +*/ |
| 206 | +#if defined(__DOXYGEN__) |
| 207 | +#define ATOMIC_BLOCK(type) |
| 208 | +#else |
| 209 | +#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \ |
| 210 | + __ToDo ; __ToDo = 0 ) |
| 211 | +#endif /* __DOXYGEN__ */ |
| 212 | + |
| 213 | +/** \def NONATOMIC_BLOCK(type) |
| 214 | + \ingroup util_atomic |
| 215 | +
|
| 216 | + Creates a block of code that is executed non-atomically. Upon |
| 217 | + entering the block the Global Interrupt Status flag in SREG is |
| 218 | + enabled, and disabled upon exiting the block from any exit |
| 219 | + path. This is useful when nested inside ATOMIC_BLOCK sections, |
| 220 | + allowing for non-atomic execution of small blocks of code while |
| 221 | + maintaining the atomic access of the other sections of the parent |
| 222 | + ATOMIC_BLOCK. |
| 223 | +
|
| 224 | + Two possible macro parameters are permitted, |
| 225 | + NONATOMIC_RESTORESTATE and NONATOMIC_FORCEOFF. |
| 226 | +*/ |
| 227 | +#if defined(__DOXYGEN__) |
| 228 | +#define NONATOMIC_BLOCK(type) |
| 229 | +#else |
| 230 | +#define NONATOMIC_BLOCK(type) for ( type, __ToDo = __iSeiRetVal(); \ |
| 231 | + __ToDo ; __ToDo = 0 ) |
| 232 | +#endif /* __DOXYGEN__ */ |
| 233 | + |
| 234 | +/** \def ATOMIC_RESTORESTATE |
| 235 | + \ingroup util_atomic |
| 236 | +
|
| 237 | + This is a possible parameter for ATOMIC_BLOCK. When used, it will |
| 238 | + cause the ATOMIC_BLOCK to restore the previous state of the SREG |
| 239 | + register, saved before the Global Interrupt Status flag bit was |
| 240 | + disabled. The net effect of this is to make the ATOMIC_BLOCK's |
| 241 | + contents guaranteed atomic, without changing the state of the |
| 242 | + Global Interrupt Status flag when execution of the block |
| 243 | + completes. |
| 244 | +*/ |
| 245 | +#if defined(__DOXYGEN__) |
| 246 | +#define ATOMIC_RESTORESTATE |
| 247 | +#else |
| 248 | +#define ATOMIC_RESTORESTATE uint8_t sreg_save \ |
| 249 | + __attribute__((__cleanup__(__iRestore))) = SREG |
| 250 | +#endif /* __DOXYGEN__ */ |
| 251 | + |
| 252 | +/** \def ATOMIC_FORCEON |
| 253 | + \ingroup util_atomic |
| 254 | +
|
| 255 | + This is a possible parameter for ATOMIC_BLOCK. When used, it will |
| 256 | + cause the ATOMIC_BLOCK to force the state of the SREG register on |
| 257 | + exit, enabling the Global Interrupt Status flag bit. This saves on |
| 258 | + flash space as the previous value of the SREG register does not |
| 259 | + need to be saved at the start of the block. |
| 260 | +
|
| 261 | + Care should be taken that ATOMIC_FORCEON is only used when it is |
| 262 | + known that interrupts are enabled before the block's execution or |
| 263 | + when the side effects of enabling global interrupts at the block's |
| 264 | + completion are known and understood. |
| 265 | +*/ |
| 266 | +#if defined(__DOXYGEN__) |
| 267 | +#define ATOMIC_FORCEON |
| 268 | +#else |
| 269 | +#define ATOMIC_FORCEON uint8_t sreg_save \ |
| 270 | + __attribute__((__cleanup__(__iSeiParam))) = 0 |
| 271 | +#endif /* __DOXYGEN__ */ |
| 272 | + |
| 273 | +/** \def NONATOMIC_RESTORESTATE |
| 274 | + \ingroup util_atomic |
| 275 | +
|
| 276 | + This is a possible parameter for NONATOMIC_BLOCK. When used, it |
| 277 | + will cause the NONATOMIC_BLOCK to restore the previous state of |
| 278 | + the SREG register, saved before the Global Interrupt Status flag |
| 279 | + bit was enabled. The net effect of this is to make the |
| 280 | + NONATOMIC_BLOCK's contents guaranteed non-atomic, without changing |
| 281 | + the state of the Global Interrupt Status flag when execution of |
| 282 | + the block completes. |
| 283 | +*/ |
| 284 | +#if defined(__DOXYGEN__) |
| 285 | +#define NONATOMIC_RESTORESTATE |
| 286 | +#else |
| 287 | +#define NONATOMIC_RESTORESTATE uint8_t sreg_save \ |
| 288 | + __attribute__((__cleanup__(__iRestore))) = SREG |
| 289 | +#endif /* __DOXYGEN__ */ |
| 290 | + |
| 291 | +/** \def NONATOMIC_FORCEOFF |
| 292 | + \ingroup util_atomic |
| 293 | +
|
| 294 | + This is a possible parameter for NONATOMIC_BLOCK. When used, it |
| 295 | + will cause the NONATOMIC_BLOCK to force the state of the SREG |
| 296 | + register on exit, disabling the Global Interrupt Status flag |
| 297 | + bit. This saves on flash space as the previous value of the SREG |
| 298 | + register does not need to be saved at the start of the block. |
| 299 | +
|
| 300 | + Care should be taken that NONATOMIC_FORCEOFF is only used when it |
| 301 | + is known that interrupts are disabled before the block's execution |
| 302 | + or when the side effects of disabling global interrupts at the |
| 303 | + block's completion are known and understood. |
| 304 | +*/ |
| 305 | +#if defined(__DOXYGEN__) |
| 306 | +#define NONATOMIC_FORCEOFF |
| 307 | +#else |
| 308 | +#define NONATOMIC_FORCEOFF uint8_t sreg_save \ |
| 309 | + __attribute__((__cleanup__(__iCliParam))) = 0 |
| 310 | +#endif /* __DOXYGEN__ */ |
| 311 | + |
| 312 | +#endif |
0 commit comments