1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
|
/// PS/2 keyboard driver — polling, scancode set 1, US QWERTY layout.
// Port addresses
const DATA_PORT: u16 = 0x60;
const STATUS_PORT: u16 = 0x64;
// Status register bits
const STAT_OUTPUT_FULL: u8 = 0x01;
// Modifier bit constants
const MOD_LSHIFT: u8 = 0x01;
const MOD_RSHIFT: u8 = 0x02;
const MOD_LCTRL: u8 = 0x04;
const MOD_RCTRL: u8 = 0x08;
const MOD_LALT: u8 = 0x10;
const MOD_RALT: u8 = 0x20;
const MOD_CAPS: u8 = 0x40;
/// A decoded key event.
#[derive(Clone, Copy, Debug)]
pub enum KeyEvent {
Char(char),
Enter,
Backspace,
Tab,
Escape,
/// Ctrl + key (e.g. Ctrl('c') = ^C, Ctrl('s') = ^S).
Ctrl(char),
/// Ctrl + arrow (0=up, 1=down, 2=left, 3=right) — word/page nav.
CtrlArrow(u8),
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Home,
End,
PageUp,
PageDown,
Delete,
F(u8),
Unknown(u8),
}
// ----- scancode-set-1 translation tables (US QWERTY) -----
/// Normal (unshifted) scancode → ASCII mapping. 0 = unmapped.
static SCAN_NORMAL: [u8; 128] = {
let mut t = [0u8; 128];
t[0x02] = b'1';
t[0x03] = b'2';
t[0x04] = b'3';
t[0x05] = b'4';
t[0x06] = b'5';
t[0x07] = b'6';
t[0x08] = b'7';
t[0x09] = b'8';
t[0x0A] = b'9';
t[0x0B] = b'0';
t[0x0C] = b'-';
t[0x0D] = b'=';
t[0x10] = b'q';
t[0x11] = b'w';
t[0x12] = b'e';
t[0x13] = b'r';
t[0x14] = b't';
t[0x15] = b'y';
t[0x16] = b'u';
t[0x17] = b'i';
t[0x18] = b'o';
t[0x19] = b'p';
t[0x1A] = b'[';
t[0x1B] = b']';
t[0x1E] = b'a';
t[0x1F] = b's';
t[0x20] = b'd';
t[0x21] = b'f';
t[0x22] = b'g';
t[0x23] = b'h';
t[0x24] = b'j';
t[0x25] = b'k';
t[0x26] = b'l';
t[0x27] = b';';
t[0x28] = b'\'';
t[0x29] = b'`';
t[0x2B] = b'\\';
t[0x2C] = b'z';
t[0x2D] = b'x';
t[0x2E] = b'c';
t[0x2F] = b'v';
t[0x30] = b'b';
t[0x31] = b'n';
t[0x32] = b'm';
t[0x33] = b',';
t[0x34] = b'.';
t[0x35] = b'/';
t[0x37] = b'*';
t[0x39] = b' ';
t
};
/// Shifted scancode → ASCII.
static SCAN_SHIFT: [u8; 128] = {
let mut t = [0u8; 128];
t[0x02] = b'!';
t[0x03] = b'@';
t[0x04] = b'#';
t[0x05] = b'$';
t[0x06] = b'%';
t[0x07] = b'^';
t[0x08] = b'&';
t[0x09] = b'*';
t[0x0A] = b'(';
t[0x0B] = b')';
t[0x0C] = b'_';
t[0x0D] = b'+';
t[0x10] = b'Q';
t[0x11] = b'W';
t[0x12] = b'E';
t[0x13] = b'R';
t[0x14] = b'T';
t[0x15] = b'Y';
t[0x16] = b'U';
t[0x17] = b'I';
t[0x18] = b'O';
t[0x19] = b'P';
t[0x1A] = b'{';
t[0x1B] = b'}';
t[0x1E] = b'A';
t[0x1F] = b'S';
t[0x20] = b'D';
t[0x21] = b'F';
t[0x22] = b'G';
t[0x23] = b'H';
t[0x24] = b'J';
t[0x25] = b'K';
t[0x26] = b'L';
t[0x27] = b':';
t[0x28] = b'"';
t[0x29] = b'~';
t[0x2B] = b'|';
t[0x2C] = b'Z';
t[0x2D] = b'X';
t[0x2E] = b'C';
t[0x2F] = b'V';
t[0x30] = b'B';
t[0x31] = b'N';
t[0x32] = b'M';
t[0x33] = b'<';
t[0x34] = b'>';
t[0x35] = b'?';
t[0x37] = b'*';
t[0x39] = b' ';
t
};
/// Internal keyboard state
struct KbdState {
modifiers: u8,
got_e0: bool,
}
static mut KBD: KbdState = KbdState {
modifiers: 0,
got_e0: false,
};
/// Initialise the PS/2 port: drain stale bytes + enable keyboard interrupt.
pub fn init() {
drain_input();
// Enable the keyboard interrupt on the PS/2 controller.
// Read Configuration Byte (command 0x20), set bit 0 (IRQ1 enable),
// write back (command 0x60). Every wait is bounded; a wedged
// controller bails out instead of hanging the boot.
unsafe {
// Tell the keyboard controller we want to read the config byte.
// Need to wait for the controller to be ready first.
if !wait_write() {
return;
}
core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x20u8,
options(nostack, preserves_flags));
if !wait_read() {
return;
}
let mut cfg: u8;
core::arch::asm!("in al, dx", out("al") cfg, in("dx") 0x60u16,
options(nostack, preserves_flags));
cfg |= 0x01; // enable keyboard interrupt
// Write config byte back
if !wait_write() {
return;
}
core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x60u8,
options(nostack, preserves_flags));
if !wait_write() {
return;
}
core::arch::asm!("out dx, al", in("dx") 0x60u16, in("al") cfg,
options(nostack, preserves_flags));
}
}
/// Wait until the PS/2 controller is ready to receive a command.
/// Bounded: a wedged or missing 8042 must not hang the boot (the classic
/// real-hardware freeze — the controller reports busy forever on some
/// machines). Returns false on timeout.
fn wait_write() -> bool {
unsafe {
for _ in 0..1_000_000 {
let status: u8;
core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16,
options(nostack, preserves_flags));
if status & 0x02 == 0 {
// Input buffer empty — controller ready to receive
return true;
}
}
}
false
}
/// Wait until the PS/2 controller has data available to read.
/// Bounded like `wait_write`.
fn wait_read() -> bool {
unsafe {
for _ in 0..1_000_000 {
let status: u8;
core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16,
options(nostack, preserves_flags));
if status & 0x01 != 0 {
// Output buffer full — data ready to read
return true;
}
}
}
false
}
/// Drain any pending bytes. Bounded so a stuck output-full flag (or a
/// controller that keeps raising it with no data) cannot hang the boot.
fn drain_input() {
unsafe {
for _ in 0..32 {
let status: u8;
core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT,
options(nostack, preserves_flags));
if status & STAT_OUTPUT_FULL == 0 {
break;
}
let _data: u8;
core::arch::asm!("in al, dx", out("al") _data, in("dx") DATA_PORT,
options(nostack, preserves_flags));
}
}
}
/// Poll for a key event. Blocks via `hlt` until a key is pressed.
/// The keyboard interrupt handler pushes raw scancodes to a ring buffer;
/// this function pops and decodes them.
pub fn poll_key() -> KeyEvent {
loop {
if let Some(byte) = crate::interrupts::read_scancode() {
if let Some(ev) = process_byte(byte) {
return ev;
}
} else {
unsafe {
core::arch::asm!("sti; hlt", options(nostack, preserves_flags));
}
}
}
}
/// Non-blocking poll — read from the interrupt ring buffer if available.
pub fn try_poll_key() -> Option<KeyEvent> {
let byte = crate::interrupts::read_scancode()?;
process_byte(byte)
}
/// Process a single scancode byte. Returns Some(KeyEvent) when a visible
/// event happens; returns None for modifier changes, releases, and prefixes.
fn kbd() -> &'static mut KbdState {
unsafe { &mut *core::ptr::addr_of_mut!(KBD) }
}
/// Feed one scancode-set-1 byte into the shared decoder state machine.
/// Used by the PS/2 IRQ path (raw bytes) AND the USB HID keyboard driver
/// (which translates HID usage codes to scancode-set-1 make/break bytes so
/// the same shift/ctrl/caps decoding applies). Returns a KeyEvent on a
/// visible press; releases and modifier-only changes return None.
pub(crate) fn feed_scancode(byte: u8) -> Option<KeyEvent> {
process_byte(byte)
}
fn process_byte(byte: u8) -> Option<KeyEvent> {
let state = kbd();
if byte == 0xE0 {
state.got_e0 = true;
return None;
}
if state.got_e0 {
state.got_e0 = false;
return process_e0(byte);
}
// Break code (key release) — bit 7 set
if byte & 0x80 != 0 {
update_modifiers(byte & 0x7F, false, false);
return None;
}
// Make code (key press)
update_modifiers(byte, true, false);
match byte {
0x01 => Some(KeyEvent::Escape),
0x0E => Some(KeyEvent::Backspace),
0x0F => Some(KeyEvent::Tab),
0x1C => Some(KeyEvent::Enter),
0x37 => Some(KeyEvent::Char('*')),
0x3B..=0x44 => Some(KeyEvent::F(byte - 0x3B + 1)),
_ => {
let shifted = (state.modifiers & (MOD_LSHIFT | MOD_RSHIFT)) != 0;
// CapsLock inverts case for letters
let caps = state.modifiers & MOD_CAPS != 0;
let table = if shifted { SCAN_SHIFT } else { SCAN_NORMAL };
let mut ch = table[byte as usize];
// Handle CapsLock for letters
if caps && ch.is_ascii_alphabetic() {
// Swap case: if table gives lowercase, make uppercase; etc.
// Since normal table has lowercase, shift table has uppercase:
ch = if shifted {
ch.to_ascii_lowercase()
} else {
ch.to_ascii_uppercase()
};
}
// Ctrl+key — the terminal control-byte protocol (^C, ^D, ^S, ^X, …).
let ctrl = state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0;
if ctrl {
let lc = ch.to_ascii_lowercase();
return if lc.is_ascii_graphic() {
Some(KeyEvent::Ctrl(lc as char))
} else {
None // swallow non-graphic Ctrl combos
};
}
if ch != 0 {
Some(KeyEvent::Char(ch as char))
} else {
Some(KeyEvent::Unknown(byte))
}
}
}
}
/// Process an E0-prefixed scancode.
fn process_e0(byte: u8) -> Option<KeyEvent> {
if byte & 0x80 != 0 {
update_modifiers(byte & 0x7F, false, true);
return None;
}
update_modifiers(byte, true, true);
match byte {
0x48 => Some(if ctrl_held() {
KeyEvent::CtrlArrow(0)
} else {
KeyEvent::ArrowUp
}),
0x50 => Some(if ctrl_held() {
KeyEvent::CtrlArrow(1)
} else {
KeyEvent::ArrowDown
}),
0x4B => Some(if ctrl_held() {
KeyEvent::CtrlArrow(2)
} else {
KeyEvent::ArrowLeft
}),
0x4D => Some(if ctrl_held() {
KeyEvent::CtrlArrow(3)
} else {
KeyEvent::ArrowRight
}),
0x47 => Some(KeyEvent::Home),
0x4F => Some(KeyEvent::End),
0x49 => Some(KeyEvent::PageUp),
0x51 => Some(KeyEvent::PageDown),
0x53 => Some(KeyEvent::Delete),
0x35 => Some(KeyEvent::Char('/')),
_ => Some(KeyEvent::Unknown(byte)),
}
}
fn update_modifiers(scancode: u8, pressed: bool, e0: bool) {
let state = kbd();
match (scancode, e0) {
(0x2A, false) => set_mod(state, MOD_LSHIFT, pressed),
(0x36, false) => set_mod(state, MOD_RSHIFT, pressed),
(0x1D, false) => set_mod(state, MOD_LCTRL, pressed),
(0x1D, true) => set_mod(state, MOD_RCTRL, pressed),
(0x38, false) => set_mod(state, MOD_LALT, pressed),
(0x38, true) => set_mod(state, MOD_RALT, pressed),
(0x3A, false) if pressed => {
state.modifiers ^= MOD_CAPS;
}
_ => {}
}
}
fn set_mod(state: &mut KbdState, bit: u8, pressed: bool) {
if pressed {
state.modifiers |= bit;
} else {
state.modifiers &= !bit;
}
}
/// Is either Ctrl key held?
fn ctrl_held() -> bool {
let state = kbd();
state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0
}
// ---------------------------------------------------------------------------
// PS/2 mouse — auxiliary channel, IRQ12, stream mode (3-byte packets)
// ---------------------------------------------------------------------------
// PS/2 controller commands (written to port 0x64).
const CMD_AUX_ENABLE: u8 = 0xA8; // enable the auxiliary (second) port
const CMD_READ_CFG: u8 = 0x20; // read Configuration Byte → port 0x60
const CMD_WRITE_CFG: u8 = 0x60; // write Configuration Byte ← port 0x60
const CMD_SEND_TO_AUX: u8 = 0xD4; // next byte on 0x60 goes to the mouse
// Configuration Byte bits (as sent via CMD_READ_CFG / CMD_WRITE_CFG).
// Bit 0 (IRQ1/keyboard) is set by `init`; this driver preserves it by
// reading the byte and only ORing in the two mouse bits below.
const CFG_AUX_IRQ: u8 = 0x02; // bit 1 — IRQ12 (mouse)
const CFG_AUX_CLOCK: u8 = 0x20; // bit 5 — auxiliary port clock enable
// Mouse device commands (sent via CMD_SEND_TO_AUX).
const AUX_SET_DEFAULTS: u8 = 0xF6; // 100 Hz sample, 4 counts/mm, 1:1
const AUX_ENABLE: u8 = 0xF4; // start data reporting (stream mode)
const AUX_ACK: u8 = 0xFA;
/// A decoded PS/2 mouse event (one 3-byte packet).
///
/// Movement is **screen-relative**: `dx` positive = right, `dy` positive =
/// down. (The PS/2 wire format is Y-up-positive; the driver negates it so
/// consumers can apply the deltas to framebuffer coordinates directly.)
#[derive(Clone, Copy, Debug)]
pub struct MouseEvent {
pub dx: i16,
pub dy: i16,
pub left: bool,
pub right: bool,
pub middle: bool,
}
/// What happened in a window-relative (GUI) mouse event.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MouseKind {
Move,
Press,
Release,
}
/// A mouse event delivered to a focused GUI window: absolute coordinates
/// relative to the window's content top-left, the current button bitmask,
/// and the action (move / press / release). Unlike `MouseEvent` (relative
/// deltas), this is what an app's drawing loop consumes.
#[derive(Clone, Copy, Debug)]
pub struct AppMouseEvent {
pub x: i32,
pub y: i32,
pub buttons: u8,
pub kind: MouseKind,
}
/// Packet assembly state — lives in main context (consumer side), fed by
/// the raw byte ring that the IRQ12 handler fills.
struct MouseState {
phase: u8, // 0 = awaiting start byte, 1 = await X, 2 = await Y
pkt: [u8; 3],
}
static mut MOUSE: MouseState = MouseState {
phase: 0,
pkt: [0; 3],
};
static MOUSE_PRESENT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
/// True once `mouse_init` found a device that ACKed its setup commands.
pub fn mouse_present() -> bool {
MOUSE_PRESENT.load(core::sync::atomic::Ordering::Relaxed)
}
/// Enable the auxiliary (mouse) channel and put the device into stream
/// mode. Returns false when no device ACKs (controller without a second
/// port, or a missing mouse) — the kernel then just runs keyboard-only.
///
/// Must run *before* `interrupts::init` unmasks IRQ12: the init handshake
/// reads the data port directly, so any stray mouse bytes would otherwise
/// be split across the ISR path mid-handshake.
pub fn mouse_init() -> bool {
drain_input();
unsafe {
// 1. Enable the auxiliary port itself.
if !wait_write() {
return false;
}
core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_AUX_ENABLE,
options(nostack, preserves_flags));
// 2. Configuration Byte: enable IRQ12 + the aux port clock. Read,
// OR the two bits, write back (preserves the keyboard IRQ bit
// that ps2::init set).
if !wait_write() {
return false;
}
core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_READ_CFG,
options(nostack, preserves_flags));
if !wait_read() {
return false;
}
let mut cfg: u8;
core::arch::asm!("in al, dx", out("al") cfg, in("dx") DATA_PORT,
options(nostack, preserves_flags));
cfg |= CFG_AUX_IRQ | CFG_AUX_CLOCK;
if !wait_write() {
return false;
}
core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_WRITE_CFG,
options(nostack, preserves_flags));
if !wait_write() {
return false;
}
core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cfg,
options(nostack, preserves_flags));
// 3. Device setup. `set defaults` normalises the sample rate /
// resolution before enabling reporting, so the packet stream
// starts at a known cadence. Both commands need a device ACK;
// a timeout here means no mouse — bail out cleanly.
if !aux_command(AUX_SET_DEFAULTS) || !aux_command(AUX_ENABLE) {
return false;
}
}
MOUSE_PRESENT.store(true, core::sync::atomic::Ordering::Relaxed);
true
}
/// Send one byte to the mouse via the 0xD4 channel and wait for its ACK.
/// The wait is bounded so a missing device cannot hang the boot.
fn aux_command(cmd: u8) -> bool {
unsafe {
if !wait_write() {
return false;
}
core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_SEND_TO_AUX,
options(nostack, preserves_flags));
if !wait_write() {
return false;
}
core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cmd,
options(nostack, preserves_flags));
for _ in 0..100_000 {
let status: u8;
core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT,
options(nostack, preserves_flags));
if status & STAT_OUTPUT_FULL != 0 {
let b: u8;
core::arch::asm!("in al, dx", out("al") b, in("dx") DATA_PORT,
options(nostack, preserves_flags));
return b == AUX_ACK;
}
}
}
false
}
/// Non-blocking: decode the next pending mouse event, if a full packet has
/// arrived. Raw bytes are drained from the IRQ ring; incomplete packets
/// stay buffered in `MouseState` until the remaining bytes land.
pub fn try_poll_mouse() -> Option<MouseEvent> {
while let Some(b) = crate::interrupts::read_mouse_byte() {
if let Some(ev) = mouse_byte(b) {
return Some(ev);
}
}
None
}
/// Feed one raw byte into the packet assembler. Byte 0 of every standard
/// PS/2 mouse packet has bit 3 set (the sync marker), so a lost byte (ring
/// overrun, init response) is detected on the next packet and the stream
/// re-syncs — the marker is only accepted as a packet start.
fn mouse_byte(b: u8) -> Option<MouseEvent> {
let m = unsafe { &mut *core::ptr::addr_of_mut!(MOUSE) };
match m.phase {
0 => {
if b & 0x08 != 0 {
m.pkt[0] = b;
m.phase = 1;
}
None
}
1 => {
m.pkt[1] = b;
m.phase = 2;
None
}
_ => {
m.pkt[2] = b;
m.phase = 0;
Some(MouseEvent {
// 9-bit signed: low 8 bits in the data byte, sign bit in
// byte 0 (bit 4 = X, bit 5 = Y).
dx: signed9(m.pkt[0], m.pkt[1], 0x10),
// PS/2 reports Y positive = up; flip to screen convention.
dy: -signed9(m.pkt[0], m.pkt[2], 0x20),
left: m.pkt[0] & 0x01 != 0,
right: m.pkt[0] & 0x02 != 0,
middle: m.pkt[0] & 0x04 != 0,
})
}
}
}
/// Sign-extend an 8-bit movement byte to 9 bits using the packet's sign
/// flag (bit `sign` of byte 0), widening to i16 for consumers.
fn signed9(b0: u8, b: u8, sign: u8) -> i16 {
let mut v = (b as i8) as i16;
if b0 & sign != 0 {
// Set the 9th bit (0xFF00 = sign-extension mask for i16).
v |= 0xFF00u16 as i16;
}
v
}
|