MMIO (memory-mapped I/O) and port I/O
A device exposes its controls as registers, small locations the driver reads for the device's state and writes to give it orders. With memory-mapped I/O (MMIO) the device answers a range of physical addresses, and ordinary loads and stores reach it once the range is mapped. x86 also has a separate port space of 65,536 addresses, reached only by the in and out instructions, at most 32 bits at a time; older devices such as the PS/2 controller still live there. ARM64 has no port space.
Registers are not memory. A read can have a side effect, such as clearing an interrupt cause or taking a byte off a FIFO, and a write can start a command, so writing a value twice can run it twice. Each access must happen exactly once, in program order, at the width the device documents: a 32-bit register read as four bytes is four different accesses, and many devices misbehave on a wrong width or an access not aligned to its width.
The mapping decides how the CPU caches the addresses, a page at a time. Registers need uncached device memory, where every access reaches the device in order and nothing is read speculatively. A framebuffer is plain memory on the device, slow to draw into uncached, so it is mapped write-combining: stores are gathered into bursts and give up their ordering. Normal cacheable memory suits RAM a coherent device shares with the CPU. A cached register fails quietly, returning stale values and holding writes in the cache.
In the driver kit, binding.MapRegisters returns a RegisterWindow, whose Read8 to Read64 and Write8 to Write64 each make one access of that width, checked against the bounds and, over a memory window, the alignment; over an x64 port range they use in and out, accept any offset, and the 64-bit ones throw. binding.MapRegion takes a RegionCaching of Device, WriteCombining or Normal, though today only x64 applies WriteCombining, in the 2 MiB blocks a region covers whole, and every other memory window is mapped as Device. See Register windows and Bulk regions.