2026-05-08
Not all memory is equal. The same DRAM bytes can behave like cacheable system memory, uncached MMIO registers, or write-combining framebuffer space — and the CPU needs to know which is which before it issues the access. This is the job of MTRRs (Memory Type Range Registers) and the PAT (Page Attribute Table) on x86.
The hardware supports six memory types, each with different caching and ordering rules:
MTRRs are coarse — typically 8–16 fixed and variable range registers covering physical address regions (e.g., 0xA0000–0xBFFFF for legacy VGA = UC). They're set up by the BIOS at boot and rarely touched after.
PAT is the modern fix. It's a per-page attribute selected by 3 bits in the page table entry (PWT, PCD, PAT bits) indexing into an 8-entry MSR. This lets the OS pick a memory type per 4KB page, overriding the MTRR for that region. Linux uses PAT to mark GPU framebuffer pages as WC even though the underlying region is WB in MTRR.
Concrete example: When a Linux driver calls ioremap_wc() on a PCIe BAR, the kernel sets PAT bits in the page table to select WC. A memcpy() to that address now coalesces 64-byte cache-line bursts on the PCIe bus instead of issuing 16 separate 4-byte transactions. Throughput jumps from ~200 MB/s to ~3 GB/s on a typical x16 link.
Rule of thumb: The effective memory type is the most restrictive of MTRR and PAT. If MTRR says UC and PAT says WB, you get UC. This prevents PAT misconfiguration from accidentally caching device registers — a safety net the CPU enforces in hardware.
Conflicts between MTRR and PAT can silently kill performance. Tools like x86info -r or /proc/mtrr + /sys/kernel/debug/x86/pat_memtype_list reveal what the hardware actually decided.
