memory-mapped I/O
/ MMIO, 'em-em-eye-oh' /
How does software talk to a device's control knobs at all? Two designs exist. In memory-mapped I/O, the device's control and status registers are made to look like ordinary memory addresses: writing to a particular address sends a command to the device, and reading from it returns the device's status. The CPU uses the very same load and store instructions it uses for RAM; the hardware routes those addresses to the device instead of memory.
Concretely, a device exposes a block of registers at some physical address range. To use them, the driver maps that range with ioremap, which gives a kernel virtual pointer; then a write to *reg at the right offset (done via accessors like writel/readl) actually pokes a hardware register. For example, writel(1, base + 0x10) might mean 'set bit 0 of the control register at offset 0x10 to start the device.' The alternative design, port-mapped I/O (PMIO), used mainly on x86, puts device registers in a separate I/O address space reached only by special instructions, in (read a port) and out (write a port), e.g. outb(value, 0x3F8) — these ports are NOT part of the memory address space and need privileged instructions. MMIO has won on modern hardware because it needs no special instructions and the registers can be a full address space wide.
MMIO matters because it is the fundamental way a driver actually commands hardware — almost every register poke in a modern driver is an MMIO access. The crucial honesty: MMIO addresses are NOT normal memory. Reads can have side effects (reading a status register may clear it), values change on their own (the device updates them), and the compiler must not cache, reorder, or skip these accesses — which is why you use volatile-correct accessor functions and memory barriers, never a plain dereference of a cached pointer.
// MMIO (works everywhere): void __iomem *base = ioremap(phys_addr, size); writel(START_BIT, base + 0x10); // poke control register u32 status = readl(base + 0x14); // read status register // PMIO (x86 only, separate I/O space): outb(0x01, 0x3F8); // write a byte to port 0x3F8
MMIO uses normal loads/stores via ioremap accessors; PMIO uses special in/out instructions in a separate port space.
MMIO addresses are not normal memory: reads can have side effects, values change on their own, and accesses must not be cached or reordered — use writel/readl-style accessors and barriers, never a plain *p on a cached pointer.