Search code examples
catomicc99isr

C99 "atomic" load in baremetal portable library


I'm working on a portable library for baremetal embedded applications.

Assume that I have a timer ISR that increments a counter and, in the main loop, this counter read is from in a most certainly not atomic load.

I'm trying to ensure load consistency (i.e. that I'm not reading garbage because the load was interrupted and the value changed) without resorting to disabling interrupts. It does not matter if the value changed after reading the counter as long as the read value is proper. Does this do the trick?

uint32_t read(volatile uint32_t *var){
    uint32_t value;
    do { value = *var; } while(value != *var);
    return value;
}

Solution

  • Are you running on any systems that have uint32_t larger than a single assembly instruction word read/write size? If not, the IO to memory should be a single instructions and therefore atomic (assuming the bus is also word sized...) You get in trouble when the compiler breaks it up into multiple smaller read/writes. Otherwise, I've always had to resort to DI/EI. You could have the user configure your library such that it has information if atomic instructions or minimum 32-bit word size are available to prevent interrupt twiddling. If you have these guarantees, you don't need to verification code.

    To answer the question though, on a system that must split the read/writes, your code is not safe. Imagine a case where you read your value in correctly in the "do" part, but the value gets split during the "while" part check. Further, in an extreme case, this is an infinite loop. For complete safety, you'd need a retry count and error condition to prevent that. The loop case is extreme for sure, but I'd want it just in case. That of course makes the run time longer.

    Let's show a failure case for examples - will use 16-bit numbers on a machine that reads 8-bit values at a time to make it easier to follow:

    1. Value to read from memory *var is 0x1234
    2. Read 8-bit 0x12
    3. *var becomes 0x5678
    4. Read 8-bit 0x78 - value is now 0x1278 (invalid)
    5. *var becomes 0x1234
    6. Verification step reads 8-bit 0x12
    7. *var becomes 0x5678
    8. Verification reads 8-bit 0x78

    Value confirmed correct 0x1278, but this is an error as *var was only 0x1234 and 0x5678.

    Another failure case would be when *var just happens to change at the same frequency as your code is running, which could lead to an infinite loop as each verification fails. Or even if it did break out eventually, this would be a very hard to track performance bug.