From 922ad1f86bbef5d4f8c5f59827e5fd6a9b17c1af Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 22 May 2026 20:54:48 +0000 Subject: [PATCH] =?UTF-8?q?feat(renaser):=20Fase=2012=20=E2=80=94=20la=20b?= =?UTF-8?q?ocina=20del=20PC=20como=20capacidad=20de=20host?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La Fase 11 dio al userspace un reloj; la Fase 12, una voz. Hasta hoy renaser solo sabía dibujar para llamar la atención. - Driver `drivers/altavoz`: el canal 2 del PIT como generador de onda cuadrada + la compuerta del puerto 0x61. El canal 0 —latido del kernel— no se toca. `tono(hz)` es su única vía; un 0 la silencia. - Capacidad `sys_tono(frecuencia_hz)` — la undécima función del host. La bocina es un recurso único: pertenece a la ventana ENFOCADA, como el teclado desde la Fase 8c. Al cambiar el foco, el compositor la calla; la nueva dueña la reclama en su próximo fotograma. - App nueva `tonada` (`apps/tonada/`, wasm32): toca una escala de Do mayor y la dibuja como una escalera de barras. Junta el reloj (`sys_tiempo_mono`) y la bocina (`sys_tono`). - `GENESIS` crece de 6 a 7 apps; `tonada` es la maestra del escritorio. Verificado en QEMU. Visual: la escalera de `tonada` recorre la escala con el tiempo. Sonido: con la bocina enrutada a un WAV, el PCM capturado es una onda cuadrada oscilante de ~375 Hz — la frecuencia media de la escala de Do mayor. Co-Authored-By: Claude Opus 4.7 --- renaser/CHANGELOG.md | 33 ++++++ renaser/CLAUDE.md | 8 +- renaser/DIARIO.md | 22 ++++ renaser/ROADMAP.md | 20 +++- renaser/apps/tonada/Cargo.lock | 7 ++ renaser/apps/tonada/Cargo.toml | 29 +++++ renaser/apps/tonada/src/lib.rs | 147 ++++++++++++++++++++++++++ renaser/boot/src/main.rs | 13 +-- renaser/kernel/assets/tonada.wasm | Bin 0 -> 1069 bytes renaser/kernel/src/compositor.rs | 5 + renaser/kernel/src/drivers/altavoz.rs | 73 +++++++++++++ renaser/kernel/src/drivers/mod.rs | 9 +- renaser/kernel/src/wasm/env.rs | 20 +++- 13 files changed, 371 insertions(+), 15 deletions(-) create mode 100644 renaser/apps/tonada/Cargo.lock create mode 100644 renaser/apps/tonada/Cargo.toml create mode 100644 renaser/apps/tonada/src/lib.rs create mode 100755 renaser/kernel/assets/tonada.wasm create mode 100644 renaser/kernel/src/drivers/altavoz.rs diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index d477953..6f603c1 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -873,3 +873,36 @@ un sentido del tiempo: el reloj monótono del sistema, como capacidad. capturas. Un `Alt+N` da a luz un segundo `pulso` ~15 s después del arranque; flotado junto al primero, su barra está en la MISMA fase — la prueba de que el compás se rige por el reloj absoluto, no por una cuenta de fotogramas. + +## Fase 12 — La bocina del PC como capacidad de host — 2026-05-22 + +La Fase 11 le dio al userspace un reloj; la Fase 12, una voz. La bocina del PC +—el canal 2 del PIT como generador de onda cuadrada— se suma a la matriz de +capacidades. Hasta hoy renaser sólo sabía DIBUJAR para llamar la atención. + +### Añadido +- **Driver `drivers/altavoz`.** Programa el canal 2 del PIT a una frecuencia y + abre la compuerta del puerto 0x61. El canal 0 del PIT es el latido del + kernel; el canal 2 es de la bocina y de nadie más — no se perturban—. + `tono(hz)` es su única vía; un `0` la silencia. +- **Capacidad `sys_tono(frecuencia_hz)`** — la undécima función del host. La + bocina es un recurso ÚNICO y global: para que dos apps no se la disputen, + pertenece —como el teclado desde la Fase 8c— a la ventana ENFOCADA. Una app + sin foco puede pedir un tono; sencillamente, no se oye. Al cambiar el foco, + el compositor calla la bocina (`mover_foco`, `cerrar`): la nueva dueña la + reclama en su próximo fotograma. +- **App `tonada`** (`apps/tonada/`, `wasm32`). Toca una escala de Do mayor en + bucle y la dibuja como una escalera de ocho barras, con la nota que suena + encendida. Junta las dos capacidades nuevas del kernel: el reloj + (`sys_tiempo_mono`) le marca el compás, la bocina (`sys_tono`) le da voz. +- `tonada` encabeza el userspace de génesis: `GENESIS` pasa de 6 a 7 apps, con + `tonada` como la ventana maestra —enfocada al arrancar, así suena de + inmediato—. + +### Verificado +- QEMU. Visual: la escalera de `tonada` enciende la nota en curso y ésta + recorre la escala con el tiempo (capturas con la 6.ª y la 1.ª nota vivas). + Sonido: con la bocina enrutada a un WAV (`-audiodev wav` + `-machine + pcspk-audiodev`), el PCM capturado —50 s— es una onda cuadrada oscilante de + ±24 000 de amplitud, con una frecuencia media de ~375 Hz: la de la escala de + Do mayor. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index 4a7ddae..1031367 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -28,7 +28,7 @@ Reconstruir una app WASM del userspace tras tocarla. Los `.wasm` viven en modulo `hello_wasm` se copia como `app.wasm`, el resto conserva su nombre: ```sh -cd apps/ # hello_wasm, discola, glotona, cronista, memoriosa, pulso +cd apps/ # hello_wasm, discola, glotona, cronista, memoriosa, pulso, tonada cargo build --target wasm32-unknown-unknown --release cp target/wasm32-unknown-unknown/release/.wasm ../../kernel/assets/.wasm # (hello_wasm es la excepcion: su destino es kernel/assets/app.wasm) @@ -78,9 +78,11 @@ objetos—, la Fase 8 COMPLETA —el compositor teselante e interactivo: teselad con `mirada-layout` (8a), ciclado de layout (8b), foco y enrutamiento selectivo del teclado (8c), promoción y reordenación de ventanas (8d)—, la Fase 9 COMPLETA —orden-Z y ventanas flotantes: composición con solapamiento (`Alt+F`)— -la Fase 10 COMPLETA —alta y baja de aplicaciones en vivo (`Alt+N` / `Alt+Q`)— y +la Fase 10 COMPLETA —alta y baja de aplicaciones en vivo (`Alt+N` / `Alt+Q`)—, la Fase 11 COMPLETA —el reloj del sistema como capacidad de host -(`sys_tiempo_mono`) + la app `pulso`—. Todo verificado en QEMU. Ver `ROADMAP.md`. +(`sys_tiempo_mono`) + la app `pulso`— y la Fase 12 COMPLETA —la bocina del PC +como capacidad de host (`sys_tono`) + la app `tonada`—. Todo verificado en +QEMU. Ver `ROADMAP.md`. ## Flujo de trabajo diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index 3853276..c20af8f 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -454,6 +454,28 @@ incorpora justo donde va el primero, al instante, como dos bailarines que oyen la misma música—. Porque ninguno lleva su propia cuenta: ambos miran el mismo reloj del recibidor. +## La voz — la casa aprende a sonar + +La casa sabía mostrarse, recordar, medir el tiempo. Pero era, hasta hoy, una +casa muda. Lo único que sabía hacer para llamar la atención era pintar —una +franja roja, un borde de color—. Nunca un sonido. + +Hoy estrenó su voz. No una voz rica, de orquesta: la más pobre que existe, un +único hilo de sonido, el viejo altavoz que todo PC lleva escondido. Pero basta. +Con él la casa puede ya emitir un tono, agudo o grave, o callar. + +Y, como con el reloj, hubo una regla de cortesía. La voz es una sola, y si +todos los inquilinos hablaran a la vez no se entendería nada. Así que la voz, +igual que el oído —el teclado—, pertenece al inquilino del cuarto que se está +mirando. Los demás pueden mover los labios; sólo se oye al que tiene la +atención de la casa. Al cambiar la mirada de cuarto, la casa calla un instante, +y el nuevo elegido toma la palabra. + +Para estrenarla llegó «tonada», que no sabe hablar pero sí cantar: toca una +escala, una y otra vez, y la dibuja como una escalera de luces que sube al +compás de la música. Míralo cantar en silencio desde cualquier rincón; +acércate —dale el foco— y lo oirás. + --- *El diario continúa. La próxima página la escribirá la próxima jornada.* diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md index 9db872f..6c86b82 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -195,8 +195,24 @@ tiempo. Verificada en QEMU (`sendkey`). de que el tiempo es absoluto, no una cuenta de fotogramas. - El userspace de génesis crece de 5 a 6 apps. -Líneas abiertas posteriores: audio como capacidad de host; reciclado de las -ranuras de ventana cerradas. +## Fase 12 — la bocina del PC como capacidad de host (completada) + +La Fase 11 le dio al userspace un reloj; la Fase 12, una voz. La bocina del PC +se suma a la matriz de capacidades. Verificada en QEMU. + +- Driver `drivers/altavoz`: el canal 2 del PIT como generador de onda cuadrada + + la compuerta del puerto 0x61. El canal 0 (latido del kernel) no se toca. +- Capacidad `sys_tono(frecuencia_hz)` —la undécima—. La bocina es un recurso + único: pertenece a la ventana ENFOCADA, como el teclado desde la Fase 8c. Al + cambiar el foco, el compositor la calla. +- App nueva `tonada`: toca una escala de Do mayor y la dibuja como una + escalera de barras. Junta el reloj (`sys_tiempo_mono`) y la bocina + (`sys_tono`). El userspace de génesis crece de 6 a 7 apps. +- Verificado por captura: la onda cuadrada de la bocina, enrutada a un WAV, + late a la frecuencia media de la escala. + +Líneas abiertas posteriores: reciclado de las ranuras de ventana cerradas; +audio con varias voces (PCM) más allá del tono único de la bocina. ## Principios que persisten entre fases diff --git a/renaser/apps/tonada/Cargo.lock b/renaser/apps/tonada/Cargo.lock new file mode 100644 index 0000000..0f20950 --- /dev/null +++ b/renaser/apps/tonada/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "tonada" +version = "0.1.0" diff --git a/renaser/apps/tonada/Cargo.toml b/renaser/apps/tonada/Cargo.toml new file mode 100644 index 0000000..5382cd8 --- /dev/null +++ b/renaser/apps/tonada/Cargo.toml @@ -0,0 +1,29 @@ +# ============================================================================= +# renaser :: apps/tonada — Fase 12 :: una melodia visual para estrenar la bocina +# ----------------------------------------------------------------------------- +# Un modulo WebAssembly puro que toca una escala —y la dibuja— gobernandose por +# dos capacidades del host: el reloj monotono (`sys_tiempo_mono`, Fase 11) que +# le marca el compas, y la bocina (`sys_tono`, Fase 12) por la que suena. Tiene +# su propio `[workspace]`: queda fuera del espacio de trabajo del kernel. +# ============================================================================= + +[package] +name = "tonada" +version = "0.1.0" +edition = "2021" +description = "renaser :: app WASM — una melodia visual: reloj del host + bocina" + +[workspace] + +# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que +# wasmi instancia. La aplicacion solo habla con el kernel por funciones del host. +[lib] +crate-type = ["cdylib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true diff --git a/renaser/apps/tonada/src/lib.rs b/renaser/apps/tonada/src/lib.rs new file mode 100644 index 0000000..b16438a --- /dev/null +++ b/renaser/apps/tonada/src/lib.rs @@ -0,0 +1,147 @@ +// ============================================================================= +// renaser :: apps/tonada — Fase 12 :: una melodia visual para estrenar la voz +// ----------------------------------------------------------------------------- +// Con la Fase 11 el userspace gano un reloj; con la Fase 12, una voz: la +// bocina del PC, capacidad `sys_tono`. `tonada` las junta. Toca una escala de +// Do mayor, una y otra vez, y la dibuja como una escalera de barras — la nota +// que suena, encendida. +// +// Su escena y su sonido son una FUNCION PURA del reloj del host: no guarda +// estado entre fotogramas. Y la bocina pertenece a la ventana ENFOCADA —como +// el teclado—: `tonada` pide su tono en cada fotograma, pero solo se oye +// cuando tiene el foco. Mira la melodia siempre; escuchala cuando la enfocas. +// ============================================================================= + +#![no_std] + +// --- Las capacidades que el kernel `renaser` inyecta a esta aplicacion. --- +#[link(wasm_import_module = "renaser")] +extern "C" { + /// Compone un bufer de pixeles (de ESTA memoria lineal) en la region que el + /// kernel asigno a esta aplicacion. + fn sys_render_frame(ptr: u32, len: u32); + /// El reloj monotono del sistema: milisegundos desde el arranque. + fn sys_tiempo_mono() -> u64; + /// Hace sonar la bocina del PC a una frecuencia (un 0 la silencia). Solo se + /// oye si esta ventana tiene el foco — lo decide el host. + fn sys_tono(frecuencia_hz: u32); +} + +/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco. +#[panic_handler] +fn al_fallar(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +// --- Geometria de la escena. El ancho y el alto DEBEN coincidir con la region +// que el kernel asigna a esta app. --- +const ANCHO: usize = 360; +const ALTO: usize = 120; + +/// La melodia: una escala de Do mayor ascendente. Frecuencias en Hz —de Do4 a +/// Do5—. El indice de la nota es, ademas, el indice de su barra en pantalla. +const MELODIA: [u32; 8] = [262, 294, 330, 349, 392, 440, 494, 523]; + +/// Duracion de cada nota, en milisegundos. +const NOTA_MS: u64 = 420; + +/// Azul nocturno: el fondo del lienzo. +const FONDO: u32 = 0x0A_18_30; +/// Una nota en reposo — una barra de la escalera, apagada. +const BARRA: u32 = 0x24_30_4E; +/// La nota que suena AHORA — el indigo brillante del foco del compositor. +const BARRA_VIVA: u32 = 0x8B_5C_F6; + +/// Margen lateral, en pixeles. +const MARGEN: usize = 22; +/// La linea base sobre la que se alzan las barras. +const BASE_Y: usize = 100; +/// Altura de la barra mas grave, y cuanto crece cada peldaño de la escala. +const ALTO_MIN: usize = 13; +const ALTO_PASO: usize = 11; + +/// El lienzo de la aplicacion, en SU propia memoria lineal. El kernel jamas lo +/// ve directamente: solo recibe el (ptr, len) que cada fotograma le entrega. +static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO]; + +/// Preparacion: el kernel la invoca UNA sola vez, al cargar el modulo. +#[no_mangle] +pub extern "C" fn init() { + pintar(); +} + +/// Un fotograma de trabajo: pide el tono de la nota actual, redibuja la +/// escalera y RETORNA, cediendo la CPU al kernel y a las apps vecinas. +#[no_mangle] +pub extern "C" fn tick() { + pintar(); +} + +/// Pinta —y suena— la melodia en el instante ACTUAL. No guarda estado alguno: +/// la nota viva es una funcion pura del reloj del host. +fn pintar() { + // SEGURIDAD: durante `init` y `tick` esta es la unica via de acceso a + // LIENZO, y el kernel jamas reentra el modulo mientras una de ellas corre. + let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) }; + + // La nota actual: se deriva SOLO del reloj del host. + // SEGURIDAD: `sys_tiempo_mono` es una capacidad del host; no toca memoria. + let tiempo = unsafe { sys_tiempo_mono() }; + let ciclo = NOTA_MS * MELODIA.len() as u64; + let actual = ((tiempo % ciclo) / NOTA_MS) as usize; + + // Pedir el tono de la nota actual. El host solo lo hara sonar si esta + // ventana tiene el foco — la app pide sin saberlo. + // SEGURIDAD: `sys_tono` es una capacidad del host; no toca memoria. + unsafe { + sys_tono(MELODIA[actual]); + } + + // Fondo limpio. + for pixel in lienzo.iter_mut() { + *pixel = FONDO; + } + + // Una barra por nota: la escalera de la melodia. La que suena, encendida. + let util = ANCHO - 2 * MARGEN; + let ranura = util / MELODIA.len(); + let barra_ancho = ranura * 3 / 4; + let mut i = 0; + while i < MELODIA.len() { + let altura = ALTO_MIN + i * ALTO_PASO; + let x0 = MARGEN + i * ranura + (ranura - barra_ancho) / 2; + let color = if i == actual { BARRA_VIVA } else { BARRA }; + columna(lienzo, x0, barra_ancho, altura, color); + i += 1; + } + + volcar(lienzo); +} + +/// Pinta una barra vertical: `ancho` pixeles desde la columna `x0`, `altura` +/// pixeles alzandose sobre la linea base `BASE_Y`. Se recorta al lienzo. +fn columna(lienzo: &mut [u32], x0: usize, ancho: usize, altura: usize, color: u32) { + let x1 = (x0 + ancho).min(ANCHO); + let y1 = BASE_Y.min(ALTO); + let y0 = y1.saturating_sub(altura); + let mut fila = y0; + while fila < y1 { + let base = fila * ANCHO; + let mut col = x0; + while col < x1 { + lienzo[base + col] = color; + col += 1; + } + fila += 1; + } +} + +/// Entrega el lienzo completo al kernel. El (ptr, len) apunta SIEMPRE dentro de +/// nuestra memoria lineal, y su tamaño es, exactamente, el de la region. +fn volcar(lienzo: &[u32]) { + // SEGURIDAD: `sys_render_frame` es una capacidad del host; el (ptr, len) + // describe nuestra propia memoria lineal y el host lo verifica sin piedad. + unsafe { + sys_render_frame(lienzo.as_ptr() as u32, (ANCHO * ALTO * 4) as u32); + } +} diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs index 6a87aef..a185fa8 100644 --- a/renaser/boot/src/main.rs +++ b/renaser/boot/src/main.rs @@ -107,12 +107,13 @@ struct AppGenesis { region: (u32, u32, u32, u32), } -/// El userspace de genesis — las seis aplicaciones que pueblan un disco recien -/// forjado. El compas visual `pulso` (Fase 11), un saludo (`hola`), la -/// `memoriosa` interactiva que recuerda entre sesiones (Fase 7c), y tres demos -/// de los guardarrailes del kernel: `discola` (combustible), `glotona` -/// (memoria) y `cronista` (la cronica de los arranques). -const GENESIS: [AppGenesis; 6] = [ +/// El userspace de genesis — las siete aplicaciones que pueblan un disco recien +/// forjado. La melodia visual `tonada` (Fase 12), el compas visual `pulso` +/// (Fase 11), un saludo (`hola`), la `memoriosa` interactiva que recuerda entre +/// sesiones (Fase 7c), y tres demos de los guardarrailes del kernel: `discola` +/// (combustible), `glotona` (memoria) y `cronista` (la cronica de los arranques). +const GENESIS: [AppGenesis; 7] = [ + AppGenesis { nombre: "tonada", archivo: "tonada.wasm", region: (100, 120, 360, 120) }, AppGenesis { nombre: "pulso", archivo: "pulso.wasm", region: (100, 120, 360, 120) }, AppGenesis { nombre: "hola", archivo: "app.wasm", region: (100, 120, 480, 560) }, AppGenesis { nombre: "memoriosa", archivo: "memoriosa.wasm", region: (700, 120, 360, 80) }, diff --git a/renaser/kernel/assets/tonada.wasm b/renaser/kernel/assets/tonada.wasm new file mode 100755 index 0000000000000000000000000000000000000000..d87d0f9deeb1212de5ce52459564f3389c328182 GIT binary patch literal 1069 zcmZQbEY4+QU|?VrWJzFPtV>|5XGmbGuV+YLU|{lOW-m(3ODs+;;xDc&jxWhf%`M1} z&&|)vXJCNIaDZh%LX0pW0gzBpYF3io z9a*xJ7(Xpb6wVer^RWfff*pft!;-pbf&}U=U~qvAEe8 zm>qZ+xVgDC85tl!s=&s`z@Wy+z~IHmz!1yGz|g_Sz_5dnf#Dq^0|Pe`0|UbmCYC%< z-0*OhB-i zV5Rwa`Aph4)fk$A6d@-SW(Ayz%?dK}N)n4$%t0#rN}P+cl8v%L!u+b-&2r*RlJkpF zEejI!GLy42^U@71z|8oh{L;LX;`ro@)Z}bdaMEJaB|+!DA5*6PRz;C&CE;5 zEJ{r-(akSQEy~HyOV{SgEzK#(EKAHOP1WYlEiFk*%1PBt&&f|p%qi9u&dbj$DM~CT p$jnREO)Dq?c~G0bC^ap$C^auRRkx(FAhlSVqc}4?PdBxq1OPpRIQswq literal 0 HcmV?d00001 diff --git a/renaser/kernel/src/compositor.rs b/renaser/kernel/src/compositor.rs index 5b3d401..03e542a 100644 --- a/renaser/kernel/src/compositor.rs +++ b/renaser/kernel/src/compositor.rs @@ -410,6 +410,9 @@ fn mover_foco(adelante: bool) { } } FOCO.store(nuevo, Ordering::Relaxed); + // La bocina pertenece a la ventana enfocada (Fase 12): al cambiar el foco, + // callar — la nueva dueña la reclamara en su proximo fotograma si quiere. + crate::drivers::altavoz::tono(0); // La ventana recien enfocada, si flota, al frente del orden-Z. alzar_si_flota(&mut escritorio, nuevo); recomponer(&escritorio); @@ -599,6 +602,8 @@ fn cerrar() { }) .unwrap_or(foco); FOCO.store(nuevo, Ordering::Relaxed); + // El foco cambia: callar la bocina (Fase 12) — ver `mover_foco`. + crate::drivers::altavoz::tono(0); alzar_si_flota(&mut escritorio, nuevo); aplicar_teselado(&mut escritorio); recomponer(&escritorio); diff --git a/renaser/kernel/src/drivers/altavoz.rs b/renaser/kernel/src/drivers/altavoz.rs new file mode 100644 index 0000000..a97151f --- /dev/null +++ b/renaser/kernel/src/drivers/altavoz.rs @@ -0,0 +1,73 @@ +// ============================================================================= +// renaser :: kernel/src/drivers/altavoz.rs — Fase 12 :: la bocina del PC +// ----------------------------------------------------------------------------- +// La bocina del PC es el instrumento mas humilde del hardware: un solo bit que, +// conmutado a la frecuencia justa, hace vibrar una membrana. No hay PCM, ni +// DMA, ni mezclador — solo el canal 2 del PIT generando una onda cuadrada y +// una compuerta en el puerto 0x61 que la deja pasar al altavoz, o no. +// +// El canal 0 del PIT es el latido del kernel (ver `pic`); el canal 2 es de la +// bocina y de nadie mas — programarlo aqui no perturba el temporizador—. Esta +// es la unica via del kernel hacia el sonido; la capacidad `sys_tono` la +// ofrece al userspace, gobernada por el foco del compositor. +// ============================================================================= + +use x86_64::instructions::port::Port; + +/// Frecuencia del cristal del PIT, en Hz — el divisor se calcula contra ella. +const PIT_BASE_HZ: u32 = 1_193_182; + +/// Puerto de comando del PIT. +const PIT_COMANDO: u16 = 0x43; +/// Puerto de datos del canal 2 del PIT — el de la bocina. +const PIT_CANAL2: u16 = 0x42; +/// Puerto de control de la bocina (bits 0 y 1: compuerta del PIT y dato). +const CONTROL_BOCINA: u16 = 0x61; + +/// Pone la bocina a sonar a `frecuencia_hz`. Un `0` —o una frecuencia que un +/// divisor de 16 bits no pueda representar (por debajo de ~19 Hz)— la SILENCIA. +/// Es la unica via del kernel hacia el sonido. +pub fn tono(frecuencia_hz: u32) { + if frecuencia_hz == 0 || PIT_BASE_HZ / frecuencia_hz > 0xFFFF { + silenciar(); + return; + } + // El divisor cabe en 16 bits; un `.max(1)` lo protege de una frecuencia + // disparatadamente alta que lo dejara en cero. + let divisor = (PIT_BASE_HZ / frecuencia_hz).max(1) as u16; + + // SEGURIDAD: 0x43 y 0x42 son los puertos del PIT en la arquitectura PC; + // 0xB6 selecciona el canal 2, acceso lobyte+hibyte, modo 3 (onda cuadrada). + // El canal 2 es exclusivo de la bocina: no perturba el latido del kernel. + unsafe { + let mut comando = Port::::new(PIT_COMANDO); + let mut canal2 = Port::::new(PIT_CANAL2); + comando.write(0xB6u8); + canal2.write((divisor & 0xFF) as u8); + canal2.write((divisor >> 8) as u8); + } + abrir_compuerta(); +} + +/// Abre la compuerta del puerto 0x61: deja pasar la onda del canal 2 al altavoz. +fn abrir_compuerta() { + // SEGURIDAD: 0x61 es el puerto de control de la bocina; sus bits 0 y 1 + // —compuerta del PIT y dato del altavoz— se tocan con leer-modificar- + // escribir para no perturbar los demas bits del chipset. + unsafe { + let mut control = Port::::new(CONTROL_BOCINA); + let estado = control.read(); + control.write(estado | 0b11); + } +} + +/// Silencia la bocina: cierra la compuerta del puerto 0x61. La onda del canal 2 +/// sigue generandose, pero ya no alcanza la membrana. +fn silenciar() { + // SEGURIDAD: ver `abrir_compuerta`. Limpiar los bits 0 y 1 corta el sonido. + unsafe { + let mut control = Port::::new(CONTROL_BOCINA); + let estado = control.read(); + control.write(estado & !0b11); + } +} diff --git a/renaser/kernel/src/drivers/mod.rs b/renaser/kernel/src/drivers/mod.rs index 91318fd..80a0135 100644 --- a/renaser/kernel/src/drivers/mod.rs +++ b/renaser/kernel/src/drivers/mod.rs @@ -6,10 +6,13 @@ // abren la primera via hacia hardware que el kernel debe DESCUBRIR y reclamar // por si mismo: // -// * `pci` — acceso al espacio de configuracion del bus PCI (0xCF8/0xCFC). -// * `disco` — el disco virtio-blk: asignador de marcos DMA, `Hal` y la -// lectura, por sondeo, de su primer sector. +// * `pci` — acceso al espacio de configuracion del bus PCI (0xCF8/0xCFC). +// * `disco` — el disco virtio-blk: asignador de marcos DMA, `Hal` y la +// lectura, por sondeo, de su primer sector. +// * `altavoz` — la bocina del PC: el canal 2 del PIT como generador de tono +// (Fase 12). // ============================================================================= +pub mod altavoz; pub mod disco; pub mod pci; diff --git a/renaser/kernel/src/wasm/env.rs b/renaser/kernel/src/wasm/env.rs index 44c9557..0d26930 100644 --- a/renaser/kernel/src/wasm/env.rs +++ b/renaser/kernel/src/wasm/env.rs @@ -14,7 +14,8 @@ // * sys_object_fijar_raiz — coronar un objeto como raiz; // * sys_estado_cargar — leer el estado persistido de la app (Fase 7c); // * sys_estado_guardar — anclar el estado persistido de la app (Fase 7c); -// * sys_tiempo_mono — leer el reloj monotono del sistema (Fase 11). +// * sys_tiempo_mono — leer el reloj monotono del sistema (Fase 11); +// * sys_tono — hacer sonar la bocina del PC (Fase 12). // // GUARDARRAIL: el kernel valida MATEMATICAMENTE todo puntero que el modulo le // entrega contra los limites reales de su memoria lineal. No se confia en que @@ -457,5 +458,22 @@ pub(crate) fn enlazar_capacidades( }, )?; + // --- CAPACIDAD 11 :: sys_tono(frecuencia_hz) --- + // Hace sonar la bocina del PC a `frecuencia_hz` (un 0 la silencia). La + // bocina es un recurso UNICO y global: para que dos apps no se la disputen, + // pertenece —como el teclado desde la Fase 8c— a la ventana ENFOCADA. Una + // app sin foco puede pedir un tono; sencillamente, no se oye. Y cuando el + // foco cambia, el compositor calla la bocina: la nueva dueña la reclamara + // en su proximo fotograma si quiere sonar. + enlazador.func_wrap( + "renaser", + "sys_tono", + |caller: Caller<'_, ContextoCapacidades>, frecuencia_hz: u32| { + if crate::compositor::foco() == caller.data().indice_app { + crate::drivers::altavoz::tono(frecuencia_hz); + } + }, + )?; + Ok(()) }