summaryrefslogtreecommitdiffstats
path: root/rsnix/src/interrupts.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-07-31 02:58:31 +0200
committersalmonik <salmonik@tuta.io>2026-07-31 02:58:31 +0200
commit6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b (patch)
treeed59a5bd59a94b3a731b28c68c507f18afc44b0b /rsnix/src/interrupts.rs
parentauto-version all strings from Cargo.toml via env!("CARGO_PKG_VERSION") (diff)
downloadrs-dos-6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b.tar.gz
rs-dos-6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b.zip
restructure: rsnix kernel + shell crate + rsdos entry point
Architecture: rs-dos/ (workspace root, was raisernics/) ├── rsnix/ — kernel lib crate (uefi, serial, gop, ps2, interrupts, memory) ├── shell/ — shell lib crate (prompt, commands, line input) ├── rsdos/ — binary crate (efi_main, init, panic handler) └── rsboot/ — future git submodule No functional changes — boots to shell prompt identically. Version auto-pulled from Cargo.toml via env!("CARGO_PKG_VERSION").
Diffstat (limited to 'rsnix/src/interrupts.rs')
-rw-r--r--rsnix/src/interrupts.rs401
1 files changed, 401 insertions, 0 deletions
diff --git a/rsnix/src/interrupts.rs b/rsnix/src/interrupts.rs
new file mode 100644
index 0000000..8f2acd7
--- /dev/null
+++ b/rsnix/src/interrupts.rs
@@ -0,0 +1,401 @@
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) > 255 {
178 return; // drop — ring is full
179 }
180 unsafe {
181 *self.buf[t as usize & 255].get() = byte;
182 }
183 // Ensure byte is globally visible before incrementing tail.
184 self.tail.store(next, Ordering::Release);
185 }
186
187 /// Pop one byte — called from main context. Returns None when empty.
188 fn pop(&self) -> Option<u8> {
189 let h = self.head.load(Ordering::Relaxed);
190 let t = self.tail.load(Ordering::Acquire);
191 if h == t {
192 return None;
193 }
194 let byte = unsafe { *self.buf[h as usize & 255].get() };
195 self.head.store(h.wrapping_add(1), Ordering::Release);
196 Some(byte)
197 }
198}
199
200static KEYBUF: KeyBuf = KeyBuf::new();
201
202// ---------------------------------------------------------------------------
203// Interrupt stubs (assembly) and dispatch
204// ---------------------------------------------------------------------------
205
206core::arch::global_asm!(
207 // ISR stubs — push vector number + error code, jump to common.
208 ".globl isr_stub_32",
209 ".p2align 4",
210 "isr_stub_32:",
211 " push 0", // error code (non-exception)
212 " push 32", // vector
213 " jmp isr_common",
214 "",
215 ".globl isr_stub_33",
216 ".p2align 4",
217 "isr_stub_33:",
218 " push 0",
219 " push 33",
220 " jmp isr_common",
221 "",
222 // Catch-all for unexpected interrupts.
223 ".globl isr_stub_spurious",
224 ".p2align 4",
225 "isr_stub_spurious:",
226 " push 0",
227 " push 0xFF",
228 " jmp isr_common",
229 "",
230 // Common handler — save all registers, call into Rust, restore, iretq.
231 "isr_common:",
232 " push rax",
233 " push rcx",
234 " push rdx",
235 " push rbx",
236 " push rbp",
237 " push rsi",
238 " push rdi",
239 " push r8",
240 " push r9",
241 " push r10",
242 " push r11",
243 " push r12",
244 " push r13",
245 " push r14",
246 " push r15",
247 "",
248 " mov rcx, rsp", // first arg (rcx = MS x64 ABI)
249 " call isr_dispatch",
250 "",
251 " pop r15",
252 " pop r14",
253 " pop r13",
254 " pop r12",
255 " pop r11",
256 " pop r10",
257 " pop r9",
258 " pop r8",
259 " pop rdi",
260 " pop rsi",
261 " pop rbp",
262 " pop rbx",
263 " pop rdx",
264 " pop rcx",
265 " pop rax",
266 " add rsp, 16", // pop vector + error code
267 " iretq",
268);
269
270/// Saved register frame (as pushed by `isr_common`).
271#[repr(C)]
272struct IntFrame {
273 r15: u64,
274 r14: u64,
275 r13: u64,
276 r12: u64,
277 r11: u64,
278 r10: u64,
279 r9: u64,
280 r8: 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,
293}
294
295/// Called from the asm common handler — dispatches to the right Rust handler.
296#[no_mangle]
297unsafe extern "C" fn isr_dispatch(frame: &IntFrame) {
298 unsafe {
299 match frame.vector {
300 32 => eoi(0),
301 33 => {
302 let byte: u8;
303 core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16,
304 options(nostack, preserves_flags));
305 // Push scancode to ring buffer (called with interrupts disabled).
306 KEYBUF.push(byte);
307 eoi(1);
308 }
309 _ => {}
310 }
311 }
312}
313
314/// Timer IRQ (PIT channel 0 ~ 100 Hz).
315fn timer_irq() {
316 eoi(0);
317}
318
319/// Keyboard IRQ — read scancode from port 0x60 and push to ring buffer.
320fn keyboard_irq() {
321 let byte: u8;
322 unsafe {
323 asm!("in al, dx", out("al") byte, in("dx") 0x60,
324 options(nostack, preserves_flags));
325 }
326 KEYBUF.push(byte);
327 eoi(1);
328}
329
330// ---------------------------------------------------------------------------
331// Public API
332// ---------------------------------------------------------------------------
333
334/// Read one scancode from the interrupt-driven ring buffer (non-blocking).
335pub fn read_scancode() -> Option<u8> {
336 KEYBUF.pop()
337}
338
339/// Global tick counter (incremented by timer IRQ, ~100 Hz).
340pub static TICKS: AtomicU16 = AtomicU16::new(0);
341
342/// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts.
343pub fn init() {
344 unsafe {
345 // Read the current code segment selector from CS.
346 let cs: u64;
347 asm!("mov {0}, cs", out(reg) cs, options(nostack, preserves_flags));
348 let cs = cs as u16;
349
350 // Build the IDT.
351 let idt_ptr = IDT.as_mut_ptr();
352 let spurious = isr_stub_spurious as *const () as u64;
353
354 // Fill all 256 entries with the spurious handler.
355 for vec in 0..=255u8 {
356 idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8);
357 }
358
359 // Override vectors for interrupt we actually handle.
360 idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8);
361 idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8);
362
363 // Build the IDTR pseudo-descriptor.
364 IDTR = [
365 ((256u16 * 16 - 1)), // limit = 256*16 - 1
366 idt_ptr as u16, // base lo
367 (idt_ptr as u64 >> 16) as u16, // base mid
368 (idt_ptr as u64 >> 32) as u16, // base hi
369 (idt_ptr as u64 >> 48) as u16, // base top
370 ];
371
372 // Load IDT.
373 asm!("lidt [{}]", in(reg) &IDTR, options(nostack, preserves_flags));
374
375 // Remap PIC and mask everything.
376 remap_pic();
377
378 // Set up PIT timer at ~100 Hz.
379 pit_set_rate(100);
380
381 // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master.
382 let mut mask: u8;
383 asm!("in al, dx", out("al") mask, in("dx") PIC1_DATA,
384 options(nostack, preserves_flags));
385 mask &= !0x03u8;
386 asm!("out dx, al", in("dx") PIC1_DATA, in("al") mask,
387 options(nostack, preserves_flags));
388
389 // Enable interrupts.
390 asm!("sti", options(nostack));
391 }
392}
393
394// ---------------------------------------------------------------------------
395// Extern symbols from global_asm!
396// ---------------------------------------------------------------------------
397extern "C" {
398 fn isr_stub_32();
399 fn isr_stub_33();
400 fn isr_stub_spurious();
401}