feat(renaser): Fase 12 — la bocina del PC como capacidad de host
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 <noreply@anthropic.com>
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/<app> # hello_wasm, discola, glotona, cronista, memoriosa, pulso
+cd apps/<app> # hello_wasm, discola, glotona, cronista, memoriosa, pulso, tonada
cargo build --target wasm32-unknown-unknown --release
cp target/wasm32-unknown-unknown/release/<app>.wasm ../../kernel/assets/<app>.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 0000000..d87d0f9
Binary files /dev/null and b/renaser/kernel/assets/tonada.wasm differ
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::<u8>::new(PIT_COMANDO);
+ let mut canal2 = Port::<u8>::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::<u8>::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::<u8>::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(())
}