Search code examples
catomickeilarm7

How to compare and swap atomically in ARM7?


I would like to modify a global variable which is shared by different tasks and IRQ contexts in a RTOS. Therefore I need to modify this variable atomically. In my current implementation, I have been using enable_irq/disable_irq functions to modify the statement atomically.

extern int g_var;

void set_bit_atomic(int mask)
{
    disable_irq();
    g_var |= mask;
    enable_irq();
}

I've found the __sync_bool_compare_and_swap function in GCC documentation as a helper for atomic operations.

My current toolchain is KEIL MDK, and I would like to switch to the approach shown below,

void set_bit_atomic(int mask)
{
    volatile int tmp;
    do {
        tmp = g_var;
    } while (!__sync_bool_compare_and_swap(&g_var, tmp, tmp | mask));
}

How can I write __sync_bool_compare_and_swap function in ARMv4 command set(as inline assembly)?


Solution

  • I have found a similar implementation for __kernel_cmpxchg function in Linux kernel source.

    It has been written for ARMv5 and earlier, and It seems to work for ARM7TDMI (ARMv4).

    1:      ldr     r3, [r2]        @ load current val
            subs    r3, r3, r0      @ compare with oldval
    2:      streq   r1, [r2]        @ store newval if eq
            rsbs    r0, r3, #0      @ set return val and C flag
            bx      lr              @ or "mov pc, lr" if no thumb support
    

    Details can be found at this link.

    There are two important issues that I would like to warn,

    1- __kernel_cmpxchg returns 0 when swap occurred, while __sync_bool_compare_and_swap function returns true.

    2- function prototypes are different.

    typedef int (*__kernel_cmpxchg_t)(int oldval, int newval, volatile int *ptr);
    #define __kernel_cmpxchg ((__kernel_cmpxchg_t)0xffff0fc0)
    
    bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
    

    Therefore I had to change the usage as below,

    void set_bit_atomic(int mask)
    {
        volatile int tmp;
        do {
            tmp = g_var;
        } while (my_compare_and_swap(tmp, tmp | mask, &g_var));
    }
    

    Caveat: This code does not work properly without kernel support. See the comments below.