feat(renaser): Fase 13 — ratón, puntero y arrastre de flotantes

renaser dialogaba sólo con el teclado; las ventanas flotantes nacían
en cascada y allí se quedaban. La Fase 13 trae el ratón.

- Driver `drivers/raton`: el ratón PS/2 cuelga del dispositivo
  auxiliar del 8042 + IRQ12. El driver despierta el aux, programa
  su IRQ, le ordena reportar, ensambla paquetes de 3 bytes con
  guarda del bit-3. Posición como atómicos, eventos como cola
  lock-free — el mismo guardarraíl que el teclado.
- El puntero, capa de PRESENTACIÓN: `Pantalla::estampar_puntero`
  pinta un sprite de flecha 12×18 sobre el framebuffer después de
  copiar el lienzo. El lienzo nunca lo contiene — hace de
  save-under natural—.
- Compositor: `atender_raton` drena eventos. Botón bajando es un
  clic-para-enfocar consistente con `mover_foco` (silencia bocina,
  alza si flota). Si la enfocada flota, arranca un arrastre con el
  desfase de agarre; el botón sostenido la sigue al puntero; al
  soltar, termina.
- `refrescar_puntero` reestampa el framebuffer si el puntero se
  movió en una vuelta tranquila en que ninguna app pintó.

Verificado en QEMU (mouse_move / mouse_button del monitor): el
puntero aparece al arrancar, se mueve por la pantalla, un clic
sobre pulso le da el foco, y un arrastre con el botón sostenido
mueve la flotante de la cascada al centro-abajo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-22 23:21:06 +00:00
parent d1b700eb2b
commit 8fc1d99ddf
11 changed files with 644 additions and 4 deletions
+3
View File
@@ -11,8 +11,11 @@
// lectura, por sondeo, de su primer sector.
// * `altavoz` — la bocina del PC: el canal 2 del PIT como generador de tono
// (Fase 12).
// * `raton` — el raton PS/2: el dispositivo auxiliar del 8042 + IRQ12,
// paquetes de 3 bytes (Fase 13).
// =============================================================================
pub mod altavoz;
pub mod disco;
pub mod pci;
pub mod raton;
+261
View File
@@ -0,0 +1,261 @@
// =============================================================================
// renaser :: kernel/src/drivers/raton.rs — Fase 13 :: el raton PS/2
// -----------------------------------------------------------------------------
// El raton cuelga del controlador 8042 —el mismo que sirve el teclado—, por su
// puerto AUXILIAR, y anuncia cada movimiento por la IRQ12. Su lenguaje es un
// paquete de tres bytes: banderas (botones + signos), delta X y delta Y.
//
// Como el teclado en la Fase 8c, el raton respeta el GUARDARRAIL de
// interrupciones: el manejador de IRQ12 solo toca atomicos —la posicion del
// puntero— y una cola lock-free de eventos. Jamas un cerrojo cooperativo. El
// compositor drena esa cola desde el reactor, a su ritmo.
// =============================================================================
use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering};
use crossbeam_queue::ArrayQueue;
use spin::Once;
use x86_64::instructions::port::Port;
use crate::pic;
/// Puerto de datos del controlador 8042 (compartido con el teclado).
const DATOS: u16 = 0x60;
/// Puerto de estado / comando del 8042.
const ESTADO: u16 = 0x64;
/// Capacidad de la cola de eventos del raton — holgada: nadie agita tanto.
const CAPACIDAD: usize = 128;
/// Un evento del raton, tal como lo entrega un paquete completo: la posicion
/// ya absoluta del puntero y el estado crudo de los botones (bit 0 izquierdo,
/// bit 1 derecho, bit 2 central). Lo produce la IRQ12; lo consume el compositor.
#[derive(Clone, Copy)]
pub struct EventoRaton {
pub x: u16,
pub y: u16,
pub botones: u8,
}
/// Posicion del puntero, en pixeles. La escribe la IRQ12 a cada paquete; la lee
/// la consola para estampar el puntero, y el compositor para situar los clics.
static RATON_X: AtomicUsize = AtomicUsize::new(0);
static RATON_Y: AtomicUsize = AtomicUsize::new(0);
/// Limites de la pantalla — el puntero jamas se sale de ellos.
static ANCHO: AtomicUsize = AtomicUsize::new(0);
static ALTO: AtomicUsize = AtomicUsize::new(0);
/// ¿Esta el raton inicializado y vivo? Hasta entonces no hay puntero que pintar.
static ACTIVO: AtomicBool = AtomicBool::new(false);
/// Estado del ensamblado del paquete de 3 bytes. Lo toca SOLO la IRQ12, que es
/// no-reentrante en un solo nucleo: una secuencia de atomicos basta, sin cerrojo.
static FASE: AtomicUsize = AtomicUsize::new(0);
static BYTE0: AtomicU8 = AtomicU8::new(0);
static BYTE1: AtomicU8 = AtomicU8::new(0);
/// Estado de los botones en el paquete anterior — para detectar transiciones.
static BOTONES_ANTES: AtomicU8 = AtomicU8::new(0);
/// La cola de eventos: la IRQ12 deposita (lock-free, segura en interrupcion),
/// el compositor drena desde el reactor cooperativo.
static EVENTOS: Once<ArrayQueue<EventoRaton>> = Once::new();
// =============================================================================
// Dialogo con el 8042 — esperas acotadas, sin colgar jamas el arranque
// =============================================================================
/// Espera, con un tope de intentos, a que el 8042 admita una escritura (su
/// bufer de entrada vacio). Devuelve `false` si se agota la paciencia.
fn esperar_envio() -> bool {
for _ in 0..100_000 {
// SEGURIDAD: 0x64 es el puerto de estado del 8042, fijo en el PC.
let estado = unsafe { Port::<u8>::new(ESTADO).read() };
if estado & 0b10 == 0 {
return true;
}
}
false
}
/// Espera, con un tope de intentos, a que el 8042 tenga un byte que entregar.
fn esperar_recepcion() -> bool {
for _ in 0..100_000 {
// SEGURIDAD: ver `esperar_envio`.
let estado = unsafe { Port::<u8>::new(ESTADO).read() };
if estado & 0b01 != 0 {
return true;
}
}
false
}
/// Escribe un byte en el puerto de comando del 8042.
fn comando_8042(byte: u8) {
esperar_envio();
// SEGURIDAD: 0x64 es el puerto de comando del 8042 en la arquitectura PC.
unsafe { Port::<u8>::new(ESTADO).write(byte) };
}
/// Escribe un byte en el puerto de datos del 8042.
fn escribir_datos(byte: u8) {
esperar_envio();
// SEGURIDAD: 0x60 es el puerto de datos del 8042 en la arquitectura PC.
unsafe { Port::<u8>::new(DATOS).write(byte) };
}
/// Lee un byte del puerto de datos del 8042.
fn leer_datos() -> u8 {
esperar_recepcion();
// SEGURIDAD: ver `escribir_datos`.
unsafe { Port::<u8>::new(DATOS).read() }
}
/// Envia un comando AL raton (no al 8042): el prefijo 0xD4 le dice al 8042 que
/// el proximo byte de datos va al dispositivo auxiliar. Consume el ACK (0xFA).
fn comando_raton(byte: u8) {
comando_8042(0xD4);
escribir_datos(byte);
let _ack = leer_datos();
}
/// Vacia el bufer de salida del 8042 — descarta bytes rezagados que pudieran
/// desincronizar el ensamblado del primer paquete.
fn vaciar() {
for _ in 0..16 {
// SEGURIDAD: ver `esperar_envio`.
let estado = unsafe { Port::<u8>::new(ESTADO).read() };
if estado & 0b01 == 0 {
return;
}
let _ = unsafe { Port::<u8>::new(DATOS).read() };
}
}
// =============================================================================
// Arranque
// =============================================================================
/// Funda el raton: despierta el dispositivo auxiliar del 8042, programa su IRQ,
/// le ordena reportar movimiento y deja el puntero en el centro de la pantalla.
/// Requiere el heap activo; debe invocarse una vez, antes de habilitar las
/// interrupciones.
pub fn init(ancho: usize, alto: usize) {
ANCHO.store(ancho, Ordering::Relaxed);
ALTO.store(alto, Ordering::Relaxed);
RATON_X.store(ancho / 2, Ordering::Relaxed);
RATON_Y.store(alto / 2, Ordering::Relaxed);
EVENTOS.call_once(|| ArrayQueue::new(CAPACIDAD));
vaciar();
// Despertar el dispositivo auxiliar (el raton) en el 8042.
comando_8042(0xA8);
// Leer el byte de configuracion, encender la IRQ del auxiliar (bit 1) y
// asegurar que su reloj corre (bit 5 a cero), y reescribirlo.
comando_8042(0x20);
let mut config = leer_datos();
config |= 0b0000_0010;
config &= !0b0010_0000;
comando_8042(0x60);
escribir_datos(config);
// Al raton: valores por defecto y, despues, reportar movimiento.
comando_raton(0xF6);
comando_raton(0xF4);
vaciar();
ACTIVO.store(true, Ordering::Relaxed);
// Levantar la mascara de la IRQ12 — el raton vive en el PIC esclavo.
pic::desenmascarar(12);
}
// =============================================================================
// El paquete de 3 bytes — punto de entrada desde la IRQ12
// =============================================================================
/// Punto de entrada DESDE el manejador de IRQ12. Ensambla el paquete de tres
/// bytes y, al completarlo, actualiza la posicion del puntero y encola un
/// evento. Deliberadamente breve y libre de panicos: corre en contexto de IRQ.
pub fn recibir_byte(byte: u8) {
match FASE.load(Ordering::Relaxed) {
0 => {
// El primer byte SIEMPRE trae el bit 3 a 1. Si no, el flujo esta
// desincronizado: se descarta el byte y se sigue esperando uno bueno.
if byte & 0b0000_1000 == 0 {
return;
}
BYTE0.store(byte, Ordering::Relaxed);
FASE.store(1, Ordering::Relaxed);
}
1 => {
BYTE1.store(byte, Ordering::Relaxed);
FASE.store(2, Ordering::Relaxed);
}
_ => {
FASE.store(0, Ordering::Relaxed);
procesar(BYTE0.load(Ordering::Relaxed), BYTE1.load(Ordering::Relaxed), byte);
}
}
}
/// Procesa un paquete completo: traduce los deltas a una posicion absoluta del
/// puntero, acotada a la pantalla, y encola el evento si hay algo que el
/// compositor deba atender —un boton pulsado o un arrastre en curso—.
fn procesar(banderas: u8, dx_crudo: u8, dy_crudo: u8) {
// Un paquete con desbordamiento de delta trae un salto disparatado: se
// ignora su movimiento por completo.
if banderas & 0b1100_0000 != 0 {
return;
}
let dx = dx_crudo as i8 as i32;
// El raton PS/2 da Y positivo hacia ARRIBA; la pantalla, hacia ABAJO.
let dy = dy_crudo as i8 as i32;
let ancho = ANCHO.load(Ordering::Relaxed) as i32;
let alto = ALTO.load(Ordering::Relaxed) as i32;
let x = (RATON_X.load(Ordering::Relaxed) as i32 + dx).clamp(0, (ancho - 1).max(0)) as usize;
let y = (RATON_Y.load(Ordering::Relaxed) as i32 - dy).clamp(0, (alto - 1).max(0)) as usize;
RATON_X.store(x, Ordering::Relaxed);
RATON_Y.store(y, Ordering::Relaxed);
// Encolar un evento SOLO si importa: si los botones cambiaron, o si alguno
// sigue pulsado —un arrastre—. El movimiento ocioso no satura la cola; el
// puntero, aun asi, ya se movio (los atomicos de arriba).
let botones = banderas & 0b0000_0111;
let antes = BOTONES_ANTES.swap(botones, Ordering::Relaxed);
if botones != antes || botones != 0 {
if let Some(cola) = EVENTOS.get() {
// Si la cola se desborda, el evento se pierde en silencio: mas vale
// perder un gesto que arriesgar un panico dentro de una IRQ.
let _ = cola.push(EventoRaton {
x: x as u16,
y: y as u16,
botones,
});
}
}
}
// =============================================================================
// Consulta — para la consola (puntero) y el compositor (eventos)
// =============================================================================
/// La posicion actual del puntero, o `None` si el raton aun no esta vivo. La
/// consulta la consola para estampar el puntero en cada volcado de pantalla.
pub fn posicion() -> Option<(usize, usize)> {
if !ACTIVO.load(Ordering::Relaxed) {
return None;
}
Some((
RATON_X.load(Ordering::Relaxed),
RATON_Y.load(Ordering::Relaxed),
))
}
/// Extrae el siguiente evento del raton, o `None` si no hay ninguno pendiente.
/// La drena el compositor, evento a evento, desde el reactor cooperativo.
pub fn siguiente_evento() -> Option<EventoRaton> {
EVENTOS.get().and_then(ArrayQueue::pop)
}