Map device registers with ioremap instead of assuming a low BAR

Every driver reached its registers with P2V(bar.base), which assumes the
BAR sits inside the 0-4 GB the direct map covers. Under SeaBIOS it always
does. Under OVMF it does not: firmware may place a 64-bit BAR anywhere,
and it puts virtio-gpu's at about 768 GB. P2V produced an address with no
translation behind it, so the driver's first register write took a page
fault, then a double fault, then a triple fault and a reboot loop --
which, coming from a driver bind, reads as broken hardware rather than a
missing mapping.

ioremap() carries each region on a 1 GB page in PDPT entries 4-511, which
were unused: the direct map occupies 0-3, and kpml4[0], kpml4[KERNBASE]
and every process's PML4 all share that one table, so a mapping appears
in every address space at once with no page table to allocate. The pages
are cache-disabled and write-through 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 returns the direct-map address unchanged,
so the BIOS path, where every BAR is low, is byte for byte what it was.

virtio-gpu now initialises under OVMF (1280x800, queue 64) instead of
triple faulting. e1000 and xhci had the identical latent bug; xhci was
already refusing to bind above 4 GB with a comment saying the mapping did
not exist yet, and now it does.

BIOS unchanged: bootcheck 108 [ok] with -Fb -Tty -Usb -Sock -Evdev -Ps,
e1000 and xhci both binding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-09 21:30:35 +02:00
commit b768f5ded9
5 changed files with 106 additions and 12 deletions

View file

@ -111,7 +111,23 @@ find_cfg(struct pci_dev *d, int want, uint32 *out_mult)
if (type == want && bar < 6 && d->bar[bar].base) {
if (out_mult) // only the notify capability has this trailing field
*out_mult = pci_read32(d->bus, d->slot, d->func, off + 16);
return (volatile uint8 *)P2V(d->bar[bar].base + o);
// ioremap, not P2V. OVMF puts this device's 64-bit BAR at about 768 GB,
// far outside the 4 GB the direct map covers, and P2V there produces an
// address with nothing behind it -- the first register write took a
// page fault, then a double fault, then a triple fault and a reboot
// loop. SeaBIOS places the BAR low, which is why the BIOS path never
// showed it.
//
// Map at least as far as the capability's own offset reaches: the BAR
// size is what the device reports, and a mapping shorter than the
// offset we are about to add would fault just as mysteriously.
uint64 need = (uint64)o + PGSIZE;
uint64 len = d->bar[bar].size > need ? d->bar[bar].size : need;
volatile uint8 *va = (volatile uint8 *)ioremap(d->bar[bar].base, len);
if (!va)
return 0;
return va + o;
}
}
off = next;