Skip to content

Instantly share code, notes, and snippets.

@RKennedy9064
Created April 22, 2020 00:10
Show Gist options
  • Save RKennedy9064/5bffdfba096a1c4e3a1e10c50681be72 to your computer and use it in GitHub Desktop.
Save RKennedy9064/5bffdfba096a1c4e3a1e10c50681be72 to your computer and use it in GitHub Desktop.
Basic mouse input
// 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());
}
}
// Init the mouse wherever it makes sense for your OS
mouse::MOUSE.lock().init().unwrap();
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