ps2d: add MouseTx abstraction and WIP touchpad identification

This commit is contained in:
Jeremy Soller
2026-01-29 08:56:00 -07:00
parent 3edfd63eec
commit ce9c2a1fb4
+159 -48
View File
@@ -21,9 +21,56 @@ enum MouseCommand {
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
enum MouseCommandData {
SetResolution = 0xE8,
SetSampleRate = 0xF3,
}
#[derive(Debug)]
struct MouseTx {
write: &'static [u8],
write_i: usize,
read: Vec<u8>,
read_bytes: usize,
}
impl MouseTx {
fn new(write: &'static [u8], read_bytes: usize, ps2: &mut Ps2) -> Result<Self, ()> {
let mut this = Self {
write,
write_i: 0,
read: Vec::with_capacity(read_bytes),
read_bytes,
};
this.try_write(ps2)?;
Ok(this)
}
fn try_write(&mut self, ps2: &mut Ps2) -> Result<(), ()> {
if let Some(write) = self.write.get(self.write_i) {
if let Err(err) = ps2.mouse_command_async(*write) {
log::error!("failed to write {:02X} to mouse: {:?}", write, err);
return Err(());
}
}
Ok(())
}
fn handle(&mut self, data: u8, ps2: &mut Ps2) -> Result<bool, ()> {
if self.write_i < self.write.len() {
if data == 0xFA {
self.write_i += 1;
self.try_write(ps2)?;
} else {
log::error!("unknown mouse response {:02X}", data);
return Err(());
}
} else {
self.read.push(data);
}
Ok(self.write_i >= self.write.len() && self.read.len() >= self.read_bytes)
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
#[allow(dead_code)]
@@ -47,8 +94,12 @@ pub enum MouseState {
Reset,
/// BAT completion code returned
Bat,
/// Identify touchpad
IdentifyTouchpad { tx: MouseTx },
/// Enable intellimouse features
EnableIntellimouse { index: usize, sent_command: bool },
EnableIntellimouse { tx: MouseTx },
/// Status request
Status { index: usize },
/// Device ID update
DeviceId,
/// Enable reporting command sent
@@ -96,6 +147,20 @@ impl MouseState {
}
}
fn request_status(&mut self, ps2: &mut Ps2) -> MouseResult {
match ps2.mouse_command_async(MouseCommand::StatusRequest as u8) {
Ok(()) => {
*self = MouseState::Status { index: 0 };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
Err(err) => {
log::error!("failed to request mouse status: {:?}", err);
//TODO: reset mouse instead?
self.request_id(ps2)
}
}
}
fn request_id(&mut self, ps2: &mut Ps2) -> MouseResult {
match ps2.mouse_command_async(MouseCommand::GetDeviceId as u8) {
Ok(()) => {
@@ -110,40 +175,45 @@ impl MouseState {
}
}
fn identify_touchpad(&mut self, ps2: &mut Ps2) -> MouseResult {
let cmd = TouchpadCommand::Identify as u8;
match MouseTx::new(&[
// Ensure command alignment
MouseCommand::SetScaling1To1 as u8,
// Send special identify touchpad command
MouseCommandData::SetResolution as u8, 0,
MouseCommandData::SetResolution as u8, 0,
MouseCommandData::SetResolution as u8, 0,
MouseCommandData::SetResolution as u8, 0,
// Status request
MouseCommand::StatusRequest as u8
], 3, ps2) {
Ok(tx) => {
*self = MouseState::IdentifyTouchpad { tx };
MouseResult::Timeout(COMMAND_TIMEOUT)
},
Err(()) => {
self.enable_intellimouse(ps2)
}
}
}
fn enable_intellimouse(
&mut self,
index: usize,
sent_command: bool,
ps2: &mut Ps2,
) -> MouseResult {
let magic = [200, 100, 80];
if let Some(data) = magic.get(index) {
match ps2.mouse_command_async(if sent_command {
*data
} else {
MouseCommandData::SetSampleRate as u8
}) {
Ok(()) => {
*self = if sent_command {
MouseState::EnableIntellimouse {
index: index + 1,
sent_command: false,
}
} else {
MouseState::EnableIntellimouse {
index,
sent_command: true,
}
};
MouseResult::Timeout(COMMAND_TIMEOUT)
}
Err(err) => {
log::error!("failed to send intellimouse command: {:?}", err);
self.request_id(ps2)
}
match MouseTx::new(&[
MouseCommandData::SetSampleRate as u8, 200,
MouseCommandData::SetSampleRate as u8, 100,
MouseCommandData::SetSampleRate as u8, 80
], 0, ps2) {
Ok(tx) => {
*self = MouseState::EnableIntellimouse { tx };
MouseResult::Timeout(COMMAND_TIMEOUT)
},
Err(()) => {
self.request_id(ps2)
}
} else {
self.request_id(ps2)
}
}
@@ -174,7 +244,7 @@ impl MouseState {
if data == MouseId::Base as u8 {
// Enable intellimouse features
log::debug!("BAT mouse id {:02X} (base)", data);
self.enable_intellimouse(0, false, ps2)
self.identify_touchpad(ps2)
} else if data == MouseId::Intellimouse1 as u8 {
// Extra packet already enabled
log::debug!("BAT mouse id {:02X} (intellimouse)", data);
@@ -184,18 +254,50 @@ impl MouseState {
MouseResult::Timeout(RESET_TIMEOUT)
}
}
MouseState::EnableIntellimouse {
index,
sent_command,
} => {
if data == 0xFA {
self.enable_intellimouse(index, sent_command, ps2)
} else {
log::warn!(
"unknown mouse response {:02X} while enabling intellimouse",
data
);
self.request_id(ps2)
MouseState::IdentifyTouchpad { ref mut tx } => {
match tx.handle(data, ps2) {
Ok(done) => if done {
//TODO: handle touchpad identification
// If tx.read[1] == 0x47, this is a synaptics touchpad
self.request_status(ps2)
} else {
MouseResult::Timeout(COMMAND_TIMEOUT)
},
Err(()) => {
self.enable_intellimouse(ps2)
}
}
}
MouseState::EnableIntellimouse { ref mut tx } => {
match tx.handle(data, ps2) {
Ok(done) => if done {
self.request_status(ps2)
} else {
MouseResult::Timeout(COMMAND_TIMEOUT)
},
Err(()) => {
self.request_status(ps2)
}
}
}
MouseState::Status { index } => {
match index {
0 => {
//TODO: check response
*self = MouseState::Status { index: 1 };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
1 => {
*self = MouseState::Status { index: 2 };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
2 => {
*self = MouseState::Status { index: 3 };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
_ => {
self.request_id(ps2)
}
}
}
MouseState::DeviceId => {
@@ -232,27 +334,36 @@ impl MouseState {
self.reset(ps2)
}
MouseState::Reset => {
log::warn!("timeout while waiting for mouse reset");
log::warn!("timeout waiting for mouse reset");
//TODO: retry reset?
*self = MouseState::None;
MouseResult::None
}
MouseState::Bat => {
log::warn!("timeout while waiting for BAT completion");
log::warn!("timeout waiting for BAT completion");
//TODO: limit number of resets
self.reset(ps2)
}
MouseState::IdentifyTouchpad { .. } => {
//TODO: retry?
log::warn!("timeout identifying touchpad");
self.request_status(ps2)
}
MouseState::EnableIntellimouse { .. } => {
//TODO: retry?
log::warn!("timeout while enabling intellimouse");
log::warn!("timeout enabling intellimouse");
self.request_status(ps2)
}
MouseState::Status { index } => {
log::warn!("timeout waiting for mouse status {}", index);
self.request_id(ps2)
}
MouseState::DeviceId => {
log::warn!("timeout while requesting mouse id");
log::warn!("timeout requesting mouse id");
self.enable_reporting(0, ps2)
}
MouseState::EnableReporting { id } => {
log::warn!("timeout while enabling reporting");
log::warn!("timeout enabling reporting");
//TODO: limit number of retries
self.enable_reporting(id, ps2)
}