Electronic – Race condition in AVR timers

avrinterrupts

I'm trying to implement a global timer, so that I can call time_us() anywhere in program and the function will return microseconds since program start. I've done this using TIMER2 (8-bit clock) and interrupt on overflow, and counting the overflows:

volatile uint64_t _time_overflow_cnt;

void time_reset(){
    TCCR2A=0; // no pin output,
    TCCR2B=(1<<CS21); // prescaler=8
    TIMSK2=1<<TOIE2; // interrupt on overflow (256 cycles)
    sei();
    TCNT2=0; // reset timer to 0
    _time_overflow_cnt=0;
}

ISR(TIMER2_OVF_vect){
    _time_overflow_cnt++;
}

uint64_t time_us(){
    return (_time_overflow_cnt*256 + TCNT2)/(F_CPU/8/1000000ULL);
}

However, this code has a race condition, which is in my case quite noticeable – if we rewrite this code into assembly, we see that the time_us is quite lengthy (64-bit calculations take some time) and if TCNT2 overflows during this time, the calculations is skewed by whole 256 cycles. I tried turning interrupts off before and turning them back on after calculation, but this does not help – it only secures the overflow counter, while the TCNT2 keeps increasing.
I also tried saving overflow counter, then saving TCNT2, checking if the former changed in between, and a few other 'solutions' similar to this one, but all off them had some TOCTOU (Time Of Check to Time Of Use) problems. Have you got a better idea for implementation?

Best Answer

Just make the retrieval atomic.

#include <util/atomic.h>

 ...

uint64_t time_us(){
    register uint8_t tcnt2
    register uint64_t ovcnt;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        tcnt2 = TCNT2;
        ovcnt = _time_overflow_cnt;
    }
    return (ovcnt*256 + tcnt2)/(F_CPU/8/1000000ULL);
}