summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-07-31 02:40:18 +0200
committersalmonik <salmonik@tuta.io>2026-07-31 02:40:18 +0200
commit4b48b6938d91a91d7b1dd242991258fe47e284f0 (patch)
treed0d32321e9384370b72b3bda4105daaea6853196
parentinterrupts: IDT, PIC remap, PIT timer, PS/2 keyboard IRQ (diff)
downloadrs-dos-4b48b6938d91a91d7b1dd242991258fe47e284f0.tar.gz
rs-dos-4b48b6938d91a91d7b1dd242991258fe47e284f0.zip
fix: ISR dispatch calling convention + PS/2 interrupt enable
Root cause: x86_64-unknown-uefi uses the Microsoft x64 calling convention where the first argument is passed in rcx, not rdi. The ISR stub was setting rdi = rsp, but isr_dispatch() read the frame pointer from rcx, getting garbage → triple fault. Also: - Enable PS/2 keyboard IRQ via controller config byte (bit 0). - Expose read_scancode() and TICKS from interrupt module. - ps2::poll_key() uses 'sti; hlt' race-free sleep pattern.
Diffstat (limited to '')
-rw-r--r--src/interrupts.rs96
-rw-r--r--src/ps2.rs64
2 files changed, 112 insertions, 48 deletions
diff --git a/src/interrupts.rs b/src/interrupts.rs
index 204d3d7..90d5bbb 100644
--- a/src/interrupts.rs
+++ b/src/interrupts.rs
@@ -12,13 +12,13 @@ use core::sync::atomic::{AtomicU16, Ordering};
12#[derive(Copy, Clone)] 12#[derive(Copy, Clone)]
13#[repr(C, packed)] 13#[repr(C, packed)]
14struct IdtEntry { 14struct IdtEntry {
15 offset_lo: u16, 15 offset_lo: u16,
16 selector: u16, 16 selector: u16,
17 ist: u8, 17 ist: u8,
18 flags: u8, 18 flags: u8,
19 offset_mid: u16, 19 offset_mid: u16,
20 offset_hi: u32, 20 offset_hi: u32,
21 _reserved: u32, 21 _reserved: u32,
22} 22}
23 23
24/// Fill one IDT entry at the given offset (in bytes). 24/// Fill one IDT entry at the given offset (in bytes).
@@ -27,13 +27,13 @@ unsafe fn idt_set_entry(idt: *mut u8, vec: u8, handler: u64, selector: u16, flag
27 let off = (vec as usize) * 16; 27 let off = (vec as usize) * 16;
28 let p = idt.add(off) as *mut IdtEntry; 28 let p = idt.add(off) as *mut IdtEntry;
29 p.write(IdtEntry { 29 p.write(IdtEntry {
30 offset_lo: handler as u16, 30 offset_lo: handler as u16,
31 selector, 31 selector,
32 ist: 0, 32 ist: 0,
33 flags: flags | 0x80, // bit 7 = present 33 flags: flags | 0x80, // bit 7 = present
34 offset_mid: (handler >> 16) as u16, 34 offset_mid: (handler >> 16) as u16,
35 offset_hi: (handler >> 32) as u32, 35 offset_hi: (handler >> 32) as u32,
36 _reserved: 0, 36 _reserved: 0,
37 }); 37 });
38 } 38 }
39} 39}
@@ -46,9 +46,9 @@ static mut IDTR: [u16; 5] = [0; 5]; // 2-byte limit + 4-byte base (48-bit pseudo
46// PIC (8259A) 46// PIC (8259A)
47// --------------------------------------------------------------------------- 47// ---------------------------------------------------------------------------
48 48
49const PIC1_CMD: u16 = 0x20; 49const PIC1_CMD: u16 = 0x20;
50const PIC1_DATA: u16 = 0x21; 50const PIC1_DATA: u16 = 0x21;
51const PIC2_CMD: u16 = 0xA0; 51const PIC2_CMD: u16 = 0xA0;
52const PIC2_DATA: u16 = 0xA1; 52const PIC2_DATA: u16 = 0xA1;
53 53
54const PIC_EOI: u8 = 0x20; 54const PIC_EOI: u8 = 0x20;
@@ -89,8 +89,8 @@ fn eoi(irq: u8) {
89// PIT timer (channel 0, ~100 Hz) 89// PIT timer (channel 0, ~100 Hz)
90// --------------------------------------------------------------------------- 90// ---------------------------------------------------------------------------
91 91
92const PIT_CMD: u16 = 0x43; 92const PIT_CMD: u16 = 0x43;
93const PIT_CH0: u16 = 0x40; 93const PIT_CH0: u16 = 0x40;
94const PIT_FREQ: u64 = 1_193_182; 94const PIT_FREQ: u64 = 1_193_182;
95 95
96/// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish). 96/// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish).
@@ -177,7 +177,9 @@ impl KeyBuf {
177 if next.wrapping_sub(h) as u16 > 255 { 177 if next.wrapping_sub(h) as u16 > 255 {
178 return; // drop — ring is full 178 return; // drop — ring is full
179 } 179 }
180 unsafe { *self.buf[t as usize & 255].get() = byte; } 180 unsafe {
181 *self.buf[t as usize & 255].get() = byte;
182 }
181 // Ensure byte is globally visible before incrementing tail. 183 // Ensure byte is globally visible before incrementing tail.
182 self.tail.store(next, Ordering::Release); 184 self.tail.store(next, Ordering::Release);
183 } 185 }
@@ -206,8 +208,8 @@ core::arch::global_asm!(
206 ".globl isr_stub_32", 208 ".globl isr_stub_32",
207 ".p2align 4", 209 ".p2align 4",
208 "isr_stub_32:", 210 "isr_stub_32:",
209 " push 0", // error code (non-exception) 211 " push 0", // error code (non-exception)
210 " push 32", // vector 212 " push 32", // vector
211 " jmp isr_common", 213 " jmp isr_common",
212 "", 214 "",
213 ".globl isr_stub_33", 215 ".globl isr_stub_33",
@@ -243,7 +245,7 @@ core::arch::global_asm!(
243 " push r14", 245 " push r14",
244 " push r15", 246 " push r15",
245 "", 247 "",
246 " mov rdi, rsp", // first arg: pointer to saved registers 248 " mov rcx, rsp", // first arg (rcx = MS x64 ABI)
247 " call isr_dispatch", 249 " call isr_dispatch",
248 "", 250 "",
249 " pop r15", 251 " pop r15",
@@ -261,36 +263,50 @@ core::arch::global_asm!(
261 " pop rdx", 263 " pop rdx",
262 " pop rcx", 264 " pop rcx",
263 " pop rax", 265 " pop rax",
264 " add rsp, 16", // pop vector + error code 266 " add rsp, 16", // pop vector + error code
265 " iretq", 267 " iretq",
266); 268);
267 269
268/// Saved register frame (as pushed by `isr_common`). 270/// Saved register frame (as pushed by `isr_common`).
269#[repr(C)] 271#[repr(C)]
270struct IntFrame { 272struct IntFrame {
271 r15: u64, r14: u64, r13: u64, r12: u64, 273 r15: u64,
272 r11: u64, r10: u64, r9: u64, r8: u64, 274 r14: u64,
273 rdi: u64, rsi: u64, rbp: u64, rbx: u64, 275 r13: u64,
274 rdx: u64, rcx: u64, rax: u64, 276 r12: u64,
275 vector: u64, 277 r11: u64,
276 error: u64, 278 r10: u64,
277 rip: u64, 279 r9: u64,
278 cs: u64, 280 r8: u64,
279 rflags: u64, 281 rdi: u64,
282 rsi: u64,
283 rbp: u64,
284 rbx: u64,
285 rdx: u64,
286 rcx: u64,
287 rax: u64,
288 vector: u64,
289 error: u64,
290 rip: u64,
291 cs: u64,
292 rflags: u64,
280} 293}
281 294
282/// Called from the asm common handler — dispatches to the right Rust handler. 295/// Called from the asm common handler — dispatches to the right Rust handler.
283#[no_mangle] 296#[no_mangle]
284extern "C" fn isr_dispatch(frame: &IntFrame) { 297unsafe extern "C" fn isr_dispatch(frame: &IntFrame) {
285 match frame.vector { 298 unsafe {
286 32 => timer_irq(), 299 match frame.vector {
287 33 => keyboard_irq(), 300 32 => eoi(0),
288 0xFF => { /* spurious — ignore */ } 301 33 => {
289 other => { 302 let byte: u8;
290 // Log unexpected interrupt on serial (but don't halt — might be benign). 303 core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16,
291 crate::serial::write_str("intr: unhandled vector "); 304 options(nostack, preserves_flags));
292 crate::serial::put_byte(other as u8); 305 // Push scancode to ring buffer (called with interrupts disabled).
293 crate::serial::put_byte(b'\n'); 306 KEYBUF.push(byte);
307 eoi(1);
308 }
309 _ => {}
294 } 310 }
295 } 311 }
296} 312}
@@ -346,7 +362,7 @@ pub fn init() {
346 362
347 // Build the IDTR pseudo-descriptor. 363 // Build the IDTR pseudo-descriptor.
348 IDTR = [ 364 IDTR = [
349 (256u16 * 16 - 1) as u16, // limit = 256*16 - 1 365 (256u16 * 16 - 1) as u16, // limit = 256*16 - 1
350 idt_ptr as u16, // base lo 366 idt_ptr as u16, // base lo
351 (idt_ptr as u64 >> 16) as u16, // base mid 367 (idt_ptr as u64 >> 16) as u16, // base mid
352 (idt_ptr as u64 >> 32) as u16, // base hi 368 (idt_ptr as u64 >> 32) as u16, // base hi
diff --git a/src/ps2.rs b/src/ps2.rs
index 3a42c1a..4ea8b7a 100644
--- a/src/ps2.rs
+++ b/src/ps2.rs
@@ -158,9 +158,64 @@ static mut KBD: KbdState = KbdState {
158 got_e0: false, 158 got_e0: false,
159}; 159};
160 160
161/// Initialise the PS/2 port for the keyboard — just drain stale bytes. 161/// Initialise the PS/2 port: drain stale bytes + enable keyboard interrupt.
162pub fn init() { 162pub fn init() {
163 drain_input(); 163 drain_input();
164
165 // Enable the keyboard interrupt on the PS/2 controller.
166 // Read Configuration Byte (command 0x20), set bit 0 (IRQ1 enable),
167 // write back (command 0x60).
168 unsafe {
169 // Tell the keyboard controller we want to read the config byte.
170 // Need to wait for the controller to be ready first.
171 wait_write();
172 core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x20u8,
173 options(nostack, preserves_flags));
174 wait_read();
175 let mut cfg: u8;
176 core::arch::asm!("in al, dx", out("al") cfg, in("dx") 0x60u16,
177 options(nostack, preserves_flags));
178
179 cfg |= 0x01; // enable keyboard interrupt
180
181 // Write config byte back
182 wait_write();
183 core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x60u8,
184 options(nostack, preserves_flags));
185 wait_write();
186 core::arch::asm!("out dx, al", in("dx") 0x60u16, in("al") cfg,
187 options(nostack, preserves_flags));
188 }
189}
190
191/// Wait until the PS/2 controller is ready to receive a command.
192fn wait_write() {
193 unsafe {
194 loop {
195 let status: u8;
196 core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16,
197 options(nostack, preserves_flags));
198 if status & 0x02 == 0 {
199 // Input buffer empty — controller ready to receive
200 break;
201 }
202 }
203 }
204}
205
206/// Wait until the PS/2 controller has data available to read.
207fn wait_read() {
208 unsafe {
209 loop {
210 let status: u8;
211 core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16,
212 options(nostack, preserves_flags));
213 if status & 0x01 != 0 {
214 // Output buffer full — data ready to read
215 break;
216 }
217 }
218 }
164} 219}
165 220
166fn drain_input() { 221fn drain_input() {
@@ -184,18 +239,11 @@ fn drain_input() {
184/// this function pops and decodes them. 239/// this function pops and decodes them.
185pub fn poll_key() -> KeyEvent { 240pub fn poll_key() -> KeyEvent {
186 loop { 241 loop {
187 // Disable interrupts so the buffer-empty → hlt window is atomic.
188 unsafe { core::arch::asm!("cli", options(nostack, preserves_flags)); }
189
190 if let Some(byte) = crate::interrupts::read_scancode() { 242 if let Some(byte) = crate::interrupts::read_scancode() {
191 unsafe { core::arch::asm!("sti", options(nostack, preserves_flags)); }
192 if let Some(ev) = process_byte(byte) { 243 if let Some(ev) = process_byte(byte) {
193 return ev; 244 return ev;
194 } 245 }
195 } else { 246 } else {
196 // Re-enable and halt. If a keyboard IRQ arrived between the
197 // buffer check and `sti`, the CPU sees a pending interrupt
198 // and `hlt` returns immediately.
199 unsafe { 247 unsafe {
200 core::arch::asm!("sti; hlt", options(nostack, preserves_flags)); 248 core::arch::asm!("sti; hlt", options(nostack, preserves_flags));
201 } 249 }