Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can this be atomically executed?

I would like to know whether it is possible to ensure line is atomically executed, given that it could be executed by both the ISR and Main context. I'm working on an ARM9 (LPC313x) and using RealView 4 (armcc).

foo() { 
  ..
  stack_var = ++volatile_var; // line
  ..
}

I'm looking for any routine like _atomic_ for C166, direct assembly code, etc. I would prefer not to have to disable the interrupts.

Thank you very much.

like image 643
JoeSlav Avatar asked Mar 24 '11 11:03

JoeSlav


2 Answers

No, I don't think that you ever can expect ++volatile_var to be atomic, even if you don't have the assignment. Use a proper atomic primitive for that. If your compiler doesn't provide such an extension you easily find short inline assembler for that on the web. The assembler instructions are call ldrex and strex for atomic exchange on arm, I think.

Edit: it seems that the specific processor type that is asked for in the question does not implement these instructions.

Edit: The following should work with gcc, for another compiler one probably has to adapt the __asm__ parts.

inline
size_t arm_ldrex(size_t volatile*ptr) {
  size_t ret;
  __asm__ volatile ("ldrex %0,[%1]\t@ load exclusive\n"
                    : "=&r" (ret)
                    : "r" (ptr)
                    : "cc", "memory"
                    );
  return ret;
}

inline
_Bool arm_strex(size_t volatile*ptr, size_t val) {
  size_t error;
  __asm__ volatile ("strex %0,%1,[%2]\t@ store exclusive\n"
                    : "=&r" (error)
                    : "r" (val), "r" (ptr)
                    : "cc", "memory"
                    );
  return !error;
}

inline
size_t atomic_add_fetch(size_t volatile *object, size_t operand) {
  for (;;) {
    size_t oldval = arm_ldrex(object);
    size_t newval = oldval + operand;
    if (arm_strex(object, newval)) return newval;
  }
}
like image 147
Jens Gustedt Avatar answered Oct 13 '22 00:10

Jens Gustedt


From a quick look, the C166 _atomic_ macro seems to utilize an instruction that effectively masks interrupts for the duration of a specified number of instructions. There is nothing directly corresponding to that in the ARM architecture.

You could of course use the swp instruction (or __swp intrinsic in the RealView toolchain) to implement a lock around the critical section. ldrex/strex mentioned in another answer do not exist in ARM architecture version 5, which includes the ARM9 processors. http://infocenter.arm.com/help/topic/com.arm.doc.dui0491c/CJAHDCHB.html and http://infocenter.arm.com/help/topic/com.arm.doc.dui0489c/Chdbbbai.html respectively.

A simplistic lock implementation around this (using the RealView toolchain) would be:

{
    /* Loop until lock acquired */
    while (__swp(LOCKED, &lockvar) == LOCKED);
    ..
    /* Critical section */
    ..
    lockvar = UNLOCKED;
}

However, this will lead to deadlock in the ISR context when the Main thread is holding the lock.

I think masking interrupts around the operation is likely to be the least hairy solution, although if your Main context is executing in User mode it will require a system call to implement.

like image 24
unixsmurf Avatar answered Oct 12 '22 22:10

unixsmurf