tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Sutichaykuna

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>

8fcc4dc06780530645c220691b4df569a645eba9 · ruwaq sergio · (5 killa ñawpaq) · tayta c715ee2de · kay commitpi sach'ata qhaway

Semántico sayk'uy · 11

renaser/boot/src/main.rs

renaser/kernel/src/drivers/disco.rs

renaser/kernel/src/main.rs

renaser/kernel/src/memory/mmio.rs

renaser/kernel/src/memory/mod.rs

  • musuq mod mmio pub

5 qillqakuna · +158 −10

diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs
index 28b9a16..a185fa8 100644
--- a/renaser/boot/src/main.rs
+++ b/renaser/boot/src/main.rs
@@ -323,13 +323,6 @@ fn lanzar_qemu(imagen: &Path, ovmf: &str) -> Result<(), String> {
         .arg("-vga").arg("std")
         .arg("-serial").arg("stdio")
         .arg("--no-reboot")
-        // El cargador `bootloader` 0.11 mapea la memoria fisica que ve, pero NO
-        // las regiones MMIO en el agujero PCI de 64 bits (por encima de 4 GiB),
-        // donde OVMF coloca los BAR de virtio-blk en QEMU q35 modernos con
-        // KVM. Sin mapeo, leer un registro MMIO del disco era un #PF
-        // inevitable. Apagar `pci-hole64-size` fuerza a OVMF a alojar todos
-        // los BAR en los primeros 4 GiB, que el cargador si mapea.
-        .arg("-global").arg("q35-pcihost.pci-hole64-size=0")
         // El disco de objetos, como dispositivo virtio-blk sobre el bus PCI.
         .arg("-drive").arg(format!("format=raw,file={NOMBRE_DISCO},if=none,id=drv0"))
         .arg("-device").arg("virtio-blk-pci,drive=drv0");
diff --git a/renaser/kernel/src/drivers/disco.rs b/renaser/kernel/src/drivers/disco.rs
index 112f0f3..0592b68 100644
--- a/renaser/kernel/src/drivers/disco.rs
+++ b/renaser/kernel/src/drivers/disco.rs
@@ -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")
     }
 
diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs
index bda07c4..5d53b35 100644
--- a/renaser/kernel/src/main.rs
+++ b/renaser/kernel/src/main.rs
@@ -439,6 +439,15 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
     memory::init();
     traza("heap fundado");
 
+    // --- 4.5. Mapeador de MMIO: envuelve la tabla L4 activa para abrir paginas
+    //          nuevas hacia los BAR MMIO de virtio (que pueden caer fuera de
+    //          lo que el cargador mapeo). Necesita `physical_memory_offset`
+    //          para alcanzar la L4 via el mapeo de memoria fisica. ---
+    if let Some(offset) = offset_fisico {
+        memory::mmio::init(offset);
+        traza("mmio :: mapeador fundado");
+    }
+
     // --- 5. Con el heap activo, fundar lo que depende de el: el canal de
     //        scancodes, el reloj de fotogramas y la tipografia vectorial. ---
     async_system::teclado::init();
diff --git a/renaser/kernel/src/memory/mmio.rs b/renaser/kernel/src/memory/mmio.rs
new file mode 100644
index 0000000..31be720
--- /dev/null
+++ b/renaser/kernel/src/memory/mmio.rs
@@ -0,0 +1,134 @@
+// =============================================================================
+//  renaser :: kernel/src/memory/mmio.rs — el mapeador de regiones MMIO
+// -----------------------------------------------------------------------------
+//  El cargador `bootloader_api` mapea la memoria fisica de la maquina, pero la
+//  «ventana PCI de 64 bits» —donde OVMF aloja los BAR prefetchables de virtio
+//  en QEMU q35 modernos— suele caer FUERA de ese mapeo: phys 32 GiB, 768 GiB o
+//  mas, segun la fase de la luna. Sin mapeo, leer el primer registro del disco
+//  era un #PF inmediato; con el, el kernel puede hablar con el dispositivo.
+//
+//  Este modulo abre paginas en la tabla L4 que el cargador nos cedio. Reutiliza
+//  como asignador de marcos el de DMA del disco (`drivers::disco`): los marcos
+//  para las tablas intermedias salen del mismo banco que los buferes virtio.
+//  No mapea de mas: solo lo que se le pide, por paginas.
+// =============================================================================
+
+use core::sync::atomic::{AtomicU64, Ordering};
+
+use spin::{Mutex, Once};
+
+use x86_64::registers::control::Cr3;
+use x86_64::structures::paging::mapper::{MapToError, Mapper};
+use x86_64::structures::paging::{
+    FrameAllocator, OffsetPageTable, Page, PageTable, PageTableFlags, PhysFrame, Size4KiB,
+};
+use x86_64::{PhysAddr, VirtAddr};
+
+/// Desplazamiento al que el cargador mapeo la memoria fisica. Lo necesitamos
+/// tanto para alcanzar la tabla L4 actual como para traducir la direccion
+/// fisica del BAR a su virtual de destino.
+static OFFSET_FISICO: AtomicU64 = AtomicU64::new(0);
+
+/// El mapeador del kernel: una vista mutable de la tabla L4 actual envuelta en
+/// el truco de `OffsetPageTable`, que lo asume todo accesible via el mapeo de
+/// memoria fisica que el cargador ya dejo activo. Se funda en el arranque, una
+/// sola vez, antes de cualquier llamada a `mapear`.
+static MAPEADOR: Once<Mutex<OffsetPageTable<'static>>> = Once::new();
+
+/// Funda el mapeador: localiza la tabla L4 activa (via CR3) y la envuelve en un
+/// `OffsetPageTable` que aprovecha el mapeo de memoria fisica del cargador. A
+/// partir de aqui `mapear` puede abrir paginas nuevas en la tabla.
+pub fn init(offset_fisico: u64) {
+    OFFSET_FISICO.store(offset_fisico, Ordering::Relaxed);
+    let (l4_frame, _) = Cr3::read();
+    let l4_virt = l4_frame.start_address().as_u64() + offset_fisico;
+    // SEGURIDAD: el cargador mapeo toda la RAM fisica (incluida la tabla L4
+    // activa) en `l4_phys + offset_fisico`. Esa direccion es valida, esta
+    // alineada a pagina y la tabla vive lo que vive el kernel. La tomamos como
+    // referencia mutable porque renaser es de un solo nucleo y todo acceso al
+    // mapeador queda serializado tras el `Mutex` de `MAPEADOR`.
+    let l4: &'static mut PageTable = unsafe { &mut *(l4_virt as *mut PageTable) };
+    let mapeador = unsafe { OffsetPageTable::new(l4, VirtAddr::new(offset_fisico)) };
+    MAPEADOR.call_once(|| Mutex::new(mapeador));
+}
+
+/// Asigna marcos para las tablas de paginas intermedias. Toma del banco DMA
+/// del disco —no hay heap aqui— y deja cada marco a CEROS, como una tabla
+/// vacia exige. Si el banco esta exhausto, devuelve `None` y el mapeo falla
+/// en lugar de incendiar el kernel.
+struct Marcos;
+
+unsafe impl FrameAllocator<Size4KiB> for Marcos {
+    fn allocate_frame(&mut self) -> Option<PhysFrame<Size4KiB>> {
+        let fisica = crate::drivers::disco::asignar_marco_para_tabla()?;
+        let offset = OFFSET_FISICO.load(Ordering::Relaxed);
+        // SEGURIDAD: `asignar_marco_para_tabla` entrego un marco exclusivo y
+        // mapeado a `fisica + offset` por el cargador. Las tablas de paginas
+        // exigen empezar a cero — si no, la CPU las leeria como llenas de
+        // basura— asi que lo limpiamos antes de cederlo.
+        unsafe {
+            core::ptr::write_bytes((fisica + offset) as *mut u8, 0, 4096);
+        }
+        Some(PhysFrame::containing_address(PhysAddr::new(fisica)))
+    }
+}
+
+/// Abre en la tabla L4 las paginas que cubren la region MMIO [fisica, fisica +
+/// tam), de modo que cada pagina sea accesible en `fisica + offset_fisico` con
+/// las banderas habituales del MMIO (escribible, sin cache). Si la pagina ya
+/// estaba mapeada, se respeta la entrada existente sin gritar — un BAR puede
+/// solaparse con regiones que el cargador ya cubrio—. Si el mapeo falla en
+/// medio, registramos el problema en COM1 y seguimos: que la app que llame al
+/// MMIO se entere por su propio fallo.
+pub fn mapear(fisica: u64, tam: usize) {
+    use core::fmt::Write;
+
+    let Some(mapeador) = MAPEADOR.get() else {
+        return;
+    };
+    if tam == 0 {
+        return;
+    }
+    let offset = OFFSET_FISICO.load(Ordering::Relaxed);
+    let virt_inicio = fisica + offset;
+    let virt_fin = virt_inicio + tam as u64 - 1;
+
+    let pagina_inicio = Page::<Size4KiB>::containing_address(VirtAddr::new(virt_inicio));
+    let pagina_fin = Page::<Size4KiB>::containing_address(VirtAddr::new(virt_fin));
+    let frame_inicio = PhysFrame::<Size4KiB>::containing_address(PhysAddr::new(fisica));
+
+    let banderas = PageTableFlags::PRESENT
+        | PageTableFlags::WRITABLE
+        | PageTableFlags::NO_CACHE
+        | PageTableFlags::WRITE_THROUGH;
+
+    let mut mapeador = mapeador.lock();
+    let mut frame = frame_inicio;
+    let mut pagina = pagina_inicio;
+    while pagina <= pagina_fin {
+        // SEGURIDAD: cada par (pagina, frame) describe un mapeo MMIO honesto:
+        // la pagina virtual `fisica + offset` apunta a la pagina fisica
+        // `fisica` que el dispositivo posee. El `Marcos` cede marcos limpios
+        // para las tablas intermedias.
+        let resultado = unsafe { mapeador.map_to(pagina, frame, banderas, &mut Marcos) };
+        match resultado {
+            Ok(flush) => flush.flush(),
+            // El cargador ya habia mapeado la region como una pagina 4 KiB
+            // (PageAlreadyMapped) o como una pagina huge — 2 MiB / 1 GiB —
+            // (ParentEntryHugePage). En ambos casos el acceso ya funciona; no
+            // hay nada que añadir y SEGUIMOS sin detenernos. Solo abortamos si
+            // se nos agotan los marcos para una tabla intermedia nueva.
+            Err(MapToError::PageAlreadyMapped(_)) | Err(MapToError::ParentEntryHugePage) => {}
+            Err(MapToError::FrameAllocationFailed) => {
+                let _ = writeln!(
+                    crate::baliza::Serie,
+                    "mmio :: sin marcos para la tabla — mapeo incompleto en {:#x}",
+                    pagina.start_address().as_u64(),
+                );
+                return;
+            }
+        }
+        pagina += 1;
+        frame += 1;
+    }
+}
diff --git a/renaser/kernel/src/memory/mod.rs b/renaser/kernel/src/memory/mod.rs
index c858b3f..eafcc63 100644
--- a/renaser/kernel/src/memory/mod.rs
+++ b/renaser/kernel/src/memory/mod.rs
@@ -7,5 +7,6 @@
 // =============================================================================
 
 pub mod allocator;
+pub mod mmio;
 
 pub use allocator::init;