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