Intro

Memory-Mapped I/O (aka MMIO) is, in my opinion, one of the coolest and most useful features of processors.

In this series I’m going to explain MMIO and how it’s used in Microcontrollers and PCIe. I’m also going to go into the Userspace I/O Linux driver and how it’s used to write userspace drivers.

History

The way it works harkens back to some of the earliest CPUs. Back in those days, computers were composed of many discrete chips. The CPU chip itself was merely the execution unit - it was pure processing (registers, ALU, control logic). Even caches were external chips. In order to access any kind of memory (anything that wasn’t in the CPU registers), it put the address on external pins, toggled some other pins to indicate an access was happening and its type (read or write), and then used data pins to send/receive the data value.
Hardware designers were able to exploit this by having dedicated circuitry that decodes some of the address bits. For example, if the upper address bits were in one range activate the RAM chips. If it was another range, activate some kind of logic device (like a graphics output chip or MMU or a sound chip or whatever the board had on it).

These days, CPUs are very integrated. At a minimum, most chips have the MMU, the memory controller, and many of the highest speed I/O busses (like PCIe) on the same package as the CPU. Quite a few even have a GPU built in. But the idea of “everything is a memory address” lives on.

This is a multi-part post, continue here: