summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-07-31 02:28:10 +0200
committersalmonik <salmonik@tuta.io>2026-07-31 02:28:10 +0200
commitaf8d5aa4538ac71f11a5418ef94ae478868a0484 (patch)
treec235d89cbdd1486dd2239d08abc750be2e5a88d2
parentInitial commit: raisernics toy kernel (diff)
downloadrs-dos-af8d5aa4538ac71f11a5418ef94ae478868a0484.tar.gz
rs-dos-af8d5aa4538ac71f11a5418ef94ae478868a0484.zip
interrupts: IDT, PIC remap, PIT timer, PS/2 keyboard IRQ
- IDT with 256 entries, loaded via lidt (CS read from current context) - PIC 8259A remapped to vectors 32-47, timer+keyboard IRQs unmasked - PIT channel 0 at ~100 Hz (mode 2, rate generator) - PS/2 keyboard IRQ (IRQ1 → vector 33): reads port 0x60, pushes byte to lock-free SPSC ring buffer - Timer IRQ (IRQ0 → vector 32): sends EOI, increments tick counter - ps2::poll_key() now reads from ring buffer with cli/sti+hlt for race-free sleep (instead of busy-polling the data port) - build.rs: use idiomatic Result<_, Box<dyn Error>> return type - tools/qemu.sh: fix SC2207 shellcheck warning
-rw-r--r--build.rs4
-rw-r--r--src/interrupts.rs385
-rw-r--r--src/main.rs5
-rw-r--r--src/ps2.rs55
-rwxr-xr-xtools/qemu.sh2
5 files changed, 414 insertions, 37 deletions
diff --git a/build.rs b/build.rs
index 44e6344..35447e9 100644
--- a/build.rs
+++ b/build.rs
@@ -26,8 +26,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
26 return Err("not a PSF2 font".into()); 26 return Err("not a PSF2 font".into());
27 } 27 }
28 28
29 let hdr_size = u32::from_le_bytes(raw[8..12].try_into()?) as usize; 29 let hdr_size = u32::from_le_bytes(raw[8..12].try_into()?) as usize;
30 let num_glyphs = u32::from_le_bytes(raw[16..20].try_into()?) as usize; 30 let num_glyphs = u32::from_le_bytes(raw[16..20].try_into()?) as usize;
31 let bytes_per_glyph = u32::from_le_bytes(raw[20..24].try_into()?) as usize; 31 let bytes_per_glyph = u32::from_le_bytes(raw[20..24].try_into()?) as usize;
32 32
33 if num_glyphs != 256 { 33 if num_glyphs != 256 {
diff --git a/src/interrupts.rs b/src/interrupts.rs
new file mode 100644
index 0000000..204d3d7
--- /dev/null
+++ b/src/interrupts.rs
@@ -0,0 +1,385 @@
1// ---------------------------------------------------------------------------
2// Interrupt handling: IDT, PIC remap, ISR dispatch.
3// ---------------------------------------------------------------------------
4use core::arch::asm;
5use core::sync::atomic::{AtomicU16, Ordering};
6
7// ---------------------------------------------------------------------------
8// IDT entry and table
9// ---------------------------------------------------------------------------
10
11/// A single 64-bit IDT entry (16 bytes).
12#[derive(Copy, Clone)]
13#[repr(C, packed)]
14struct IdtEntry {
15 offset_lo: u16,
16 selector: u16,
17 ist: u8,
18 flags: u8,
19 offset_mid: u16,
20 offset_hi: u32,
21 _reserved: u32,
22}
23
24/// Fill one IDT entry at the given offset (in bytes).
25unsafe fn idt_set_entry(idt: *mut u8, vec: u8, handler: u64, selector: u16, flags: u8) {
26 unsafe {
27 let off = (vec as usize) * 16;
28 let p = idt.add(off) as *mut IdtEntry;
29 p.write(IdtEntry {
30 offset_lo: handler as u16,
31 selector,
32 ist: 0,
33 flags: flags | 0x80, // bit 7 = present
34 offset_mid: (handler >> 16) as u16,
35 offset_hi: (handler >> 32) as u32,
36 _reserved: 0,
37 });
38 }
39}
40
41/// The one true IDT (live in .bss, zero-initialised).
42static mut IDT: [u8; 256 * 16] = [0u8; 256 * 16];
43static mut IDTR: [u16; 5] = [0; 5]; // 2-byte limit + 4-byte base (48-bit pseudo-desc)
44
45// ---------------------------------------------------------------------------
46// PIC (8259A)
47// ---------------------------------------------------------------------------
48
49const PIC1_CMD: u16 = 0x20;
50const PIC1_DATA: u16 = 0x21;
51const PIC2_CMD: u16 = 0xA0;
52const PIC2_DATA: u16 = 0xA1;
53
54const PIC_EOI: u8 = 0x20;
55
56const ICW1: u8 = 0x11; // edge, cascade, icw4
57const ICW4: u8 = 0x01; // x86 mode
58
59/// Remap PIC IRQs 0-15 to IDT vectors 32-47, then mask all.
60unsafe fn remap_pic() {
61 unsafe {
62 asm!("out dx, al", in("dx") PIC1_CMD, in("al") ICW1, options(nostack, preserves_flags));
63 asm!("out dx, al", in("dx") PIC2_CMD, in("al") ICW1, options(nostack, preserves_flags));
64 asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0x20u8, options(nostack, preserves_flags));
65 asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0x28u8, options(nostack, preserves_flags));
66 asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0x04u8, options(nostack, preserves_flags));
67 asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0x02u8, options(nostack, preserves_flags));
68 asm!("out dx, al", in("dx") PIC1_DATA, in("al") ICW4, options(nostack, preserves_flags));
69 asm!("out dx, al", in("dx") PIC2_DATA, in("al") ICW4, options(nostack, preserves_flags));
70 asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0xFFu8, options(nostack, preserves_flags));
71 asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0xFFu8, options(nostack, preserves_flags));
72 }
73}
74
75/// Send End-Of-Interrupt to the master PIC (and slave if needed).
76fn eoi(irq: u8) {
77 unsafe {
78 // Slave IRQs are 8-15, mapped to vectors 40-47.
79 if irq >= 8 {
80 asm!("out dx, al", in("dx") PIC2_CMD, in("al") PIC_EOI,
81 options(nostack, preserves_flags));
82 }
83 asm!("out dx, al", in("dx") PIC1_CMD, in("al") PIC_EOI,
84 options(nostack, preserves_flags));
85 }
86}
87
88// ---------------------------------------------------------------------------
89// PIT timer (channel 0, ~100 Hz)
90// ---------------------------------------------------------------------------
91
92const PIT_CMD: u16 = 0x43;
93const PIT_CH0: u16 = 0x40;
94const PIT_FREQ: u64 = 1_193_182;
95
96/// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish).
97fn pit_set_rate(hz: u64) {
98 let divisor = (PIT_FREQ / hz) as u16;
99 unsafe {
100 // Mode 2, channel 0, lobyte/hibyte, binary
101 asm!("out dx, al", in("dx") PIT_CMD, in("al") 0b00_11_010_0u8,
102 options(nostack, preserves_flags));
103 asm!("out dx, al", in("dx") PIT_CH0, in("al") (divisor & 0xFF) as u8,
104 options(nostack, preserves_flags));
105 asm!("out dx, al", in("dx") PIT_CH0, in("al") (divisor >> 8) as u8,
106 options(nostack, preserves_flags));
107 }
108}
109
110// ---------------------------------------------------------------------------
111// Lock-free SPSC ring buffer for keyboard scancodes
112// ---------------------------------------------------------------------------
113
114/// Single-producer (ISR) / single-consumer (main) lock-free ring buffer.
115struct KeyBuf {
116 buf: [core::cell::UnsafeCell<u8>; 256],
117 head: AtomicU16, // next slot to read (written by consumer / main)
118 tail: AtomicU16, // next slot to write (written by producer / ISR)
119}
120
121unsafe impl Sync for KeyBuf {}
122
123impl KeyBuf {
124 const fn new() -> Self {
125 // UnsafeCell requires const initialisation in a const ctx.
126 // Build an array the long way — one cell at a time.
127 const NEW_CELL: core::cell::UnsafeCell<u8> = core::cell::UnsafeCell::new(0);
128 // `[NEW_CELL; 256]` needs Clone, which UnsafeCell doesn't have.
129 // Use a union trick or just mark the whole buffer init with zeroes.
130 // SAFETY: we initialise via the const-fn path in init().
131 Self {
132 buf: [
133 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
134 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
135 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
136 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
137 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
138 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
139 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
140 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
141 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
142 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
143 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
144 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
145 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
146 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
147 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
148 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
149 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
150 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
151 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
152 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
153 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
154 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
155 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
156 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
157 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
158 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
159 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
160 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
161 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
162 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
163 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
164 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
165 ],
166 head: AtomicU16::new(0),
167 tail: AtomicU16::new(0),
168 }
169 }
170
171 /// Push one byte — called from ISR (interrupts disabled, single-producer).
172 fn push(&self, byte: u8) {
173 let t = self.tail.load(Ordering::Relaxed);
174 let h = self.head.load(Ordering::Acquire);
175 let next = t.wrapping_add(1);
176 // Full if the distance between head and tail == capacity (256).
177 if next.wrapping_sub(h) as u16 > 255 {
178 return; // drop — ring is full
179 }
180 unsafe { *self.buf[t as usize & 255].get() = byte; }
181 // Ensure byte is globally visible before incrementing tail.
182 self.tail.store(next, Ordering::Release);
183 }
184
185 /// Pop one byte — called from main context. Returns None when empty.
186 fn pop(&self) -> Option<u8> {
187 let h = self.head.load(Ordering::Relaxed);
188 let t = self.tail.load(Ordering::Acquire);
189 if h == t {
190 return None;
191 }
192 let byte = unsafe { *self.buf[h as usize & 255].get() };
193 self.head.store(h.wrapping_add(1), Ordering::Release);
194 Some(byte)
195 }
196}
197
198static KEYBUF: KeyBuf = KeyBuf::new();
199
200// ---------------------------------------------------------------------------
201// Interrupt stubs (assembly) and dispatch
202// ---------------------------------------------------------------------------
203
204core::arch::global_asm!(
205 // ISR stubs — push vector number + error code, jump to common.
206 ".globl isr_stub_32",
207 ".p2align 4",
208 "isr_stub_32:",
209 " push 0", // error code (non-exception)
210 " push 32", // vector
211 " jmp isr_common",
212 "",
213 ".globl isr_stub_33",
214 ".p2align 4",
215 "isr_stub_33:",
216 " push 0",
217 " push 33",
218 " jmp isr_common",
219 "",
220 // Catch-all for unexpected interrupts.
221 ".globl isr_stub_spurious",
222 ".p2align 4",
223 "isr_stub_spurious:",
224 " push 0",
225 " push 0xFF",
226 " jmp isr_common",
227 "",
228 // Common handler — save all registers, call into Rust, restore, iretq.
229 "isr_common:",
230 " push rax",
231 " push rcx",
232 " push rdx",
233 " push rbx",
234 " push rbp",
235 " push rsi",
236 " push rdi",
237 " push r8",
238 " push r9",
239 " push r10",
240 " push r11",
241 " push r12",
242 " push r13",
243 " push r14",
244 " push r15",
245 "",
246 " mov rdi, rsp", // first arg: pointer to saved registers
247 " call isr_dispatch",
248 "",
249 " pop r15",
250 " pop r14",
251 " pop r13",
252 " pop r12",
253 " pop r11",
254 " pop r10",
255 " pop r9",
256 " pop r8",
257 " pop rdi",
258 " pop rsi",
259 " pop rbp",
260 " pop rbx",
261 " pop rdx",
262 " pop rcx",
263 " pop rax",
264 " add rsp, 16", // pop vector + error code
265 " iretq",
266);
267
268/// Saved register frame (as pushed by `isr_common`).
269#[repr(C)]
270struct IntFrame {
271 r15: u64, r14: u64, r13: u64, r12: u64,
272 r11: u64, r10: u64, r9: u64, r8: u64,
273 rdi: u64, rsi: u64, rbp: u64, rbx: u64,
274 rdx: u64, rcx: u64, rax: u64,
275 vector: u64,
276 error: u64,
277 rip: u64,
278 cs: u64,
279 rflags: u64,
280}
281
282/// Called from the asm common handler — dispatches to the right Rust handler.
283#[no_mangle]
284extern "C" fn isr_dispatch(frame: &IntFrame) {
285 match frame.vector {
286 32 => timer_irq(),
287 33 => keyboard_irq(),
288 0xFF => { /* spurious — ignore */ }
289 other => {
290 // Log unexpected interrupt on serial (but don't halt — might be benign).
291 crate::serial::write_str("intr: unhandled vector ");
292 crate::serial::put_byte(other as u8);
293 crate::serial::put_byte(b'\n');
294 }
295 }
296}
297
298/// Timer IRQ (PIT channel 0 ~ 100 Hz).
299fn timer_irq() {
300 eoi(0);
301}
302
303/// Keyboard IRQ — read scancode from port 0x60 and push to ring buffer.
304fn keyboard_irq() {
305 let byte: u8;
306 unsafe {
307 asm!("in al, dx", out("al") byte, in("dx") 0x60,
308 options(nostack, preserves_flags));
309 }
310 KEYBUF.push(byte);
311 eoi(1);
312}
313
314// ---------------------------------------------------------------------------
315// Public API
316// ---------------------------------------------------------------------------
317
318/// Read one scancode from the interrupt-driven ring buffer (non-blocking).
319pub fn read_scancode() -> Option<u8> {
320 KEYBUF.pop()
321}
322
323/// Global tick counter (incremented by timer IRQ, ~100 Hz).
324pub static TICKS: AtomicU16 = AtomicU16::new(0);
325
326/// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts.
327pub fn init() {
328 unsafe {
329 // Read the current code segment selector from CS.
330 let cs: u64;
331 asm!("mov {0}, cs", out(reg) cs, options(nostack, preserves_flags));
332 let cs = cs as u16;
333
334 // Build the IDT.
335 let idt_ptr = IDT.as_mut_ptr();
336 let spurious = isr_stub_spurious as *const () as u64;
337
338 // Fill all 256 entries with the spurious handler.
339 for vec in 0..=255u8 {
340 idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8);
341 }
342
343 // Override vectors for interrupt we actually handle.
344 idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8);
345 idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8);
346
347 // Build the IDTR pseudo-descriptor.
348 IDTR = [
349 (256u16 * 16 - 1) as u16, // limit = 256*16 - 1
350 idt_ptr as u16, // base lo
351 (idt_ptr as u64 >> 16) as u16, // base mid
352 (idt_ptr as u64 >> 32) as u16, // base hi
353 (idt_ptr as u64 >> 48) as u16, // base top
354 ];
355
356 // Load IDT.
357 asm!("lidt [{}]", in(reg) &IDTR, options(nostack, preserves_flags));
358
359 // Remap PIC and mask everything.
360 remap_pic();
361
362 // Set up PIT timer at ~100 Hz.
363 pit_set_rate(100);
364
365 // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master.
366 let mut mask: u8;
367 asm!("in al, dx", out("al") mask, in("dx") PIC1_DATA,
368 options(nostack, preserves_flags));
369 mask &= !0x03u8;
370 asm!("out dx, al", in("dx") PIC1_DATA, in("al") mask,
371 options(nostack, preserves_flags));
372
373 // Enable interrupts.
374 asm!("sti", options(nostack));
375 }
376}
377
378// ---------------------------------------------------------------------------
379// Extern symbols from global_asm!
380// ---------------------------------------------------------------------------
381extern "C" {
382 fn isr_stub_32();
383 fn isr_stub_33();
384 fn isr_stub_spurious();
385}
diff --git a/src/main.rs b/src/main.rs
index 2b1df77..9a901f5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,6 +9,7 @@ extern crate alloc;
9 9
10mod font; 10mod font;
11mod gop; 11mod gop;
12mod interrupts;
12mod memory; 13mod memory;
13mod ps2; 14mod ps2;
14mod serial; 15mod serial;
@@ -127,6 +128,10 @@ pub extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut SystemT
127 ps2::init(); 128 ps2::init();
128 serial_println!("ps2: keyboard initialized"); 129 serial_println!("ps2: keyboard initialized");
129 130
131 // Interrupts — IDT, PIC, PIT timer, keyboard IRQ handler
132 interrupts::init();
133 serial_println!("intr: IDT loaded, PIC remapped, timer ~100 Hz");
134
130 // Memory: the bump allocator was set up during capture_memory_map. 135 // Memory: the bump allocator was set up during capture_memory_map.
131 // `alloc::Vec`, `alloc::String`, `format!` etc. are now usable. 136 // `alloc::Vec`, `alloc::String`, `format!` etc. are now usable.
132 137
diff --git a/src/ps2.rs b/src/ps2.rs
index e972658..3a42c1a 100644
--- a/src/ps2.rs
+++ b/src/ps2.rs
@@ -179,47 +179,34 @@ fn drain_input() {
179 } 179 }
180} 180}
181 181
182/// Poll for a key event. Blocks until a key is pressed. 182/// Poll for a key event. Blocks via `hlt` until a key is pressed.
183/// The keyboard interrupt handler pushes raw scancodes to a ring buffer;
184/// this function pops and decodes them.
183pub fn poll_key() -> KeyEvent { 185pub fn poll_key() -> KeyEvent {
184 loop { 186 loop {
185 let byte = read_data(); 187 // Disable interrupts so the buffer-empty → hlt window is atomic.
186 if let Some(ev) = process_byte(byte) { 188 unsafe { core::arch::asm!("cli", options(nostack, preserves_flags)); }
187 return ev;
188 }
189 }
190}
191 189
192/// Non-blocking poll. 190 if let Some(byte) = crate::interrupts::read_scancode() {
193pub fn try_poll_key() -> Option<KeyEvent> { 191 unsafe { core::arch::asm!("sti", options(nostack, preserves_flags)); }
194 unsafe { 192 if let Some(ev) = process_byte(byte) {
195 let status: u8; 193 return ev;
196 core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, 194 }
197 options(nostack, preserves_flags)); 195 } else {
198 if status & STAT_OUTPUT_FULL == 0 { 196 // Re-enable and halt. If a keyboard IRQ arrived between the
199 return None; 197 // buffer check and `sti`, the CPU sees a pending interrupt
198 // and `hlt` returns immediately.
199 unsafe {
200 core::arch::asm!("sti; hlt", options(nostack, preserves_flags));
201 }
200 } 202 }
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 } 203 }
206} 204}
207 205
208/// Read a byte from the keyboard data port (blocking). 206/// Non-blocking poll — read from the interrupt ring buffer if available.
209fn read_data() -> u8 { 207pub fn try_poll_key() -> Option<KeyEvent> {
210 unsafe { 208 let byte = crate::interrupts::read_scancode()?;
211 loop { 209 process_byte(byte)
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} 210}
224 211
225/// Process a single scancode byte. Returns Some(KeyEvent) when a visible 212/// Process a single scancode byte. Returns Some(KeyEvent) when a visible
diff --git a/tools/qemu.sh b/tools/qemu.sh
index 428954f..b75bed4 100755
--- a/tools/qemu.sh
+++ b/tools/qemu.sh
@@ -81,7 +81,7 @@ QEMU_CMD=(
81[ -n "$GUI_FLAG" ] && QEMU_CMD+=("$GUI_FLAG") 81[ -n "$GUI_FLAG" ] && QEMU_CMD+=("$GUI_FLAG")
82if [ -n "$DEBUG_FLAG" ]; then 82if [ -n "$DEBUG_FLAG" ]; then
83 # Split the flag string safely into words 83 # Split the flag string safely into words
84 read -ra dbg <<< "$DEBUG_FLAG" 84 read -ra dbg <<<"$DEBUG_FLAG"
85 QEMU_CMD+=("${dbg[@]}") 85 QEMU_CMD+=("${dbg[@]}")
86fi 86fi
87 87