Skip to content

Commit

Permalink
refactor: move ps/2 io operations to separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
winstonallo committed Mar 2, 2025
1 parent a7ddccc commit 6054eb0
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 118 deletions.
123 changes: 6 additions & 117 deletions src/ps2/controller.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
/// https://wiki.osdev.org/%228042%22_PS/2_Controller
use core::arch::asm;

use super::{Key, scancodes::SCANCODE_TO_KEY};

const DATA_PORT: u16 = 0x60;
const STATUS_PORT: u16 = 0x64;
const COMMAND_PORT: u16 = 0x64;
const OUTPUT_BUFFER_STATUS_BIT: u8 = 1;
use crate::ps2::{
DATA_PORT,
io::{flush_output_buffer, read, send_command, send_data, wait_for_data},
};

#[repr(u8)]
enum Command {
pub enum Command {
DisableFirstPort = 0xAD,
DisableSecondPort = 0xA7,
EnableFirstPort = 0xAE,
Expand All @@ -22,7 +18,7 @@ enum Command {
}

#[repr(u8)]
enum Status {
pub enum Status {
OutputFull = 0x01,
InputFull = 0x02,
}
Expand Down Expand Up @@ -293,110 +289,3 @@ pub fn init() -> Result<(), &'static str> {

Ok(())
}

fn send_command(cmd: Command) {
while unsafe { read(STATUS_PORT) } & Status::InputFull as u8 != 0 {}

unsafe { write(COMMAND_PORT, cmd as u8) };
}

fn send_data(data: u8) {
while unsafe { read(STATUS_PORT) } & Status::InputFull as u8 != 0 {}

unsafe { write(DATA_PORT, data) };
}

fn wait_for_data() -> u8 {
while unsafe { read(STATUS_PORT) } & Status::OutputFull as u8 == 0 {}

unsafe { read(DATA_PORT) }
}

/// Reads all data from the output buffer, flushing it. Note that this will
/// go into an endless loop if called without disabling the ports first.
fn flush_output_buffer() {
while unsafe { read(STATUS_PORT) } & Status::OutputFull as u8 != 0 {
unsafe { read(DATA_PORT) };
}
}

static mut LAST_KEY: Option<u8> = None;

/// Reads from the PS2 data port if the PS2 status port is ready. Returns `Some(KeyScanCode)`
/// if the converted scancode is a supported character.
///
/// /// ### Example Usage:
/// ```
/// let mut v = Vga::new();
///
/// if let Some(c) = read_if_ready() == KeyScanCode::A {
/// v.write_char(b'a');
/// }
pub fn read_if_ready() -> Option<Key> {
if !is_ps2_data_available() {
return None;
}

let code = unsafe { read(DATA_PORT) };

if code == 0xF0 {
while !is_ps2_data_available() {}
let _ = unsafe { read(DATA_PORT) };
unsafe { LAST_KEY = None };
return None;
}

if code == 0xE0 {
while !is_ps2_data_available() {}
let extended_code = unsafe { read(DATA_PORT) };
unsafe { LAST_KEY = Some(extended_code) };
return SCANCODE_TO_KEY[extended_code as usize].1;
}

unsafe { LAST_KEY = Some(code) };
SCANCODE_TO_KEY[code as usize].1
}

/// Returns `true` if the PS2 input buffer has data ready to be read,
/// meaning the least significant bit of the PS2 status port is set.
fn is_ps2_data_available() -> bool {
status() & OUTPUT_BUFFER_STATUS_BIT != 0
}

/// Reads from `STATUS_PORT` and returns the extracted value.
fn status() -> u8 {
let res: u8;

unsafe {
res = read(STATUS_PORT);
}

res
}

/// Reads from `port` and returns the extracted value.
unsafe fn read(port: u16) -> u8 {
assert!(port == DATA_PORT || port == STATUS_PORT);

let res: u8;

unsafe {
asm!(
"in al, dx",
in("dx") port,
out("al") res,
);
}

res
}

unsafe fn write(port: u16, val: u8) {
unsafe {
asm!(
"out dx, al",
in("dx") port,
in("al") val,
);
}
}
114 changes: 114 additions & 0 deletions src/ps2/io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use core::arch::asm;

use super::{
COMMAND_PORT, DATA_PORT, Key, OUTPUT_BUFFER_STATUS_BIT, STATUS_PORT,
controller::{Command, Status},
scancodes::SCANCODE_TO_KEY,
};

pub fn send_command(cmd: Command) {
while unsafe { read(STATUS_PORT) } & Status::InputFull as u8 != 0 {}

unsafe { write(COMMAND_PORT, cmd as u8) };
}

pub fn send_data(data: u8) {
while unsafe { read(STATUS_PORT) } & Status::InputFull as u8 != 0 {}

unsafe { write(DATA_PORT, data) };
}

pub fn wait_for_data() -> u8 {
while unsafe { read(STATUS_PORT) } & Status::OutputFull as u8 == 0 {}

unsafe { read(DATA_PORT) }
}

/// Reads all data from the output buffer, flushing it. Note that this will
/// go into an endless loop if called without disabling the ports first.
pub fn flush_output_buffer() {
while unsafe { read(STATUS_PORT) } & Status::OutputFull as u8 != 0 {
unsafe { read(DATA_PORT) };
}
}

static mut LAST_KEY: Option<u8> = None;

/// Reads from the PS2 data port if the PS2 status port is ready. Returns `Some(KeyScanCode)`
/// if the converted scancode is a supported character.
///
/// /// ### Example Usage:
/// ```
/// let mut v = Vga::new();
///
/// if let Some(c) = read_if_ready() == KeyScanCode::A {
/// v.write_char(b'a');
/// }
pub fn read_if_ready() -> Option<Key> {
if !is_ps2_data_available() {
return None;
}

let code = unsafe { read(DATA_PORT) };

if code == 0xF0 {
while !is_ps2_data_available() {}
let _ = unsafe { read(DATA_PORT) };
unsafe { LAST_KEY = None };
return None;
}

if code == 0xE0 {
while !is_ps2_data_available() {}
let extended_code = unsafe { read(DATA_PORT) };
unsafe { LAST_KEY = Some(extended_code) };
return SCANCODE_TO_KEY[extended_code as usize].1;
}

unsafe { LAST_KEY = Some(code) };
SCANCODE_TO_KEY[code as usize].1
}

/// Returns `true` if the PS2 input buffer has data ready to be read,
/// meaning the least significant bit of the PS2 status port is set.
fn is_ps2_data_available() -> bool {
status() & OUTPUT_BUFFER_STATUS_BIT != 0
}

/// Reads from `STATUS_PORT` and returns the extracted value.
fn status() -> u8 {
let res: u8;

unsafe {
res = read(STATUS_PORT);
}

res
}

/// Reads from `port` and returns the extracted value.
pub unsafe fn read(port: u16) -> u8 {
assert!(port == DATA_PORT || port == STATUS_PORT);

let res: u8;

unsafe {
asm!(
"in al, dx",
in("dx") port,
out("al") res,
);
}

res
}

unsafe fn write(port: u16, val: u8) {
unsafe {
asm!(
"out dx, al",
in("dx") port,
in("al") val,
);
}
}
9 changes: 8 additions & 1 deletion src/ps2/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
mod controller;
mod io;
mod scancodes;

pub use controller::{init, read_if_ready};
pub const DATA_PORT: u16 = 0x60;
pub const STATUS_PORT: u16 = 0x64;
pub const COMMAND_PORT: u16 = 0x64;
pub const OUTPUT_BUFFER_STATUS_BIT: u8 = 1;

pub use controller::init;
pub use io::read_if_ready;
pub use scancodes::Key;

0 comments on commit 6054eb0

Please sign in to comment.