cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G0 Concurrency Question

Carl_G
Senior

I have some data that is accessed from a normal context and also from an IRQ. I am disabling the interrupt to prevent interrupted access. So no concerns there. However, what about forcing both the normal context and the interrupt context to have the same data? Do I need to declare every accessed variable as std::atomic? Or can I wrap the section of code in something else that ensures every variable inside of it gets flushed or something once that function is exited?

example code

void PRL_Receive::CleanStart()
{
	uint32_t ien;
	ien = NVIC_GetEnableIRQ(UCPD1_2_IRQn);
	NVIC_DisableIRQ(UCPD1_2_IRQn);

	state = PRL_Rx_Wait_for_PHY_Message;
	entry = true;

	bMsgReceivedFlag = false;
	isSoftReset = false;
	MessageID = 0U;
	bMsgIDRecvd = false;
	isCRCSent = false;
	u8txResult=0;
	bResultValid=false;

	prlRxRecvBytes = 0U;
	if(ien){
		NVIC_EnableIRQ(UCPD1_2_IRQn);
	}
}

 

after this is called by the IRQ I want the normal context to see all the updated values the next time it accesses any of these. Or if its called by the normal context, the next time the IRQ access them I want it to have the latest value as well. Apparently, in c/c++ volatile is not enough.

 

 

4 REPLIES 4
Ozone
Lead II

I suppose you are mixing up different things here.

> Do I need to declare every accessed variable as std::atomic?
> ...
> Apparently, in c/c++ volatile is not enough.

Surely "volatile" does not apply here. This just instructs to actually access the variable when the source code does so, and not try any optimisations based upon static analysis.
While I have limited experience with C++, "atomic" usually refers to the object / memory cell access alone. This will not prevent any interrupt from changing the value in between subsequent accesses. Because this is usually what happens - get a "state flag", perform an action, and update that "state flag".

Full-size OSes and multithreading environments (like Pthreds) provide proper tools for this problem, mutexes, semaphores, or critical sections.

> I have some data that is accessed from a normal context and also from an IRQ. I am disabling the interrupt to prevent interrupted access.

AFAIK those multithreading environments do it the same way, by disabling interrupts and task switching to avoid race conditions during accessing.
When you wrap each access from the application context in DI / EI, the variables should be consistent in every instance.

KnarfB
Principal III

Apparently, in c/c++ volatile is not enough.

Because of what observations?

 

Put those variables in a struct to make it explicit that they should be updated together.

To ensure data integrity, use a critical section everywhere where the struct is accessed. This is usually implemented by __disable_irq() and __enable_irq() expanding to a single (atomic) machine instruction. Implement nesting if needed.

 

volatile is required because the compiler doesn't know about interrupts. An interrupt may change some global variable at any time (unforseeable by the compiler) whose previous value was already loaded to a register due to optimization and becomes now invalid.

 

AFAIK the atomic library is not included in the default STM32 gcc distribution.

hth

KnarfB

 

Hi,

The variables are members of a class. With the above including barriers i.e.

__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->ICER[0U] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    __DSB();
    __ISB();
  }
__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    __COMPILER_BARRIER();
    NVIC->ISER[0U] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    __COMPILER_BARRIER();
  }
}

 

Do I still need the variables to be volatile? Or will the barriers protect from any external reordering happening? Also, this function is called both by IRQ and from non-IRQ code. is that fine?

Pavel A.
Evangelist III

Recent CubeIDE versions have both stdatomic.h for C and <atomic> for C++ std::atomic.

IIRC, C++ and C standards say that operations on atomics are ensured to be atomic only with respect to other explicit atomic operations, but not with respect to hardware interrupts. Behavior with respect to  hardware interrupts depends on specific hardware and compiler. GCC, clang and other sane compilers on sane hardware (like STM32) ensure that atomic of small types, properly aligned, do work with interrupts. To verify, you can look at the generated [dis]assembly.