diff options
Diffstat (limited to '')
| -rw-r--r-- | rsnix/src/ps2.rs | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/rsnix/src/ps2.rs b/rsnix/src/ps2.rs index 35848b6..5f4a804 100644 --- a/rsnix/src/ps2.rs +++ b/rsnix/src/ps2.rs | |||
| @@ -16,6 +16,7 @@ const MOD_RALT: u8 = 0x20; | |||
| 16 | const MOD_CAPS: u8 = 0x40; | 16 | const MOD_CAPS: u8 = 0x40; |
| 17 | 17 | ||
| 18 | /// A decoded key event. | 18 | /// A decoded key event. |
| 19 | #[derive(Clone, Copy, Debug)] | ||
| 19 | pub enum KeyEvent { | 20 | pub enum KeyEvent { |
| 20 | Char(char), | 21 | Char(char), |
| 21 | Enter, | 22 | Enter, |
| @@ -400,3 +401,185 @@ fn ctrl_held() -> bool { | |||
| 400 | let state = kbd(); | 401 | let state = kbd(); |
| 401 | state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0 | 402 | state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0 |
| 402 | } | 403 | } |
| 404 | |||
| 405 | // --------------------------------------------------------------------------- | ||
| 406 | // PS/2 mouse — auxiliary channel, IRQ12, stream mode (3-byte packets) | ||
| 407 | // --------------------------------------------------------------------------- | ||
| 408 | |||
| 409 | // PS/2 controller commands (written to port 0x64). | ||
| 410 | const CMD_AUX_ENABLE: u8 = 0xA8; // enable the auxiliary (second) port | ||
| 411 | const CMD_READ_CFG: u8 = 0x20; // read Configuration Byte → port 0x60 | ||
| 412 | const CMD_WRITE_CFG: u8 = 0x60; // write Configuration Byte ← port 0x60 | ||
| 413 | const CMD_SEND_TO_AUX: u8 = 0xD4; // next byte on 0x60 goes to the mouse | ||
| 414 | |||
| 415 | // Configuration Byte bits (as sent via CMD_READ_CFG / CMD_WRITE_CFG). | ||
| 416 | // Bit 0 (IRQ1/keyboard) is set by `init`; this driver preserves it by | ||
| 417 | // reading the byte and only ORing in the two mouse bits below. | ||
| 418 | const CFG_AUX_IRQ: u8 = 0x02; // bit 1 — IRQ12 (mouse) | ||
| 419 | const CFG_AUX_CLOCK: u8 = 0x20; // bit 5 — auxiliary port clock enable | ||
| 420 | |||
| 421 | // Mouse device commands (sent via CMD_SEND_TO_AUX). | ||
| 422 | const AUX_SET_DEFAULTS: u8 = 0xF6; // 100 Hz sample, 4 counts/mm, 1:1 | ||
| 423 | const AUX_ENABLE: u8 = 0xF4; // start data reporting (stream mode) | ||
| 424 | const AUX_ACK: u8 = 0xFA; | ||
| 425 | |||
| 426 | /// A decoded PS/2 mouse event (one 3-byte packet). | ||
| 427 | /// | ||
| 428 | /// Movement is **screen-relative**: `dx` positive = right, `dy` positive = | ||
| 429 | /// down. (The PS/2 wire format is Y-up-positive; the driver negates it so | ||
| 430 | /// consumers can apply the deltas to framebuffer coordinates directly.) | ||
| 431 | #[derive(Clone, Copy, Debug)] | ||
| 432 | pub struct MouseEvent { | ||
| 433 | pub dx: i16, | ||
| 434 | pub dy: i16, | ||
| 435 | pub left: bool, | ||
| 436 | pub right: bool, | ||
| 437 | pub middle: bool, | ||
| 438 | } | ||
| 439 | |||
| 440 | /// Packet assembly state — lives in main context (consumer side), fed by | ||
| 441 | /// the raw byte ring that the IRQ12 handler fills. | ||
| 442 | struct MouseState { | ||
| 443 | phase: u8, // 0 = awaiting start byte, 1 = await X, 2 = await Y | ||
| 444 | pkt: [u8; 3], | ||
| 445 | } | ||
| 446 | |||
| 447 | static mut MOUSE: MouseState = MouseState { | ||
| 448 | phase: 0, | ||
| 449 | pkt: [0; 3], | ||
| 450 | }; | ||
| 451 | static MOUSE_PRESENT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); | ||
| 452 | |||
| 453 | /// True once `mouse_init` found a device that ACKed its setup commands. | ||
| 454 | pub fn mouse_present() -> bool { | ||
| 455 | MOUSE_PRESENT.load(core::sync::atomic::Ordering::Relaxed) | ||
| 456 | } | ||
| 457 | |||
| 458 | /// Enable the auxiliary (mouse) channel and put the device into stream | ||
| 459 | /// mode. Returns false when no device ACKs (controller without a second | ||
| 460 | /// port, or a missing mouse) — the kernel then just runs keyboard-only. | ||
| 461 | /// | ||
| 462 | /// Must run *before* `interrupts::init` unmasks IRQ12: the init handshake | ||
| 463 | /// reads the data port directly, so any stray mouse bytes would otherwise | ||
| 464 | /// be split across the ISR path mid-handshake. | ||
| 465 | pub fn mouse_init() -> bool { | ||
| 466 | drain_input(); | ||
| 467 | unsafe { | ||
| 468 | // 1. Enable the auxiliary port itself. | ||
| 469 | wait_write(); | ||
| 470 | core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_AUX_ENABLE, | ||
| 471 | options(nostack, preserves_flags)); | ||
| 472 | |||
| 473 | // 2. Configuration Byte: enable IRQ12 + the aux port clock. Read, | ||
| 474 | // OR the two bits, write back (preserves the keyboard IRQ bit | ||
| 475 | // that ps2::init set). | ||
| 476 | wait_write(); | ||
| 477 | core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_READ_CFG, | ||
| 478 | options(nostack, preserves_flags)); | ||
| 479 | wait_read(); | ||
| 480 | let mut cfg: u8; | ||
| 481 | core::arch::asm!("in al, dx", out("al") cfg, in("dx") DATA_PORT, | ||
| 482 | options(nostack, preserves_flags)); | ||
| 483 | cfg |= CFG_AUX_IRQ | CFG_AUX_CLOCK; | ||
| 484 | wait_write(); | ||
| 485 | core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_WRITE_CFG, | ||
| 486 | options(nostack, preserves_flags)); | ||
| 487 | wait_write(); | ||
| 488 | core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cfg, | ||
| 489 | options(nostack, preserves_flags)); | ||
| 490 | |||
| 491 | // 3. Device setup. `set defaults` normalises the sample rate / | ||
| 492 | // resolution before enabling reporting, so the packet stream | ||
| 493 | // starts at a known cadence. Both commands need a device ACK; | ||
| 494 | // a timeout here means no mouse — bail out cleanly. | ||
| 495 | if !aux_command(AUX_SET_DEFAULTS) || !aux_command(AUX_ENABLE) { | ||
| 496 | return false; | ||
| 497 | } | ||
| 498 | } | ||
| 499 | MOUSE_PRESENT.store(true, core::sync::atomic::Ordering::Relaxed); | ||
| 500 | true | ||
| 501 | } | ||
| 502 | |||
| 503 | /// Send one byte to the mouse via the 0xD4 channel and wait for its ACK. | ||
| 504 | /// The wait is bounded so a missing device cannot hang the boot. | ||
| 505 | fn aux_command(cmd: u8) -> bool { | ||
| 506 | unsafe { | ||
| 507 | wait_write(); | ||
| 508 | core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_SEND_TO_AUX, | ||
| 509 | options(nostack, preserves_flags)); | ||
| 510 | wait_write(); | ||
| 511 | core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cmd, | ||
| 512 | options(nostack, preserves_flags)); | ||
| 513 | for _ in 0..100_000 { | ||
| 514 | let status: u8; | ||
| 515 | core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, | ||
| 516 | options(nostack, preserves_flags)); | ||
| 517 | if status & STAT_OUTPUT_FULL != 0 { | ||
| 518 | let b: u8; | ||
| 519 | core::arch::asm!("in al, dx", out("al") b, in("dx") DATA_PORT, | ||
| 520 | options(nostack, preserves_flags)); | ||
| 521 | return b == AUX_ACK; | ||
| 522 | } | ||
| 523 | } | ||
| 524 | } | ||
| 525 | false | ||
| 526 | } | ||
| 527 | |||
| 528 | /// Non-blocking: decode the next pending mouse event, if a full packet has | ||
| 529 | /// arrived. Raw bytes are drained from the IRQ ring; incomplete packets | ||
| 530 | /// stay buffered in `MouseState` until the remaining bytes land. | ||
| 531 | pub fn try_poll_mouse() -> Option<MouseEvent> { | ||
| 532 | while let Some(b) = crate::interrupts::read_mouse_byte() { | ||
| 533 | if let Some(ev) = mouse_byte(b) { | ||
| 534 | return Some(ev); | ||
| 535 | } | ||
| 536 | } | ||
| 537 | None | ||
| 538 | } | ||
| 539 | |||
| 540 | /// Feed one raw byte into the packet assembler. Byte 0 of every standard | ||
| 541 | /// PS/2 mouse packet has bit 3 set (the sync marker), so a lost byte (ring | ||
| 542 | /// overrun, init response) is detected on the next packet and the stream | ||
| 543 | /// re-syncs — the marker is only accepted as a packet start. | ||
| 544 | fn mouse_byte(b: u8) -> Option<MouseEvent> { | ||
| 545 | let m = unsafe { &mut *core::ptr::addr_of_mut!(MOUSE) }; | ||
| 546 | match m.phase { | ||
| 547 | 0 => { | ||
| 548 | if b & 0x08 != 0 { | ||
| 549 | m.pkt[0] = b; | ||
| 550 | m.phase = 1; | ||
| 551 | } | ||
| 552 | None | ||
| 553 | } | ||
| 554 | 1 => { | ||
| 555 | m.pkt[1] = b; | ||
| 556 | m.phase = 2; | ||
| 557 | None | ||
| 558 | } | ||
| 559 | _ => { | ||
| 560 | m.pkt[2] = b; | ||
| 561 | m.phase = 0; | ||
| 562 | Some(MouseEvent { | ||
| 563 | // 9-bit signed: low 8 bits in the data byte, sign bit in | ||
| 564 | // byte 0 (bit 4 = X, bit 5 = Y). | ||
| 565 | dx: signed9(m.pkt[0], m.pkt[1], 0x10), | ||
| 566 | // PS/2 reports Y positive = up; flip to screen convention. | ||
| 567 | dy: -signed9(m.pkt[0], m.pkt[2], 0x20), | ||
| 568 | left: m.pkt[0] & 0x01 != 0, | ||
| 569 | right: m.pkt[0] & 0x02 != 0, | ||
| 570 | middle: m.pkt[0] & 0x04 != 0, | ||
| 571 | }) | ||
| 572 | } | ||
| 573 | } | ||
| 574 | } | ||
| 575 | |||
| 576 | /// Sign-extend an 8-bit movement byte to 9 bits using the packet's sign | ||
| 577 | /// flag (bit `sign` of byte 0), widening to i16 for consumers. | ||
| 578 | fn signed9(b0: u8, b: u8, sign: u8) -> i16 { | ||
| 579 | let mut v = (b as i8) as i16; | ||
| 580 | if b0 & sign != 0 { | ||
| 581 | // Set the 9th bit (0xFF00 = sign-extension mask for i16). | ||
| 582 | v |= 0xFF00u16 as i16; | ||
| 583 | } | ||
| 584 | v | ||
| 585 | } | ||
