aboutsummaryrefslogtreecommitdiff
path: root/src/machine/keyctrl.rs
diff options
context:
space:
mode:
authorTianhao Wang <wth@riseup.net>2024-02-05 04:18:51 +0100
committerTianhao Wang <shrik3@mailbox.org>2024-06-11 15:13:38 +0200
commit959d50d9117a0ed3f280bf7d5bfbffd1fa1a5740 (patch)
treecfa2d2cf408ce9597fed3076b76251b8e3e7537d /src/machine/keyctrl.rs
parenta6b91c56f29257c3e54f0bfb2d99bf07262cb184 (diff)
PS/2 Keyboard controller: read and decode key
Diffstat (limited to 'src/machine/keyctrl.rs')
-rw-r--r--src/machine/keyctrl.rs321
1 files changed, 224 insertions, 97 deletions
diff --git a/src/machine/keyctrl.rs b/src/machine/keyctrl.rs
index 246ac02..40385a2 100644
--- a/src/machine/keyctrl.rs
+++ b/src/machine/keyctrl.rs
@@ -3,151 +3,238 @@ use crate::machine::device_io::*;
use bitflags::bitflags;
use core::cmp;
use core::cmp::{Eq, PartialEq};
+use core::ffi::c_uchar;
use num_enum::{IntoPrimitive, TryFromPrimitive};
// Driver for the PS/2 keybard/mouse controller
-// beyound OOStuBS: I'm gonna write full driver for it.
-
+// beyound OOStuBS:
+// The 'gather' field should NEVER have imcomplete state: it should either be a
+// valid key, or nothing.
+// TODO what if IO ops fails or timeout?
// TODO figure out how to abstract the "interrupt control" layer.
// Keyboard controller should not be arch dependent
#[cfg(target_arch = "x86_64")]
use crate::arch::x86_64::interrupt::{pic_8259, pic_8259::PicDeviceInt as PD};
-// this is the driver for keyboard controller not to confuse with the keyboard module.
-// The later is an abstraction
-// This one serves a the HW driver
+use super::key::Modifiers;
// TODO
-// [functions]
-// Keyboard_Controller()
-// get_ascii_code()
-// key_decoded()
-// key_hit()
// reboot()
// set_led(char led,bool on)
// set_repeat_rate(int speed,int delay)
pub struct KeyboardController {
- code: u8,
- prefix: u8,
leds: Led,
- current: Option<Key>,
- last: Option<Key>,
- gather: Option<Key>,
+ keystate: KeyState,
+ gather: Option<Key>, // if not collected timely it will be overwritten
cport: IOPort,
dport: IOPort,
}
-pub enum KeyDelay {
- L0 = 0,
- L1 = 1,
- L2 = 2,
- L3 = 3,
+struct KeyState {
+ modi: Modifiers, // active modifiers
+ prefix: Prefix, // prefix state for some certain modifiers
+ scan: Option<u8>,
}
-// x86 and arm has different interrupt controller
-#[cfg(target_arch = "x86_64")]
-impl KeyboardController {
- #[inline(always)]
- fn disable_keyboard_int() {
- pic_8259::forbid(PD::KEYBOARD);
+#[derive(Clone, Copy, Eq, PartialEq)]
+enum Prefix {
+ PREFIX1,
+ PREFIX2,
+ NONE, // don't confuse Option None with Prefix enum NONE
+}
+
+impl Prefix {
+ pub fn try_from_u8(val: u8) -> Option<Prefix> {
+ match val {
+ Defs::C_PREFIX1 => Some(Self::PREFIX1),
+ Defs::C_PREFIX2 => Some(Self::PREFIX2),
+ _ => None,
+ }
}
+}
- #[inline(always)]
- fn enable_keyboard_int() {
- pic_8259::allow(PD::KEYBOARD);
+impl KeyState {
+ pub fn new() -> Self {
+ return Self {
+ modi: Modifiers::NONE,
+ prefix: Prefix::NONE,
+ scan: None,
+ };
}
- #[inline(always)]
- fn toggle_keyboard_int(enable: bool) {
- if enable {
- Self::enable_keyboard_int()
- } else {
- Self::disable_keyboard_int()
- }
+ pub fn toggle_capslock(&mut self) {
+ self.modi.toggle(Modifiers::CAPSLOCK);
}
- #[inline(always)]
- fn is_int_masked() -> bool {
- pic_8259::is_masked(PD::KEYBOARD)
+ pub fn toggle_numlock(&mut self) {
+ self.modi.toggle(Modifiers::NUMLOCK);
+ }
+
+ pub fn toggle_scroll_lock(&mut self) {
+ self.modi.toggle(Modifiers::SCROLL_LOCK);
}
}
impl KeyboardController {
pub fn new() -> Self {
Self {
- code: 0,
- prefix: 0,
leds: Led::NONE,
- last: None,
- current: None,
- gather: None,
+ keystate: KeyState::new(),
cport: IOPort::new(Defs::CTRL),
dport: IOPort::new(Defs::DATA),
+ gather: None,
}
}
- pub fn fetch_key(&mut self) -> Key {
- todo!();
- // this should be called by the interrupt handler.
- // 1. read raw keycode from data port
- // 2. try to decode and put the result into self.gather.
- // TODO consider move the decoding into the epilogue.
- }
-
- // key decoding is stateful, this can't be implemented into Key struct.
- pub fn decode_key(&mut self) -> bool {
- // try to decode the raw 'code' into self.gather
- // return true upon success
- // this is a transcription of the C code.... Improve later.
- // OOStuBS: The keys that were added in MF II keyboards -- compared to the older AT
- // keyboard -- always send one of two possible prefix bytes first.
- let done = false;
- if self.code == Defs::PREFIX1 || self.code == Defs::PREFIX2 {
- self.prefix = self.code;
- return false;
+ // Led and lock state are toggled together, to prevent out-of-sync
+ // TODO: rollback lock state if setting led fails
+ fn toggle_lock(&mut self, lock: Modifiers) {
+ let led = match lock {
+ Modifiers::SCROLL_LOCK => Some(Led::SCROLL_LOCK),
+ Modifiers::CAPSLOCK => Some(Led::CAPS_LOCK),
+ Modifiers::NUMLOCK => Some(Led::NUM_LOCk),
+ _ => None,
+ };
+ if let Some(led) = led {
+ self.keystate.modi.toggle(lock);
+ self.toggle_led(led);
}
+ }
- // OOStuBS: Releasing a key is actually only interesting in this implementation
- // for the "modifier" keys SHIFT, CTRL and ALT. For the other keys, we
- // can ignore the break code.
- // ; A Key's break code is identical to its make code with break_bit set
-
- false
+ fn toggle_led(&mut self, led: Led) {
+ self.leds.toggle(led);
+ todo!("toggle keyboard led: ");
}
- // block until status.outb becomes 1
- // return false on invalid SR.
- // these wait_ functions are unsafe. If used inproperly this becomes a deadloop
- // TODO support software timeout
- unsafe fn wait_write(&self) {
- loop {
- let sr = StatusReg::from_bits_truncate(self.cport.inb());
- if sr.contains(StatusReg::OUTB) {
- break;
+ pub fn update_state(&mut self, code: u8) {
+ // TODO investigate this code pattern: is there much runtime cost??
+ self.keystate.scan = Some(code);
+ if let Some(p) = Prefix::try_from_u8(code) {
+ self.keystate.prefix = p;
+ return;
+ }
+ if code & Defs::BREAK_BIT == 0 {
+ if self.press_event() {
+ self.decode_key();
}
+ } else {
+ self.release_event();
}
+ // the prefix should have been comsumed at this point so clear it
+ self.keystate.prefix = Prefix::NONE;
}
- // block until status.inb becomes 0
- unsafe fn wait_read(&self) {
- loop {
- let sr = StatusReg::from_bits_truncate(self.cport.inb());
- if !sr.contains(StatusReg::INB) {
- break;
+ fn press_event(&mut self) -> bool {
+ let mut should_decode_ascii = false;
+ let code = self.keystate.scan.unwrap();
+ match code {
+ Defs::C_SHIFT_L | Defs::C_SHIFT_R => self.keystate.modi.insert(Modifiers::SHIFT),
+ Defs::C_ALT => match self.keystate.prefix {
+ Prefix::PREFIX1 => self.keystate.modi.insert(Modifiers::ALT_RIGHT),
+ _ => self.keystate.modi.insert(Modifiers::ALT_LEFT),
+ },
+ Defs::C_CTRL => match self.keystate.prefix {
+ Prefix::PREFIX1 => self.keystate.modi.insert(Modifiers::CTRL_RIGHT),
+ _ => self.keystate.modi.insert(Modifiers::CTRL_LEFT),
+ },
+ Defs::C_CAPSLOCK => self.toggle_lock(Modifiers::CAPSLOCK),
+ Defs::C_NUM_P => {
+ if !self.keystate.modi.contains(Modifiers::CTRL_LEFT) {
+ self.toggle_lock(Modifiers::NUMLOCK);
+ }
+ }
+ Defs::C_SCRLOCK => self.toggle_lock(Modifiers::SCROLL_LOCK),
+ _ => {
+ should_decode_ascii = true;
}
}
+ should_decode_ascii
}
- pub fn set_repeat_rate_delay(rate: u8, delay: KeyDelay) {
- let rate = cmp::min(rate, 31);
- let delay = delay as u8;
- let is_masked = Self::is_int_masked();
- // idsable keyboard interrupt
- // TODO should have a timeout
- Self::disable_keyboard_int();
+ fn release_event(&mut self) {
+ // we only care about release events for shift/alt/ctrl
+ let code = self.keystate.scan.unwrap() & !Defs::BREAK_BIT;
+ match code {
+ Defs::C_SHIFT_L | Defs::C_SHIFT_R => self.keystate.modi.remove(Modifiers::SHIFT),
+ Defs::C_ALT => match self.keystate.prefix {
+ Prefix::PREFIX1 => self.keystate.modi.remove(Modifiers::ALT_RIGHT),
+ _ => self.keystate.modi.remove(Modifiers::ALT_LEFT),
+ },
+ Defs::C_CTRL => match self.keystate.prefix {
+ Prefix::PREFIX1 => self.keystate.modi.remove(Modifiers::CTRL_RIGHT),
+ _ => self.keystate.modi.remove(Modifiers::CTRL_LEFT),
+ },
+ _ => {}
+ }
+ }
- Self::toggle_keyboard_int(is_masked);
+ #[inline(always)]
+ pub fn read_status(&self) -> Option<StatusReg> {
+ // TODO maybe there is a path which leads to invalid SR, e.g. timeout?
+ Some(StatusReg::from_bits_truncate(self.cport.inb()))
+ }
+
+ // this should be called by the interrupt handler prologue
+ pub fn fetch_key(&mut self) {
+ // I'd like to see if this panics....
+ let sr = self.read_status().unwrap();
+ // ignore mouse events
+ if !sr.contains(StatusReg::OUTB) || sr.contains(StatusReg::AUXB) {
+ return;
+ }
+ self.update_state(self.dport.inb());
+ }
+
+ // this should be called by the "epilogue"
+ pub fn consume_key(&mut self) -> Option<Key>{
+ let res = self.gather.clone();
+ self.gather = None;
+ return res
+ }
+
+ pub fn decode_key(&mut self) {
+ // the decode_key should not be called when there is no scancode.
+ // mask the breakbit
+ let s = self.keystate.scan.unwrap();
+ let c = s & !Defs::BREAK_BIT;
+ let m = self.keystate.modi;
+ let p = self.keystate.prefix;
+
+ if c == 53 && p == Prefix::PREFIX1 {
+ self.gather = Some(Key {
+ asc: b'/',
+ modi: m,
+ scan: s,
+ });
+ return;
+ }
+
+ let asc = if m.contains(Modifiers::NUMLOCK) && p == Prefix::NONE && c >= 71 && c <= 83 {
+ ASC_NUM_TAB[c as usize - 71]
+ } else if m.contains(Modifiers::ALT_RIGHT) {
+ ALT_TAB[c as usize]
+ } else if m.contains(Modifiers::SHIFT) {
+ SHIFT_TAB[c as usize]
+ } else if m.contains(Modifiers::CAPSLOCK) {
+ if (c >= 16 && c <= 26) || (c >= 30 && c <= 40) || (c >= 44 && c <= 50) {
+ SHIFT_TAB[c as usize]
+ } else {
+ NORMAL_TAB[c as usize]
+ }
+ } else {
+ NORMAL_TAB[c as usize]
+ };
+
+ self.gather = Some(Key {
+ asc,
+ modi: m,
+ scan: s,
+ });
+ }
+
+ pub fn set_repeat_rate_delay() {
+ todo!();
}
pub fn reboot(&mut self) {
@@ -155,8 +242,33 @@ impl KeyboardController {
}
}
-// I think constants are more handy than enum for these...
-// Keyboard controller commands
+// x86 and arm has different interrupt controller
+#[cfg(target_arch = "x86_64")]
+impl KeyboardController {
+ #[inline(always)]
+ fn disable_keyboard_int() {
+ pic_8259::forbid(PD::KEYBOARD);
+ }
+
+ #[inline(always)]
+ fn enable_keyboard_int() {
+ pic_8259::allow(PD::KEYBOARD);
+ }
+
+ #[inline(always)]
+ fn toggle_keyboard_int(enable: bool) {
+ if enable {
+ Self::enable_keyboard_int()
+ } else {
+ Self::disable_keyboard_int()
+ }
+ }
+
+ #[inline(always)]
+ fn is_int_masked() -> bool {
+ pic_8259::is_masked(PD::KEYBOARD)
+ }
+}
enum Cmd {
// these commands are sent through DATA port
@@ -200,7 +312,22 @@ impl Defs {
pub const CTRL: u16 = 0x64;
pub const DATA: u16 = 0x60;
pub const CPU_RESET: u8 = 0xfe;
- pub const BREAK_BIT: u8 = 0x80;
- pub const PREFIX1: u8 = 0xe0;
- pub const PREFIX2: u8 = 0xe1;
+ pub const BREAK_BIT: u8 = 1 << 7;
+ // defs of special scan codes.
+ pub const C_PREFIX1: u8 = 0xe0;
+ pub const C_PREFIX2: u8 = 0xe1;
+ pub const C_SHIFT_L: u8 = 0x2a;
+ pub const C_SHIFT_R: u8 = 0x36;
+ pub const C_ALT: u8 = 0x38;
+ pub const C_CTRL: u8 = 0x1d;
+ pub const C_CAPSLOCK: u8 = 0x3a;
+ pub const C_SCRLOCK: u8 = 0x46;
+ pub const C_NUM_P: u8 = 0x45; // NumLock or Pause
+ pub const C_F1: u8 = 0x3b;
+ pub const C_DEL: u8 = 0x53;
+ pub const C_UP: u8 = 0x48;
+ pub const C_DOWN: u8 = 0x50;
+ pub const C_LEFT: u8 = 0x4b;
+ pub const C_RIGHT: u8 = 0x4d;
+ pub const C_DIV: u8 = 0x8;
}