Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reading a 64 bit volatile variable on cortex-m3

I have a 64 bit integer variable on a 32 bit Cortex-M3 ARM controller (STM32L1), which can be modified asynchronously by an interrupt handler.

volatile uint64_t v;
void some_interrupt_handler() {
    v = v + something;
}

Obviously, I need a way to access it in a way that prevents getting inconsistent, halfway updated values.

Here is the first attempt

static inline uint64_t read_volatile_uint64(volatile uint64_t *x) {
    uint64_t y;
    __disable_irq();
    y = *x;
    __enable_irq();
    return y;
}

The CMSIS inline functions __disable_irq() and __enable_irq() have an unfortunate side effect, forcing a memory barrier on the compiler, so I've tried to come up with something more fine-grained

static inline uint64_t read_volatile_uint64(volatile uint64_t *x) {
    uint64_t y;
    asm (   "cpsid i\n"
            "ldrd %[value], %[addr]\n"
            "cpsie i\n"
            : [value]"=r"(y) : [addr]"m"(*x));
    return y;
}

It still disables interrupts, which is not desirable, so I'm wondering if there's a way doing it without resorting to cpsid. The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors, Third Edition by Joseph Yiu says

If an interrupt request arrives when the processor is executing a multiple cycle instruction, such as an integer divide, the instruction could be abandoned and restarted after the interrupt handler completes. This behavior also applies to load double-word (LDRD) and store double-word (STRD) instructions.

Does it mean that I'll be fine by simply writing this?

static inline uint64_t read_volatile_uint64(volatile uint64_t *x) {
    uint64_t y;
    asm (   "ldrd %[value], %[addr]\n"
            : [value]"=&r"(y) : [addr]"m"(*x));
    return y;
}

(Using "=&r" to work around ARM errata 602117)

Is there some library or builtin function that does the same portably? I've tried atomic_load() in stdatomic.h, but it fails with undefined reference to '__atomic_load_8'.

like image 807
followed Monica to Codidact Avatar asked Mar 07 '17 14:03

followed Monica to Codidact


People also ask

Is ARM Cortex M4 64 bit?

Arm® Cortex®-M4 in a nutshell. The 32-bit Arm® Cortex®-M4 processor core is the first core of the Cortex-M line up to feature dedicated Digital Signal Processing (DSP) IP blocks, including an optional Floating-Point Unit (FPU).

What is LR in ARM?

On an ARM Cortex M series device, the link register (LR or R14) is a core register that stores the return address, such as when making a function call.

Is Cortex-M3 32-bit?

The 32-bit Arm® Cortex®-M3 core processor is designed for high-performance, real-time processing in cost-constrained applications and can handle complex tasks. Any Arm® Cortex®-M3 microcontroller offers high scalability combined with an optimal trade-off between performance and cost.

Does Cortex-M3 have FPU?

Summary. One of the most important differences between the Cortex ® -M4 MCU and Cortex ® -M3 MCU is that an optional Floating Point Unit (FPU) is added into the Cortex ® -M4 Core to enhance the floating-point data operations.


1 Answers

Yes, using a simple ldrd is safe in this application since it will be restarted (not resumed) if interrupted, hence it will appear atomic from the interrupt handler's point of view.

This holds more generally for all load instructions except those that are exception-continuable, which are a very restricted subset:

  • only ldm, pop, vldm, and vpop can be continuable
  • an instruction inside an it-block is never continuable
  • an ldm/pop whose first loaded register is also the base register (e.g. ldm r0, { r0, r1 }) is never continuable

This gives plenty of options for atomically reading a multi-word variable that's modified by an interrupt handler on the same core. If the data you wish to read is not a contiguous array of words then you can do something like:

1:      ldrex   %[val0], [%[ptr]]       // can also be byte/halfword
        ... more loads here ...
        strex   %[retry], %[val0], [%[ptr]]
        cbz     %[retry], 2f
        b       1b
2:

It doesn't really matter which word (or byte/halfword) you use for the ldrex/strex since an exception will perform an implicit clrex.

The other direction, writing a variable that's read by an interrupt handler is a lot harder. I'm not 100% sure but I think the only stores that are guaranteed to appear atomic to an interrupt handler are those that are "single-copy atomic", i.e. single byte, aligned halfword, and aligned word. Anything bigger would require disabling interrupts or using some clever lock-free structure.

like image 129
Matthijs Avatar answered Oct 04 '22 03:10

Matthijs