3
votes

INTRODUCTION: I've designed an embedded system featuring an ATSAME54N20A 32-bit ARM® Cortex®-M4F MCU. The board will be assembled and ready for programming soon so I was setting up my programming environment. I went for a bare-bone solution where only the minimum C-written files necessary are present, because although it's a time consuming process, it helps me to understand the system workings. The compiler chosen is GCC with the following arguments:

"...\arm-none-eabi-gcc.exe"  -x c -mthumb -O1 -ffunction-sections -mlong-calls -g3 -Wall -mcpu=cortex-m4 -c -std=gnu99 main.c -o main.o

...

"...\arm-none-eabi-gcc.exe" weak_handlers.o main.o SEGGER_RTT.o SEGGER_RTT_printf.o SEGGER_RTT_Syscalls_GCC.o -mthumb -Wl,-Map="app.map" -Wl,--start-group -lm  -Wl,--end-group -Wl,--gc-sections -mcpu=cortex-m4 -T flash.ld -o app.elf

QUESTION: The reference programming project I'm using to compare my code against ( Atmel Studio LEDflasher example ) uses critical sections like the following: ( present on hri_nvmctrl_e54.h line 944 )

NVMCTRL_CRITICAL_SECTION_ENTER();
((Nvmctrl *)hw)->CTRLA.reg |= NVMCTRL_CTRLA_RWS(mask);
NVMCTRL_CRITICAL_SECTION_LEAVE();

Which I don't understand. I tried to follow those function implementations to see what they were doing and ended up with the following code:

// ==============================================================================================
// Enter critical section.
// ==============================================================================================
// Get primask
register uint32_t __regPriMask         __asm__("primask");
uint32_t volatile *atomic = __regPriMask;
// Disable IRQ interrupts by setting the I-bit in the CPSR.
// Can only be executed in Privileged modes.
__asm__ volatile ("cpsid i" : : : "memory");
// Memory barrier
do {\
        __asm__ volatile ("isb 0xF":::"memory");
        __asm__ volatile ("dmb 0xF":::"memory");
        __asm__ volatile ("isb 0xF":::"memory");
} while (0U);


// ==============================================================================================
// 25.8.1 Control A
// ==============================================================================================
// NVMCTRL->     offset: CTRLA
// 0x41004000U           0x00000000U  
(*(volatile uint32_t*)0x41004000U) = 0x01000400U;


// ==============================================================================================
// Leave critical section.
// ==============================================================================================
// Memory barrier
do {\
        __asm__ volatile ("isb 0xF":::"memory");
        __asm__ volatile ("dmb 0xF":::"memory");
        __asm__ volatile ("isb 0xF":::"memory");
} while (0U);
// Set primask
  __regPriMask = &atomic;

Do any of this memory barriers make sense? Is wrapping a asm volatile ("dmb 0xF":::"memory"); between two asm volatile ("isb 0xF":::"memory"); a common useful implementation? What would those instructions mean? I'm not sure if the "GoTo Implementation" path was followed correctly to end up with these statements!

I'd like to thank everyone in advance for your time and hope this question helps others in the future !

1

1 Answers

2
votes

Do any of this memory barriers make sense?

In my eyes, yes. If there are caches, interrupts, optimisations, load latencies, etc, memory barries may be a must.

Is wrapping a asm volatile ("dmb 0xF":::"memory"); between two asm volatile ("isb 0xF":::"memory"); a common useful implementation? What would those instructions mean?

isb : it flushs buffers and fetchs instructions so far.

dmb : it completes all memory access so far.

isb : after dmb, it ensures that the new context has been loaded.

It is an useful and a very safe (and defensive) way to protect the code against procesor/compiler re-ordering, latencies, etc.

Furthermore, this critical region is disabling the interrupts in order to promote it to the highest priority task, so nobody will be able to interrupt this part of the code. Definitely, a proper way to protect a critical region.