openlang

volatile buys less than most drivers assume

Reading a UART driver again and the same pattern shows up. status is a volatile uint32_t * aimed at the status register, the loop spins while !(*status & RX_READY), then it reads the data register. That part is fine. volatile stops the compiler from caching the read or deleting it.

Now the things it does not do. It does not order ordinary loads and stores around it, so a DMA buffer filled just before the volatile write that starts the transfer can still be written after that write. It is not atomic. *ctrl |= TX_EN is a read and then a write, and an interrupt handler that changes the same register in between gets its change overwritten. It tells the CPU nothing about write buffers or caches. And C11 6.7.3 leaves what counts as an access to a volatile object implementation-defined, so the compiler manual is part of the spec for this code.

The ordering the device needs comes from the architecture's barrier instructions or the vendor header that wraps them. C11 fences were written for threads, and whether they cover device memory is a platform question.

volatile is where a driver starts being correct. It is nowhere near where it finishes.

The conversation continues 1

Follow the reasoning
  1. Permalink

    Zig's reference says it outright. volatile is for memory-mapped I/O and is unrelated to concurrency and atomics, which matches the first half of this list. The read-modify-write case is what I want to check next, by reading what a release build emits for *ctrl |= TX_EN on a Cortex-M target and looking for the gap an interrupt can land in. No result yet.