fix(renaser): mapeador MMIO en el kernel — la causa real del colapso

El `-global pci-hole64-size=0` del commit anterior NO movía los BAR:
verifiqué con `info pci` que OVMF seguía alojando el BAR4 prefetchable
64-bit del virtio-blk en `0xc000000000` (mi Proxmox) o `0x800000000`
(la laptop del usuario). El cargador `bootloader_api` 0.11 mapea la
memoria física pero no extiende su mapeo hasta la ventana PCI de 64
bits, y `KernelHal::mmio_phys_to_virt` devolvía `phys + offset` a
ciegas — un puntero a memoria sin tabla de páginas, al primer registro
MMIO leído → #PF.

La solución: un mapeador MMIO propio del kernel.

- `memory::mmio`: envuelve la tabla L4 activa (vía CR3 + el mapeo de
  memoria física del cargador) en un `OffsetPageTable`. Su función
  `mapear(fisica, tam)` abre, para cada página de la región, una
  entrada en la L4 con `PRESENT | WRITABLE | NO_CACHE | WRITE_THROUGH`
  — las banderas habituales del MMIO.
- Los marcos para tablas intermedias salen del banco DMA del disco
  (`drivers::disco::asignar_marco_para_tabla`, sin pánico). Se ponen
  a cero antes de cederlos: las tablas empiezan vacías.
- Tratamos `PageAlreadyMapped` y `ParentEntryHugePage` como éxito: la
  región ya estaba cubierta por el cargador (con páginas 4 KiB o
  hugepages 2 MiB / 1 GiB) y el acceso ya funciona. Solo abortamos el
  mapeo si se nos agota la arena DMA.
- `KernelHal::mmio_phys_to_virt` llama a `memory::mmio::mapear` antes
  de devolver el puntero virtual. virtio-drivers lo invoca con la
  base y el tamaño exactos de cada BAR; el kernel asegura que cada
  uno sea accesible antes de devolverlo.
- `kernel_main` funda el mapeador justo después del heap (paso 4.5),
  antes del disco. Necesita `physical_memory_offset` para alcanzar
  la L4 activa.

Quito el `-global q35-pcihost.pci-hole64-size=0` que añadí antes: no
movía los BAR (verificado con `info pci`) y solo confundía la
descripción del fix. Esta solución es la robusta: el kernel sabe
mapear sus propios MMIOs y deja de depender del firmware.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-23 01:28:32 +00:00
parent c715ee2dee
commit 8fcc4dc067
5 changed files with 158 additions and 10 deletions
+14 -3
View File
@@ -181,6 +181,14 @@ fn liberar_marcos(fisica: u64, paginas: usize) {
}
}
/// Asigna UN marco para servir de tabla de paginas. Sin pánico: si la arena
/// esta exhausta, devuelve `None` y deja al mapeador decidir como reaccionar
/// — el kernel no puede caerse por no poder añadir una tabla intermedia, ya
/// se delatara en cuanto el dispositivo lea su propio MMIO no mapeado.
pub fn asignar_marco_para_tabla() -> Option<u64> {
ASIGNADOR.get()?.lock().asignar(1)
}
/// Traduce una direccion fisica a la virtual que el kernel puede desreferenciar.
fn a_virtual(fisica: u64) -> *mut u8 {
(fisica + OFFSET_FISICO.load(Ordering::Relaxed)) as *mut u8
@@ -218,9 +226,12 @@ unsafe impl Hal for KernelHal {
0
}
unsafe fn mmio_phys_to_virt(fisica: PhysAddr, _tam: usize) -> NonNull<u8> {
// El cargador mapeo, como minimo, los primeros 4 GiB de memoria fisica;
// todo BAR MMIO de PCI cae dentro y es accesible en `fisica + offset`.
unsafe fn mmio_phys_to_virt(fisica: PhysAddr, tam: usize) -> NonNull<u8> {
// OVMF aloja los BAR prefetchables 64-bit de virtio en la «ventana PCI
// de 64 bits» —decenas o cientos de GiB de phys—, que el cargador NO
// mapea. Antes de devolver el puntero virtual, abrimos en la tabla L4
// las paginas que cubren la region pedida; si ya estaban, no pasa nada.
crate::memory::mmio::mapear(fisica as u64, tam);
NonNull::new(a_virtual(fisica)).expect("MMIO :: direccion fisica nula")
}