--- name: schunix-ioremap-and-bars description: "ioremap() maps device registers; never use P2V on a BAR, because firmware may place a 64-bit BAR far above the 4 GB direct map" metadata: node_type: memory type: project originSessionId: d4821aa9-2e49-40da-91e7-90ef9ed9997e modified: 2026-08-09T20:03:56.063Z --- **Never reach a device's registers with `P2V(bar.base)`.** Use `ioremap(pa, size)` from `kernel/arch/x86_64/vm.c` (declared in `include/defs.h`). Every driver used to do the `P2V` thing, which silently assumes the BAR lands inside the 0-4 GB that `kvmalloc()` direct-maps with four 1 GB pages. Under SeaBIOS it always does. Under OVMF it does not — virtio-gpu's 64-bit BAR came back at about **768 GB**, `P2V` produced an address with no translation behind it, and the first register write page faulted, then double faulted, then triple faulted into a reboot loop. Coming out of a driver bind, that reads as broken hardware rather than a missing mapping. ## How it works PDPT entries **4-511 were unused**: the direct map occupies 0-3, and `kpml4[0]`, `kpml4[KERNBASE]` and every process's PML4 (see `setupkvm`) all point at that same table. So each MMIO region takes one 1 GB page there — no page table to allocate, and the mapping appears in every address space at once. Entries are `PTE_PCD | PTE_PWT` for the same reason the top GB of the direct map is: these are registers, and a write sitting in a cache line is a write the device never sees. **A physical address below 4 GB is returned unchanged, as `P2V` would have.** That is what makes the BIOS path — where every BAR is low — provably identical rather than merely tested. ## Where this bites next Converted: virtio-gpu, e1000, xhci. xHCI had already been *refusing* to bind above 4 GB with a comment saying the mapping did not exist yet. Any new driver that reads a BAR needs the same treatment. `grep -rn 'P2V(d->bar' kernel/` should stay empty. See [[schos-uefi-loader]] for the boot path this unblocked.