fix(renaser): mapeador MMIO en el kernel — la causa real del colapso
1// =============================================================================
2// renaser :: kernel/src/drivers/disco.rs — Fase 6.2 :: el disco asincrono
3// -----------------------------------------------------------------------------
4// La Fase 6.1 hizo hablar al disco; pero lo hacia por SONDEO: el procesador se
5// quedaba en un bucle de espera activa vigilando el «used ring» de virtio,
6// incapaz de atender nada mas. La Fase 6.2 lo libera. La E/S de bloques pasa a
7// ser REACTIVA, guiada por la interrupcion fisica del dispositivo:
8//
9// * `EsperaDisco` — un `Future` nativo: enviada la peticion, cede la CPU; la
10// IRQ del disco lo despertara cuando el bloque este listo.
11// * `atender_irq` — el punto al que salta el manejador de la IRQ del disco:
12// reconoce la interrupcion en el dispositivo y despierta a quien aguardaba.
13// * `bloquear_en` — el puente para los contextos SINCRONOS (el arranque, las
14// capacidades WASM): lleva un `Future` de disco hasta su final durmiendo la
15// CPU con `hlt` —jamas en espera activa una vez el sistema esta en marcha—.
16//
17// Subsisten de la Fase 6.1 el asignador de marcos por mapa de bits (con
18// liberacion real) y `KernelHal`, el puente DMA hacia `virtio-drivers`.
19// =============================================================================
20
21use alloc::boxed::Box;
22use alloc::vec;
23use alloc::vec::Vec;
24use core::future::Future;
25use core::pin::Pin;
26use core::ptr::NonNull;
27use core::sync::atomic::{AtomicU64, AtomicU8, Ordering};
28use core::task::{Context, Poll, Waker};
29
30use spin::{Mutex, Once};
31use virtio_drivers::device::blk::{BlkReq, BlkResp, VirtIOBlk, SECTOR_SIZE};
32use virtio_drivers::transport::pci::bus::{Command, DeviceFunction, PciRoot};
33use virtio_drivers::transport::pci::PciTransport;
34use virtio_drivers::{BufferDirection, Hal, PhysAddr};
35use x86_64::instructions::interrupts;
36
37use super::pci::CamPuertos;
38
39/// Tamaño de una pagina / marco fisico, en bytes.
40const PAGINA: u64 = 4096;
41
42/// Techo de marcos que gestiona el asignador de DMA: 4096 marcos => una arena
43/// de 16 MiB. El DMA del disco —colas virtio y buferes rebote— necesita una
44/// fraccion minima de eso; el techo solo acota el tamaño del mapa de bits.
45const MAX_MARCOS: usize = 4096;
46
47/// Vendor ID de VirtIO; Device IDs de un disco de bloques (transicional/moderno).
48const VENDOR_VIRTIO: u16 = 0x1AF4;
49const VIRTIO_BLK_IDS: [u16; 2] = [0x1001, 0x1042];
50
51/// El tamaño de un sector, reexportado para el resto del kernel.
52pub const TAM_SECTOR: usize = SECTOR_SIZE;
53
54// =============================================================================
55// EL OFFSET DE MEMORIA FISICA Y EL ASIGNADOR DE MARCOS
56// =============================================================================
57
58/// Desplazamiento al que el cargador mapeo toda la memoria fisica: una
59/// direccion fisica `p` es accesible por el kernel en la virtual `p + offset`.
60static OFFSET_FISICO: AtomicU64 = AtomicU64::new(0);
61
62/// Asignador de marcos por MAPA DE BITS: gestiona una arena de RAM fisica
63/// contigua y LIBERA. Cada bit representa un marco de 4 KiB; `1` es ocupado,
64/// `0` libre. Un almacen de objetos vivo asigna y devuelve marcos sin descanso.
65struct AsignadorMarcos {
66 /// Direccion fisica del primer marco gestionado, alineada a pagina.
67 base: u64,
68 /// Numero de marcos que abarca la arena.
69 total: usize,
70 /// Mapa de ocupacion: un bit por marco.
71 mapa: Vec<u64>,
72}
73
74impl AsignadorMarcos {
75 /// ¿Esta libre el marco `i`?
76 fn libre(&self, i: usize) -> bool {
77 (self.mapa[i / 64] >> (i % 64)) & 1 == 0
78 }
79
80 /// Marca el marco `i` como ocupado.
81 fn ocupar(&mut self, i: usize) {
82 self.mapa[i / 64] |= 1 << (i % 64);
83 }
84
85 /// Marca el marco `i` como libre.
86 fn soltar(&mut self, i: usize) {
87 self.mapa[i / 64] &= !(1u64 << (i % 64));
88 }
89
90 /// Reserva `paginas` marcos CONTIGUOS y devuelve la direccion fisica del
91 /// primero. `None` si no hay un hueco contiguo bastante grande.
92 fn asignar(&mut self, paginas: usize) -> Option<u64> {
93 if paginas == 0 || paginas > self.total {
94 return None;
95 }
96 let mut i = 0;
97 while i + paginas <= self.total {
98 // Buscar `paginas` marcos libres consecutivos a partir de `i`.
99 match (i..i + paginas).find(|&k| !self.libre(k)) {
100 // Un marco ocupado rompe la racha: reanudar tras el.
101 Some(ocupado) => i = ocupado + 1,
102 // Racha completa: ocuparla y entregar su direccion fisica.
103 None => {
104 for k in i..i + paginas {
105 self.ocupar(k);
106 }
107 return Some(self.base + (i as u64) * PAGINA);
108 }
109 }
110 }
111 None
112 }
113
114 /// Devuelve a la arena `paginas` marcos que arrancan en la direccion
115 /// fisica `fisica`. Direcciones ajenas a la arena se ignoran sin daño.
116 fn liberar(&mut self, fisica: u64, paginas: usize) {
117 if fisica < self.base {
118 return;
119 }
120 let inicio = ((fisica - self.base) / PAGINA) as usize;
121 for k in inicio..(inicio + paginas).min(self.total) {
122 self.soltar(k);
123 }
124 }
125}
126
127/// El asignador global de marcos para DMA. Se funde en `init`.
128static ASIGNADOR: Once<Mutex<AsignadorMarcos>> = Once::new();
129
130/// Funda el subsistema de disco: registra el offset de memoria fisica y forja
131/// el asignador de marcos sobre la region de RAM libre que el cargador reporto.
132/// Una sola vez, antes de montar el disco.
133pub fn init(offset_fisico: u64, region_inicio: u64, region_fin: u64) {
134 use core::fmt::Write;
135 OFFSET_FISICO.store(offset_fisico, Ordering::Relaxed);
136 // Saltar SIEMPRE la primera pagina fisica: algunos cargadores la dejan sin
137 // mapear como proteccion contra punteros NULL — un marco DMA ahi seria una
138 // bomba en cuanto el driver lo desreferenciase via el mapeo alto.
139 let base = alinear_arriba(region_inicio.max(PAGINA), PAGINA);
140 let disponibles = region_fin.saturating_sub(base) / PAGINA;
141 let total = (disponibles as usize).min(MAX_MARCOS);
142 let _ = writeln!(
143 crate::baliza::Serie,
144 "disco :: init offset={:#x} region=[{:#x}, {:#x}) base={:#x} marcos={}",
145 offset_fisico,
146 region_inicio,
147 region_fin,
148 base,
149 total,
150 );
151 ASIGNADOR.call_once(|| {
152 Mutex::new(AsignadorMarcos {
153 base,
154 total,
155 mapa: vec![0u64; total.div_ceil(64)],
156 })
157 });
158}
159
160/// Redondea `valor` hacia arriba al multiplo de `alineacion` (potencia de dos).
161fn alinear_arriba(valor: u64, alineacion: u64) -> u64 {
162 (valor + alineacion - 1) & !(alineacion - 1)
163}
164
165/// Reserva `paginas` marcos fisicos contiguos de 4 KiB y devuelve su direccion
166/// fisica. Agotar la arena es un fallo del kernel, no recuperable aqui: el
167/// rasgo `Hal` no admite que `dma_alloc` falle.
168fn asignar_marcos(paginas: usize) -> u64 {
169 ASIGNADOR
170 .get()
171 .expect("DMA :: el asignador de marcos no esta fundado")
172 .lock()
173 .asignar(paginas)
174 .expect("DMA :: la arena de marcos fisicos se agoto")
175}
176
177/// Devuelve `paginas` marcos fisicos a la arena. El reverso de `asignar_marcos`.
178fn liberar_marcos(fisica: u64, paginas: usize) {
179 if let Some(asignador) = ASIGNADOR.get() {
180 asignador.lock().liberar(fisica, paginas);
181 }
182}
183
184/// Asigna UN marco para servir de tabla de paginas. Sin pánico: si la arena
185/// esta exhausta, devuelve `None` y deja al mapeador decidir como reaccionar
186/// — el kernel no puede caerse por no poder añadir una tabla intermedia, ya
187/// se delatara en cuanto el dispositivo lea su propio MMIO no mapeado.
188pub fn asignar_marco_para_tabla() -> Option<u64> {
189 ASIGNADOR.get()?.lock().asignar(1)
190}
191
192/// Traduce una direccion fisica a la virtual que el kernel puede desreferenciar.
193fn a_virtual(fisica: u64) -> *mut u8 {
194 (fisica + OFFSET_FISICO.load(Ordering::Relaxed)) as *mut u8
195}
196
197// =============================================================================
198// KernelHal — EL PUENTE ENTRE `virtio-drivers` Y LA MEMORIA DE renaser
199// =============================================================================
200
201/// La implementacion del rasgo `Hal` de `virtio-drivers`. Sin estado propio:
202/// se apoya en el asignador de marcos y el offset fisico, ambos globales.
203pub struct KernelHal;
204
205// SEGURIDAD: cada metodo respeta su contrato — `dma_alloc` entrega marcos
206// fisicos exclusivos, contiguos, alineados a pagina y a cero; `dma_dealloc` y
207// `unshare` los devuelven a la arena; las traducciones de direccion son validas
208// porque el cargador mapeo toda la memoria fisica.
209unsafe impl Hal for KernelHal {
210 fn dma_alloc(paginas: usize, _direccion: BufferDirection) -> (PhysAddr, NonNull<u8>) {
211 let fisica = asignar_marcos(paginas);
212 let virtual_ = a_virtual(fisica);
213 // SEGURIDAD: `asignar_marcos` entrego `paginas` marcos exclusivos y
214 // contiguos; `virtual_` es su imagen valida y escribible en el mapeo
215 // de memoria fisica. El rasgo exige que las paginas vayan a cero.
216 unsafe {
217 core::ptr::write_bytes(virtual_, 0, paginas * PAGINA as usize);
218 }
219 (fisica, NonNull::new(virtual_).expect("DMA :: marco fisico nulo"))
220 }
221
222 unsafe fn dma_dealloc(fisica: PhysAddr, _virtual_: NonNull<u8>, paginas: usize) -> i32 {
223 // Con un asignador real, la liberacion ya NO es un gesto vacio: los
224 // marcos vuelven a la arena y quedan disponibles para el proximo DMA.
225 liberar_marcos(fisica, paginas);
226 0
227 }
228
229 unsafe fn mmio_phys_to_virt(fisica: PhysAddr, tam: usize) -> NonNull<u8> {
230 // OVMF aloja los BAR prefetchables 64-bit de virtio en la «ventana PCI
231 // de 64 bits» —decenas o cientos de GiB de phys—, que el cargador NO
232 // mapea. Antes de devolver el puntero virtual, abrimos en la tabla L4
233 // las paginas que cubren la region pedida; si ya estaban, no pasa nada.
234 crate::memory::mmio::mapear(fisica as u64, tam);
235 NonNull::new(a_virtual(fisica)).expect("MMIO :: direccion fisica nula")
236 }
237
238 unsafe fn share(bufer: NonNull<[u8]>, direccion: BufferDirection) -> PhysAddr {
239 let longitud = bufer.len();
240 let paginas = longitud.div_ceil(PAGINA as usize);
241 let fisica = asignar_marcos(paginas);
242 // Si el bufer viaja HACIA el dispositivo, copiarlo al area DMA rebote.
243 if !matches!(direccion, BufferDirection::DeviceToDriver) {
244 // SEGURIDAD: el rasgo garantiza que `bufer` apunta a `longitud`
245 // bytes validos; el area DMA recien reservada los abarca de sobra.
246 unsafe {
247 core::ptr::copy_nonoverlapping(
248 bufer.as_ptr() as *const u8,
249 a_virtual(fisica),
250 longitud,
251 );
252 }
253 }
254 fisica
255 }
256
257 unsafe fn unshare(fisica: PhysAddr, bufer: NonNull<[u8]>, direccion: BufferDirection) {
258 let longitud = bufer.len();
259 // Si el bufer viene DESDE el dispositivo, copiar el area DMA de vuelta.
260 if !matches!(direccion, BufferDirection::DriverToDevice) {
261 // SEGURIDAD: `fisica` lo entrego `share` para este mismo `bufer`;
262 // ambas regiones abarcan los `longitud` bytes que se copian.
263 unsafe {
264 core::ptr::copy_nonoverlapping(
265 a_virtual(fisica),
266 bufer.as_ptr() as *mut u8,
267 longitud,
268 );
269 }
270 }
271 // Devolver a la arena los marcos del area rebote.
272 liberar_marcos(fisica, longitud.div_ceil(PAGINA as usize));
273 }
274}
275
276// =============================================================================
277// EL DISCO PERSISTENTE
278// =============================================================================
279
280/// El disco virtio-blk, ya montado. Envuelve al `VirtIOBlk` para poder ligarlo
281/// a un `static`.
282struct Disco(VirtIOBlk<KernelHal, PciTransport>);
283
284// SEGURIDAD: `Disco` encierra punteros crudos a las colas virtio y al MMIO del
285// dispositivo. renaser es un kernel de un solo nucleo y todo acceso al disco se
286// serializa tras el `Mutex` global `DISCO`; jamas se comparte entre hilos
287// reales. Todo acceso normal toma el cerrojo con las interrupciones acalladas,
288// de modo que la IRQ del disco jamas lo encuentra ocupado.
289unsafe impl Send for Disco {}
290
291/// El disco global de renaser. Se monta una sola vez, en `montar`.
292static DISCO: Once<Mutex<Disco>> = Once::new();
293
294/// La linea de IRQ del disco, descubierta al montarlo. Vale 0 si el firmware no
295/// enruto una linea util: en ese caso la E/S recae en el sondeo, con la red de
296/// seguridad del temporizador.
297static IRQ_DISCO: AtomicU8 = AtomicU8::new(0);
298
299/// Cuenta de interrupciones del disco atendidas desde el arranque. Es el
300/// testigo vivo de que la E/S asincrona late de verdad.
301static PULSOS_DISCO: AtomicU64 = AtomicU64::new(0);
302
303/// El waker de la (unica) espera de disco en curso. Las operaciones de disco se
304/// serializan, de modo que una sola ranura basta. La IRQ del disco lo invoca.
305static WAKER_DISCO: Mutex<Option<Waker>> = Mutex::new(None);
306
307/// Enumera el bus PCI, localiza el disco virtio-blk, lo monta y lo deja tras el
308/// `Mutex` global. Descubre ademas su linea de IRQ, registra el manejador y
309/// abre la linea en el PIC: desde aqui el disco puede interrumpir. Devuelve la
310/// capacidad del disco en sectores. Toda falla se devuelve como `Err`.
311pub fn montar() -> Result<u64, &'static str> {
312 let mut raiz = PciRoot::new(CamPuertos);
313
314 // 1. Localizar el primer disco virtio-blk recorriendo el bus.
315 let mut hallado: Option<DeviceFunction> = None;
316 'busqueda: for bus in 0..=255u8 {
317 for (device_function, info) in raiz.enumerate_bus(bus) {
318 if info.vendor_id == VENDOR_VIRTIO && VIRTIO_BLK_IDS.contains(&info.device_id) {
319 hallado = Some(device_function);
320 break 'busqueda;
321 }
322 }
323 }
324 let device_function = hallado.ok_or("virtio-blk no hallado en el bus PCI")?;
325
326 // 2. Habilitar E/S, espacio de memoria y BUS-MASTER. Sin bus-master el
327 // dispositivo no puede iniciar transferencias DMA por su cuenta.
328 raiz.set_command(
329 device_function,
330 Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER,
331 );
332
333 // 3. Montar el transporte PCI moderno de virtio y el dispositivo de bloques.
334 let transporte = PciTransport::new::<KernelHal, _>(&mut raiz, device_function)
335 .map_err(|_| "no se pudo montar el transporte PCI de virtio")?;
336 let mut disco = VirtIOBlk::<KernelHal, _>::new(transporte)
337 .map_err(|_| "no se pudo inicializar el dispositivo virtio-blk")?;
338 let capacidad = disco.capacity();
339
340 // 4. Pedir al dispositivo que EMITA una interrupcion al completar cada
341 // peticion — el corazon de la E/S asincrona de esta fase.
342 disco.enable_interrupts();
343 DISCO.call_once(|| Mutex::new(Disco(disco)));
344
345 // 5. Descubrir la linea de IRQ que el firmware asigno al dispositivo y
346 // enrutarla: registrar el manejador en la IDT y abrir la linea en el
347 // PIC. Las IRQ 0 y 1 son del temporizador y el teclado; un valor fuera
348 // de 2..15 (p. ej. 0xFF, «sin conexion») significa que no hay linea
349 // util — la E/S seguira funcionando, pero por sondeo.
350 let irq = super::pci::linea_irq(device_function);
351 if (2..=15).contains(&irq) {
352 crate::interrupts::registrar_irq_disco(irq);
353 crate::pic::desenmascarar(irq);
354 IRQ_DISCO.store(irq, Ordering::SeqCst);
355 }
356
357 Ok(capacidad)
358}
359
360/// La linea de IRQ del disco, si el firmware enruto una util.
361pub fn irq() -> Option<u8> {
362 match IRQ_DISCO.load(Ordering::SeqCst) {
363 0 => None,
364 n => Some(n),
365 }
366}
367
368/// Numero de interrupciones del disco atendidas desde el arranque.
369pub fn pulsos_disco() -> u64 {
370 PULSOS_DISCO.load(Ordering::Relaxed)
371}
372
373/// Punto de entrada DESDE el manejador de la IRQ del disco. Reconoce la
374/// interrupcion en el dispositivo —leer su registro ISR baja la linea INTx— y
375/// despierta a la tarea que aguardaba el bloque. Breve y libre de panicos:
376/// corre en contexto de interrupcion.
377pub fn atender_irq() {
378 // Estamos en contexto de interrupcion (IF=0). Todo acceso normal a `DISCO`
379 // toma su cerrojo con las interrupciones acalladas, de modo que aqui jamas
380 // esta ocupado: tomarlo no puede interbloquear.
381 if let Some(disco) = DISCO.get() {
382 let _ = disco.lock().0.ack_interrupt();
383 }
384 PULSOS_DISCO.fetch_add(1, Ordering::Relaxed);
385 if let Some(waker) = WAKER_DISCO.lock().take() {
386 waker.wake();
387 }
388}
389
390/// Inscribe el waker de la espera de disco en curso. Una sola ranura: las
391/// operaciones de disco estan serializadas. El cerrojo lo disputa el manejador
392/// de IRQ — tomarlo con las interrupciones acalladas hace imposible el bloqueo.
393fn registrar_waker(waker: Waker) {
394 interrupts::without_interrupts(|| *WAKER_DISCO.lock() = Some(waker));
395}
396
397// =============================================================================
398// EsperaDisco — UNA OPERACION DE BLOQUE COMO `Future`
399// =============================================================================
400
401/// Una transferencia de bloques en vuelo, expresada como `Future`. Posee sus
402/// propios buferes DMA —el encabezado de peticion, la respuesta y los datos—,
403/// que `virtio-drivers` mantiene prestados hasta que la operacion concluye.
404///
405/// Su `poll` envia la peticion la primera vez y, despues, comprueba el «used
406/// ring»: si el dispositivo aun no ha terminado, inscribe el waker y cede; la
407/// IRQ del disco lo reanudara. Una operacion a la vez — basta para renaser.
408pub struct EsperaDisco {
409 /// Encabezado de la peticion virtio. En el heap: direccion estable mientras
410 /// el dispositivo lo tiene prestado, sin importar si el `Future` se mueve.
411 req: Box<BlkReq>,
412 /// Respuesta de estado del dispositivo. En el heap, por la misma razon.
413 resp: Box<BlkResp>,
414 /// Los datos: destino de una lectura, origen de una escritura.
415 buf: Vec<u8>,
416 /// Primer sector de la transferencia.
417 sector: u64,
418 /// `true` si la operacion escribe; `false` si lee.
419 es_escritura: bool,
420 /// Token que `virtio-drivers` devolvio al enviar la peticion. `None` hasta
421 /// que el primer `poll` la envia.
422 token: Option<u16>,
423}
424
425impl EsperaDisco {
426 /// Hace avanzar la operacion: la envia si aun no lo estaba y comprueba si el
427 /// dispositivo la completo. `None` => sigue en vuelo; `Some` => terminada.
428 fn avanzar(&mut self) -> Option<Result<Vec<u8>, &'static str>> {
429 // Todo el dialogo con el dispositivo, con las interrupciones acalladas:
430 // asi la IRQ del disco jamas encuentra ocupado el cerrojo de `DISCO`.
431 interrupts::without_interrupts(|| {
432 let disco = match DISCO.get() {
433 Some(disco) => disco,
434 None => return Some(Err("disco no montado")),
435 };
436 let mut guardia = disco.lock();
437 let blk = &mut guardia.0;
438
439 // 1. Enviar la peticion la primera vez que se sondea esta espera.
440 if self.token.is_none() {
441 // SEGURIDAD: `req`, `buf` y `resp` viven en esta `EsperaDisco`,
442 // que no se libera ni se accede por otra via hasta que el
443 // `complete_*` de mas abajo cierre la operacion.
444 let envio = unsafe {
445 if self.es_escritura {
446 blk.write_blocks_nb(
447 self.sector as usize,
448 &mut self.req,
449 self.buf.as_slice(),
450 &mut self.resp,
451 )
452 } else {
453 blk.read_blocks_nb(
454 self.sector as usize,
455 &mut self.req,
456 self.buf.as_mut_slice(),
457 &mut self.resp,
458 )
459 }
460 };
461 match envio {
462 Ok(token) => self.token = Some(token),
463 Err(_) => return Some(Err("no se pudo enviar la peticion al disco")),
464 }
465 }
466 let token = self.token.unwrap();
467
468 // 2. ¿Ha colocado el dispositivo este token en el «used ring»?
469 if blk.peek_used() != Some(token) {
470 return None; // aun en vuelo
471 }
472
473 // 3. Completado: recoger el resultado y desligar los buferes DMA.
474 // SEGURIDAD: se pasan los MISMOS buferes que recibio el `*_nb`.
475 let fin = unsafe {
476 if self.es_escritura {
477 blk.complete_write_blocks(token, &self.req, self.buf.as_slice(), &mut self.resp)
478 } else {
479 blk.complete_read_blocks(
480 token,
481 &self.req,
482 self.buf.as_mut_slice(),
483 &mut self.resp,
484 )
485 }
486 };
487 Some(match fin {
488 Ok(()) => Ok(core::mem::take(&mut self.buf)),
489 Err(_) => Err("la operacion de disco no se completo"),
490 })
491 })
492 }
493}
494
495impl Future for EsperaDisco {
496 /// Al terminar, una lectura entrega sus datos; una escritura, un vector
497 /// vacio. El error es siempre un mensaje estable.
498 type Output = Result<Vec<u8>, &'static str>;
499
500 fn poll(self: Pin<&mut Self>, contexto: &mut Context<'_>) -> Poll<Self::Output> {
501 // `EsperaDisco` es `Unpin` —solo `Box`, `Vec` y escalares—: el `Pin`
502 // no impone nada y el acceso mutable es directo.
503 let this = self.get_mut();
504 // Inscribir el waker ANTES de comprobar: si la IRQ se cuela entre la
505 // comprobacion y la inscripcion, el waker ya esta puesto y el despertar
506 // no se pierde — el mismo blindaje que usa `EsperaFrame` (ver `reloj`).
507 registrar_waker(contexto.waker().clone());
508 match this.avanzar() {
509 Some(resultado) => Poll::Ready(resultado),
510 None => Poll::Pending,
511 }
512 }
513}
514
515/// Prepara la LECTURA asincrona de `n_sectores` sectores desde `sector`. El
516/// `Future` que devuelve no toca el disco hasta que se le sondea.
517pub fn leer_bloques(sector: u64, n_sectores: usize) -> EsperaDisco {
518 EsperaDisco {
519 req: Box::new(BlkReq::default()),
520 resp: Box::new(BlkResp::default()),
521 buf: vec![0u8; n_sectores * TAM_SECTOR],
522 sector,
523 es_escritura: false,
524 token: None,
525 }
526}
527
528/// Prepara la ESCRITURA asincrona de `datos` a partir de `sector`. La longitud
529/// de `datos` debe ser multiplo de un sector.
530pub fn escribir_bloques(sector: u64, datos: Vec<u8>) -> EsperaDisco {
531 EsperaDisco {
532 req: Box::new(BlkReq::default()),
533 resp: Box::new(BlkResp::default()),
534 buf: datos,
535 sector,
536 es_escritura: true,
537 token: None,
538 }
539}
540
541// =============================================================================
542// bloquear_en — EL PUENTE PARA LOS CONTEXTOS SINCRONOS
543// =============================================================================
544
545/// Lleva un `Future` de disco hasta su final desde un contexto SINCRONO — el
546/// arranque, o una capacidad WASM, que no pueden `.await`-ear—. Mientras el
547/// disco trabaja:
548///
549/// * si las interrupciones estan activas, duerme la CPU con `hlt`; la
550/// despertara la IRQ del disco —o el temporizador, como red de seguridad—;
551/// * si no —en el arranque, antes de habilitarlas—, sondea en bucle.
552///
553/// Asi, una vez el sistema esta en marcha, la espera de disco JAMAS malgasta
554/// ciclos en espera activa.
555fn bloquear_en<F: Future>(futuro: F) -> F::Output {
556 let mut futuro = core::pin::pin!(futuro);
557 let waker = Waker::noop();
558 let mut contexto = Context::from_waker(waker);
559 loop {
560 match futuro.as_mut().poll(&mut contexto) {
561 Poll::Ready(salida) => return salida,
562 Poll::Pending => {
563 if interrupts::are_enabled() {
564 x86_64::instructions::hlt();
565 } else {
566 core::hint::spin_loop();
567 }
568 }
569 }
570 }
571}
572
573/// Lee `buf.len() / 512` sectores consecutivos a partir de `sector`. Sincrono:
574/// construido sobre la maquinaria asincrona via `bloquear_en`. El bufer debe
575/// medir un multiplo entero de un sector.
576pub fn leer_sectores(sector: u64, buf: &mut [u8]) -> Result<(), &'static str> {
577 let datos = bloquear_en(leer_bloques(sector, buf.len() / TAM_SECTOR))?;
578 buf.copy_from_slice(&datos);
579 Ok(())
580}
581
582/// Escribe `buf.len() / 512` sectores consecutivos a partir de `sector`.
583/// Sincrono, sobre la misma maquinaria asincrona.
584pub fn escribir_sectores(sector: u64, buf: &[u8]) -> Result<(), &'static str> {
585 bloquear_en(escribir_bloques(sector, buf.to_vec())).map(|_| ())
586}