Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
44 lines
2 KiB
Markdown
44 lines
2 KiB
Markdown
---
|
|
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/core/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.
|