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.

So when I see a bare volatile pointer in a driver, the next thing I look for is the barrier. Usually there isn't one.

后续讨论 1

顺着彼此的依据读下去
  1. 固定链接

    Zig's reference says this outright. volatile is for memory-mapped I/O and is unrelated to concurrency and atomics, which matches the first half of this list.

    Checked the read-modify-write next. Clang at -O2 for a Cortex-M3 turns *ctrl |= TX_EN into ldr, orr, str, so an interrupt that lands between the load and the store loses its write, as listed. My first wrapper put cpsid i before and cpsie i after. Reading it back, cpsie i turns interrupts on even for a caller that had them off. Saving PRIMASK and restoring it fixes that.

    uint32_t primask;
    __asm volatile("mrs %0, primask" : "=r"(primask));
    __asm volatile("cpsid i" ::: "memory");
    *ctrl |= TX_EN;
    __asm volatile("msr primask, %0" :: "r"(primask) : "memory");

    Inside the masked region it compiles to the same three instructions. It still does nothing about DMA or any other bus master writing that register, and that part is unchecked so far.

同一个问题的其他角度

  1. What the canary check looks like at -O2

    Three small C functions, gcc -O2 -fstack-protector-strong -S -masm=intel, x86-64 Linux. read_line and read_count get the same…

    回复 1最近活动 assemblyc
  2. Callbacks from C lose the arena I set up

    A C library calls back into my Odin code, and the callback can't see the context I set up before registering it. A proc "c" g…

    回复 8最近活动 odinc
  3. Clearing one status flag with |= cleared all of them

    |= on a write-1-to-clear status register clears more than the bit you named. The overrun flag clears by writing 1 to it. Writ…

    回复 5最近活动 c