Reputation: 191
Cortex M 3 4 7 support LDREX and STREX assembler instructions and with these CMSIS provides for example ATOMIC_MODIFY_REG that ensures an atomic modification of an (u)int32_t (ie clear some bits and set some (maybe other) bits). Now I thought there also could be equivalently something like ATOMIC_INC and ATOMIC_DEC to atomically increment or decrement an (u)int32_t variable. But there isn't. Is there something wrong with this idea? I could easily change ATOMIC_MODIFY_REG into ATOMIC_INC but testing if this will really be atomic is not so easy. I am using STMCubeIDE, latest version.
Thanks for any help
Edit: not sure anymore if ATOMIC_MODIFY_REG is really CMSIS. here is the ATOMIC_MODIFY_REG I have in STM CubeIDE:
/* Atomic 32-bit register access macro to clear and set one or several bits */
#define ATOMIC_MODIFY_REG(REG, CLEARMSK, SETMASK) \
do { \
uint32_t val; \
do { \
val = (__LDREXW((__IO uint32_t *)&(REG)) & ~(CLEARMSK)) | (SETMASK); \
} while ((__STREXW(val,(__IO uint32_t *)&(REG))) != 0U); \
} while(0)
Upvotes: 2
Views: 834
Reputation: 22395
CMSIS 5.1 defines macros/functions for LDREX/STREX functionality. The macros/functions have variants for 'B'yte, 'H'alfword and 'W'ord. Ie, LDREXH, STREXB. It does not use the functionality to implement atomic/lock-free primitives, but you can use them to implement your own.
| Instruction | CMSIS function | | ------------ | ----------------------------------- | | LDREX | uint32_t __LDREXW (uint32_t \*addr) | | LDREXH | uint16_t __LDREXH (uint16_t \*addr) | | LDREXB | uint8_t __LDREXB (uint8_t \*addr) | | STREX | uint32_t __STREXW (uint32_t value, uint32_t \*addr) | | STREXH | uint32_t __STREXH (uint16_t value, uint16_t \*addr) | | STREXB | uint32_t __STREXB (uint8_t value, uint8_t \*addr) | | CLREX | void __CLREX (void) |
For single core CPUs and a bare metal single thread, there is no reason for an atomic increment (most drivers won't want an atomic increment). For bit fields shared with interrupts, it can be important to atomically RMW (read-modify-write) atomically. If you have an RTOS with scheduling, the scheduler MUST perform a CRLEX()
on a context switch. You can also use 'single reader/single writer' structures such as a ring buffer/fifo without resorting to atomics. However, I assume you have the knowledge and need to have these primitives.
I would use either C++ or the generated code to confirm a 'C' equivalent.
See: godbolt to process this SO example for some assembler,
create_id():
ldr r3, =ID
dmb ish
.L2:
ldrex r0, [r3]
adds r2, r0, #1
strex r1, r2, [r3]
cmp r1, #0
bne .L2
dmb ish
bx lr
This gives,
void atomic_inc(uint32_t *val)
{
DMB();
do {
uint32_t tmp = LDREX(val);
tmp++;
} while(STREX(tmp,val));
DMB();
}
For a CMSIS 'C' implementation. I would just use the C++ atomics and expose them as extern "C"
. There would be no overhead for object files that are just using the atomic C++ primitive and I would trust (and verify) the compiler. Lots of smart people have worked on making it correct.
You probably don't need the DMB() for non-SMP systems, but it is not that harmful unless performance is a must. As you can see, the loop could lock for eternity. You may wish to insert a retry counter depending on your application space and design criteria.
Upvotes: 2