Martin_from_K
Martin_from_K

Reputation: 191

Is there something like ATOMIC_INC in CMSIS for Cortex-M 3 4 7?

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

Answers (1)

artless-noise-bye-due2AI
artless-noise-bye-due2AI

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

Related Questions