Created
April 22, 2020 00:10
-
-
Save RKennedy9064/5bffdfba096a1c4e3a1e10c50681be72 to your computer and use it in GitHub Desktop.
Basic mouse input
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Add new interrupt to the idt | |
idt[InterruptIndex::Mouse.into()].set_handler_fn(mouse_interrupt_handler); | |
// Basic example interrupt handler | |
extern "x86-interrupt" fn mouse_interrupt_handler(_stack_frame: &mut InterruptStackFrame) { | |
let mut port = PortReadOnly::new(0x60); | |
let packet = unsafe { port.read() }; | |
MOUSE.lock().process_packets(packet); | |
unsafe { | |
PICS.lock() | |
.notify_end_of_interrupt(InterruptIndex::Mouse.into()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Init the mouse wherever it makes sense for your OS | |
mouse::MOUSE.lock().init().unwrap(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bitflags::bitflags; | |
use conquer_once::spin::Lazy; | |
use spinning_top::Spinlock; | |
use x86_64::instructions::port::Port; | |
const ADDRESS_PORT_ADDRESS: u16 = 0x64; | |
const DATA_PORT_ADDRESS: u16 = 0x60; | |
const GET_STATUS_BYTE: u8 = 0x20; | |
const SET_STATUS_BYTE: u8 = 0x60; | |
pub static MOUSE: Lazy<Spinlock<Mouse>> = Lazy::new(|| Spinlock::new(Mouse::new())); | |
bitflags! { | |
struct MouseFlags: u8 { | |
const LEFT_BUTTON = 0b0000_0001; | |
const RIGHT_BUTTON = 0b0000_0010; | |
const MIDDLE_BUTTON = 0b0000_0100; | |
const ALWAYS_ONE = 0b0000_1000; | |
const X_SIGN = 0b0001_0000; | |
const Y_SIGN = 0b0010_0000; | |
const X_OVERFLOW = 0b0100_0000; | |
const Y_OVERFLOW = 0b1000_0000; | |
} | |
} | |
#[repr(u8)] | |
enum Command { | |
EnablePacketStreaming = 0xF4, | |
SetDefaults = 0xF6, | |
} | |
#[derive(Debug)] | |
pub struct Mouse { | |
command_port: Port<u8>, | |
data_port: Port<u8>, | |
current_packet: u8, | |
data_packet: MouseFlags, | |
// Stores the relative x movement since the last command | |
x: i16, | |
// Stores the relative y movement since the last command | |
y: i16, | |
} | |
impl Mouse { | |
pub fn new() -> Mouse { | |
Mouse { | |
command_port: Port::new(ADDRESS_PORT_ADDRESS), | |
data_port: Port::new(DATA_PORT_ADDRESS), | |
current_packet: 0, | |
data_packet: MouseFlags::empty(), | |
x: 0, | |
y: 0, | |
} | |
} | |
pub fn init(&mut self) -> Result<(), &'static str> { | |
// Get the status byte | |
self.write_command_port(GET_STATUS_BYTE)?; | |
// Turn on bit 1 to enable IRQ12 | |
let status = self.read_data_port()? | 0x02; | |
// Send set status byte command | |
self.write_command_port(SET_STATUS_BYTE)?; | |
// Send status byte, with bit 5 disabled (mouse clock) | |
self.write_data_port(status | 0xDF)?; | |
// Enable the mouse with defaults | |
self.send_command(Command::SetDefaults)?; | |
// Turn on interrupts | |
self.send_command(Command::EnablePacketStreaming)?; | |
// Everything worked | |
Ok(()) | |
} | |
pub fn process_packets(&mut self, packet: u8) { | |
// Every 3 packets represents 1 command by default, | |
// so process them 1 at a time. | |
match self.current_packet { | |
0 => { | |
let flags = MouseFlags::from_bits_truncate(packet); | |
// If this bit isn't set the packet isn't valid, | |
// so wait until it's set. | |
if !flags.contains(MouseFlags::ALWAYS_ONE) { | |
return; | |
} | |
self.data_packet = flags; | |
} | |
1 => self.process_x_movement(packet), | |
2 => self.process_y_movement(packet), | |
_ => unreachable!(), | |
} | |
// Uncomment to print packets | |
// use crate::serial_println; | |
// | |
// if self.current_packet == 2 { | |
// serial_println!("{:08b} {} {}", self.data_packet, self.x, self.y); | |
// } | |
self.current_packet = (self.current_packet + 1) % 3; | |
} | |
fn process_x_movement(&mut self, packet: u8) { | |
// If the x overflow bit is set, then the packet represents | |
// a negative value, so sign extend it. If not, treat it as is. | |
// | |
// There's probably a cleaner way to do this. | |
if !self.data_packet.contains(MouseFlags::X_OVERFLOW) { | |
self.x = if self.data_packet.contains(MouseFlags::X_SIGN) { | |
self.sign_extend(packet) | |
} else { | |
packet as i16 | |
}; | |
} | |
} | |
fn process_y_movement(&mut self, packet: u8) { | |
// If the y overflow bit is set, then the packet represents | |
// a negative value, so sign extend it. If not, treat it as is. | |
// | |
// There's probably a cleaner way to do this. | |
if !self.data_packet.contains(MouseFlags::Y_OVERFLOW) { | |
self.y = if self.data_packet.contains(MouseFlags::Y_SIGN) { | |
self.sign_extend(packet) | |
} else { | |
packet as i16 | |
}; | |
} | |
} | |
fn sign_extend(&self, packet: u8) -> i16 { | |
// Cast as a u16 so you can | 0xFF00 without overflow errors, | |
// then cast it to an i16 so it's treated as negative. | |
((packet as u16) | 0xFF00) as i16 | |
} | |
fn read_data_port(&mut self) -> Result<u8, &'static str> { | |
self.wait_for_read()?; | |
Ok(unsafe { self.data_port.read() }) | |
} | |
fn send_command(&mut self, command: Command) -> Result<(), &'static str> { | |
// Tell the mouse we're sending a command | |
self.write_command_port(0xD4)?; | |
// Send the command | |
self.write_data_port(command as u8)?; | |
// If the mouse doesn't reply with 0xFA then the request wasn't received | |
if self.read_data_port()? != 0xFA { | |
return Err("mouse did not respond to the command"); | |
} | |
Ok(()) | |
} | |
fn write_command_port(&mut self, value: u8) -> Result<(), &'static str> { | |
self.wait_for_write()?; | |
unsafe { | |
self.command_port.write(value); | |
} | |
Ok(()) | |
} | |
fn write_data_port(&mut self, value: u8) -> Result<(), &'static str> { | |
self.wait_for_write()?; | |
unsafe { | |
self.data_port.write(value); | |
} | |
Ok(()) | |
} | |
fn wait_for_read(&mut self) -> Result<(), &'static str> { | |
// Arbirtrary timeout value, can be increased or decreased | |
// to whatever makes sense | |
let timeout = 100_000; | |
for _ in 0..timeout { | |
let value = unsafe { self.command_port.read() }; | |
// The port is ready to be read if bit 0 is set | |
if (value & 0x1) == 0x1 { | |
return Ok(()); | |
} | |
} | |
Err("wait for mouse read timeout") | |
} | |
fn wait_for_write(&mut self) -> Result<(), &'static str> { | |
// Arbirtrary timeout value, can be increased or decreased | |
// to whatever makes sense | |
let timeout = 100_000; | |
for _ in 0..timeout { | |
let value = unsafe { self.command_port.read() }; | |
// Ready to be written if bit 1 is not set | |
if (value & 0x2) == 0x0 { | |
return Ok(()); | |
} | |
} | |
Err("wait for mouse write timeout") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment