diff options
Diffstat (limited to 'src/ps2.rs')
| -rw-r--r-- | src/ps2.rs | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/src/ps2.rs b/src/ps2.rs new file mode 100644 index 0000000..e972658 --- /dev/null +++ b/src/ps2.rs | |||
| @@ -0,0 +1,329 @@ | |||
| 1 | /// PS/2 keyboard driver — polling, scancode set 1, US QWERTY layout. | ||
| 2 | // Port addresses | ||
| 3 | const DATA_PORT: u16 = 0x60; | ||
| 4 | const STATUS_PORT: u16 = 0x64; | ||
| 5 | |||
| 6 | // Status register bits | ||
| 7 | const STAT_OUTPUT_FULL: u8 = 0x01; | ||
| 8 | |||
| 9 | // Modifier bit constants | ||
| 10 | const MOD_LSHIFT: u8 = 0x01; | ||
| 11 | const MOD_RSHIFT: u8 = 0x02; | ||
| 12 | const MOD_LCTRL: u8 = 0x04; | ||
| 13 | const MOD_RCTRL: u8 = 0x08; | ||
| 14 | const MOD_LALT: u8 = 0x10; | ||
| 15 | const MOD_RALT: u8 = 0x20; | ||
| 16 | const MOD_CAPS: u8 = 0x40; | ||
| 17 | |||
| 18 | /// A decoded key event. | ||
| 19 | pub enum KeyEvent { | ||
| 20 | Char(char), | ||
| 21 | Enter, | ||
| 22 | Backspace, | ||
| 23 | Tab, | ||
| 24 | Escape, | ||
| 25 | ArrowUp, | ||
| 26 | ArrowDown, | ||
| 27 | ArrowLeft, | ||
| 28 | ArrowRight, | ||
| 29 | Home, | ||
| 30 | End, | ||
| 31 | PageUp, | ||
| 32 | PageDown, | ||
| 33 | Delete, | ||
| 34 | F(u8), | ||
| 35 | Unknown(u8), | ||
| 36 | } | ||
| 37 | |||
| 38 | // ----- scancode-set-1 translation tables (US QWERTY) ----- | ||
| 39 | |||
| 40 | /// Normal (unshifted) scancode → ASCII mapping. 0 = unmapped. | ||
| 41 | static SCAN_NORMAL: [u8; 128] = { | ||
| 42 | let mut t = [0u8; 128]; | ||
| 43 | t[0x02] = b'1'; | ||
| 44 | t[0x03] = b'2'; | ||
| 45 | t[0x04] = b'3'; | ||
| 46 | t[0x05] = b'4'; | ||
| 47 | t[0x06] = b'5'; | ||
| 48 | t[0x07] = b'6'; | ||
| 49 | t[0x08] = b'7'; | ||
| 50 | t[0x09] = b'8'; | ||
| 51 | t[0x0A] = b'9'; | ||
| 52 | t[0x0B] = b'0'; | ||
| 53 | t[0x0C] = b'-'; | ||
| 54 | t[0x0D] = b'='; | ||
| 55 | t[0x10] = b'q'; | ||
| 56 | t[0x11] = b'w'; | ||
| 57 | t[0x12] = b'e'; | ||
| 58 | t[0x13] = b'r'; | ||
| 59 | t[0x14] = b't'; | ||
| 60 | t[0x15] = b'y'; | ||
| 61 | t[0x16] = b'u'; | ||
| 62 | t[0x17] = b'i'; | ||
| 63 | t[0x18] = b'o'; | ||
| 64 | t[0x19] = b'p'; | ||
| 65 | t[0x1A] = b'['; | ||
| 66 | t[0x1B] = b']'; | ||
| 67 | t[0x1E] = b'a'; | ||
| 68 | t[0x1F] = b's'; | ||
| 69 | t[0x20] = b'd'; | ||
| 70 | t[0x21] = b'f'; | ||
| 71 | t[0x22] = b'g'; | ||
| 72 | t[0x23] = b'h'; | ||
| 73 | t[0x24] = b'j'; | ||
| 74 | t[0x25] = b'k'; | ||
| 75 | t[0x26] = b'l'; | ||
| 76 | t[0x27] = b';'; | ||
| 77 | t[0x28] = b'\''; | ||
| 78 | t[0x29] = b'`'; | ||
| 79 | t[0x2B] = b'\\'; | ||
| 80 | t[0x2C] = b'z'; | ||
| 81 | t[0x2D] = b'x'; | ||
| 82 | t[0x2E] = b'c'; | ||
| 83 | t[0x2F] = b'v'; | ||
| 84 | t[0x30] = b'b'; | ||
| 85 | t[0x31] = b'n'; | ||
| 86 | t[0x32] = b'm'; | ||
| 87 | t[0x33] = b','; | ||
| 88 | t[0x34] = b'.'; | ||
| 89 | t[0x35] = b'/'; | ||
| 90 | t[0x37] = b'*'; | ||
| 91 | t[0x39] = b' '; | ||
| 92 | t | ||
| 93 | }; | ||
| 94 | |||
| 95 | /// Shifted scancode → ASCII. | ||
| 96 | static SCAN_SHIFT: [u8; 128] = { | ||
| 97 | let mut t = [0u8; 128]; | ||
| 98 | t[0x02] = b'!'; | ||
| 99 | t[0x03] = b'@'; | ||
| 100 | t[0x04] = b'#'; | ||
| 101 | t[0x05] = b'$'; | ||
| 102 | t[0x06] = b'%'; | ||
| 103 | t[0x07] = b'^'; | ||
| 104 | t[0x08] = b'&'; | ||
| 105 | t[0x09] = b'*'; | ||
| 106 | t[0x0A] = b'('; | ||
| 107 | t[0x0B] = b')'; | ||
| 108 | t[0x0C] = b'_'; | ||
| 109 | t[0x0D] = b'+'; | ||
| 110 | t[0x10] = b'Q'; | ||
| 111 | t[0x11] = b'W'; | ||
| 112 | t[0x12] = b'E'; | ||
| 113 | t[0x13] = b'R'; | ||
| 114 | t[0x14] = b'T'; | ||
| 115 | t[0x15] = b'Y'; | ||
| 116 | t[0x16] = b'U'; | ||
| 117 | t[0x17] = b'I'; | ||
| 118 | t[0x18] = b'O'; | ||
| 119 | t[0x19] = b'P'; | ||
| 120 | t[0x1A] = b'{'; | ||
| 121 | t[0x1B] = b'}'; | ||
| 122 | t[0x1E] = b'A'; | ||
| 123 | t[0x1F] = b'S'; | ||
| 124 | t[0x20] = b'D'; | ||
| 125 | t[0x21] = b'F'; | ||
| 126 | t[0x22] = b'G'; | ||
| 127 | t[0x23] = b'H'; | ||
| 128 | t[0x24] = b'J'; | ||
| 129 | t[0x25] = b'K'; | ||
| 130 | t[0x26] = b'L'; | ||
| 131 | t[0x27] = b':'; | ||
| 132 | t[0x28] = b'"'; | ||
| 133 | t[0x29] = b'~'; | ||
| 134 | t[0x2B] = b'|'; | ||
| 135 | t[0x2C] = b'Z'; | ||
| 136 | t[0x2D] = b'X'; | ||
| 137 | t[0x2E] = b'C'; | ||
| 138 | t[0x2F] = b'V'; | ||
| 139 | t[0x30] = b'B'; | ||
| 140 | t[0x31] = b'N'; | ||
| 141 | t[0x32] = b'M'; | ||
| 142 | t[0x33] = b'<'; | ||
| 143 | t[0x34] = b'>'; | ||
| 144 | t[0x35] = b'?'; | ||
| 145 | t[0x37] = b'*'; | ||
| 146 | t[0x39] = b' '; | ||
| 147 | t | ||
| 148 | }; | ||
| 149 | |||
| 150 | /// Internal keyboard state | ||
| 151 | struct KbdState { | ||
| 152 | modifiers: u8, | ||
| 153 | got_e0: bool, | ||
| 154 | } | ||
| 155 | |||
| 156 | static mut KBD: KbdState = KbdState { | ||
| 157 | modifiers: 0, | ||
| 158 | got_e0: false, | ||
| 159 | }; | ||
| 160 | |||
| 161 | /// Initialise the PS/2 port for the keyboard — just drain stale bytes. | ||
| 162 | pub fn init() { | ||
| 163 | drain_input(); | ||
| 164 | } | ||
| 165 | |||
| 166 | fn drain_input() { | ||
| 167 | unsafe { | ||
| 168 | loop { | ||
| 169 | let status: u8; | ||
| 170 | core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, | ||
| 171 | options(nostack, preserves_flags)); | ||
| 172 | if status & STAT_OUTPUT_FULL == 0 { | ||
| 173 | break; | ||
| 174 | } | ||
| 175 | let _data: u8; | ||
| 176 | core::arch::asm!("in al, dx", out("al") _data, in("dx") DATA_PORT, | ||
| 177 | options(nostack, preserves_flags)); | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | /// Poll for a key event. Blocks until a key is pressed. | ||
| 183 | pub fn poll_key() -> KeyEvent { | ||
| 184 | loop { | ||
| 185 | let byte = read_data(); | ||
| 186 | if let Some(ev) = process_byte(byte) { | ||
| 187 | return ev; | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | /// Non-blocking poll. | ||
| 193 | pub fn try_poll_key() -> Option<KeyEvent> { | ||
| 194 | unsafe { | ||
| 195 | let status: u8; | ||
| 196 | core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, | ||
| 197 | options(nostack, preserves_flags)); | ||
| 198 | if status & STAT_OUTPUT_FULL == 0 { | ||
| 199 | return None; | ||
| 200 | } | ||
| 201 | let data: u8; | ||
| 202 | core::arch::asm!("in al, dx", out("al") data, in("dx") DATA_PORT, | ||
| 203 | options(nostack, preserves_flags)); | ||
| 204 | process_byte(data) | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | /// Read a byte from the keyboard data port (blocking). | ||
| 209 | fn read_data() -> u8 { | ||
| 210 | unsafe { | ||
| 211 | loop { | ||
| 212 | let status: u8; | ||
| 213 | core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, | ||
| 214 | options(nostack, preserves_flags)); | ||
| 215 | if status & STAT_OUTPUT_FULL != 0 { | ||
| 216 | let data: u8; | ||
| 217 | core::arch::asm!("in al, dx", out("al") data, in("dx") DATA_PORT, | ||
| 218 | options(nostack, preserves_flags)); | ||
| 219 | return data; | ||
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | /// Process a single scancode byte. Returns Some(KeyEvent) when a visible | ||
| 226 | /// event happens; returns None for modifier changes, releases, and prefixes. | ||
| 227 | fn process_byte(byte: u8) -> Option<KeyEvent> { | ||
| 228 | let state = unsafe { &mut KBD }; | ||
| 229 | |||
| 230 | if byte == 0xE0 { | ||
| 231 | state.got_e0 = true; | ||
| 232 | return None; | ||
| 233 | } | ||
| 234 | |||
| 235 | if state.got_e0 { | ||
| 236 | state.got_e0 = false; | ||
| 237 | return process_e0(byte); | ||
| 238 | } | ||
| 239 | |||
| 240 | // Break code (key release) — bit 7 set | ||
| 241 | if byte & 0x80 != 0 { | ||
| 242 | update_modifiers(byte & 0x7F, false, false); | ||
| 243 | return None; | ||
| 244 | } | ||
| 245 | |||
| 246 | // Make code (key press) | ||
| 247 | update_modifiers(byte, true, false); | ||
| 248 | |||
| 249 | match byte { | ||
| 250 | 0x01 => Some(KeyEvent::Escape), | ||
| 251 | 0x0E => Some(KeyEvent::Backspace), | ||
| 252 | 0x0F => Some(KeyEvent::Tab), | ||
| 253 | 0x1C => Some(KeyEvent::Enter), | ||
| 254 | 0x37 => Some(KeyEvent::Char('*')), | ||
| 255 | 0x3B..=0x44 => Some(KeyEvent::F(byte - 0x3B + 1)), | ||
| 256 | _ => { | ||
| 257 | let shifted = (state.modifiers & (MOD_LSHIFT | MOD_RSHIFT)) != 0; | ||
| 258 | // CapsLock inverts case for letters | ||
| 259 | let caps = state.modifiers & MOD_CAPS != 0; | ||
| 260 | let table = if shifted { SCAN_SHIFT } else { SCAN_NORMAL }; | ||
| 261 | let mut ch = table[byte as usize]; | ||
| 262 | // Handle CapsLock for letters | ||
| 263 | if caps && ch.is_ascii_alphabetic() { | ||
| 264 | // Swap case: if table gives lowercase, make uppercase; etc. | ||
| 265 | // Since normal table has lowercase, shift table has uppercase: | ||
| 266 | ch = if shifted { | ||
| 267 | ch.to_ascii_lowercase() | ||
| 268 | } else { | ||
| 269 | ch.to_ascii_uppercase() | ||
| 270 | }; | ||
| 271 | } | ||
| 272 | if ch != 0 { | ||
| 273 | Some(KeyEvent::Char(ch as char)) | ||
| 274 | } else { | ||
| 275 | Some(KeyEvent::Unknown(byte)) | ||
| 276 | } | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | /// Process an E0-prefixed scancode. | ||
| 282 | fn process_e0(byte: u8) -> Option<KeyEvent> { | ||
| 283 | if byte & 0x80 != 0 { | ||
| 284 | update_modifiers(byte & 0x7F, false, true); | ||
| 285 | return None; | ||
| 286 | } | ||
| 287 | |||
| 288 | update_modifiers(byte, true, true); | ||
| 289 | |||
| 290 | match byte { | ||
| 291 | 0x48 => Some(KeyEvent::ArrowUp), | ||
| 292 | 0x50 => Some(KeyEvent::ArrowDown), | ||
| 293 | 0x4B => Some(KeyEvent::ArrowLeft), | ||
| 294 | 0x4D => Some(KeyEvent::ArrowRight), | ||
| 295 | 0x47 => Some(KeyEvent::Home), | ||
| 296 | 0x4F => Some(KeyEvent::End), | ||
| 297 | 0x49 => Some(KeyEvent::PageUp), | ||
| 298 | 0x51 => Some(KeyEvent::PageDown), | ||
| 299 | 0x53 => Some(KeyEvent::Delete), | ||
| 300 | 0x35 => Some(KeyEvent::Char('/')), | ||
| 301 | _ => Some(KeyEvent::Unknown(byte)), | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | fn update_modifiers(scancode: u8, pressed: bool, e0: bool) { | ||
| 306 | let state = unsafe { &mut KBD }; | ||
| 307 | match (scancode, e0) { | ||
| 308 | (0x2A, false) => set_mod(state, MOD_LSHIFT, pressed), | ||
| 309 | (0x36, false) => set_mod(state, MOD_RSHIFT, pressed), | ||
| 310 | (0x1D, false) => set_mod(state, MOD_LCTRL, pressed), | ||
| 311 | (0x1D, true) => set_mod(state, MOD_RCTRL, pressed), | ||
| 312 | (0x38, false) => set_mod(state, MOD_LALT, pressed), | ||
| 313 | (0x38, true) => set_mod(state, MOD_RALT, pressed), | ||
| 314 | (0x3A, false) => { | ||
| 315 | if pressed { | ||
| 316 | state.modifiers ^= MOD_CAPS; | ||
| 317 | } | ||
| 318 | } | ||
| 319 | _ => {} | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | fn set_mod(state: &mut KbdState, bit: u8, pressed: bool) { | ||
| 324 | if pressed { | ||
| 325 | state.modifiers |= bit; | ||
| 326 | } else { | ||
| 327 | state.modifiers &= !bit; | ||
| 328 | } | ||
| 329 | } | ||
