diff options
Diffstat (limited to 'src/interrupts.rs')
| -rw-r--r-- | src/interrupts.rs | 401 |
1 files changed, 0 insertions, 401 deletions
diff --git a/src/interrupts.rs b/src/interrupts.rs deleted file mode 100644 index 90d5bbb..0000000 --- a/src/interrupts.rs +++ /dev/null | |||
| @@ -1,401 +0,0 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // Interrupt handling: IDT, PIC remap, ISR dispatch. | ||
| 3 | // --------------------------------------------------------------------------- | ||
| 4 | use core::arch::asm; | ||
| 5 | use 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)] | ||
| 14 | struct 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). | ||
| 25 | unsafe 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). | ||
| 42 | static mut IDT: [u8; 256 * 16] = [0u8; 256 * 16]; | ||
| 43 | static mut IDTR: [u16; 5] = [0; 5]; // 2-byte limit + 4-byte base (48-bit pseudo-desc) | ||
| 44 | |||
| 45 | // --------------------------------------------------------------------------- | ||
| 46 | // PIC (8259A) | ||
| 47 | // --------------------------------------------------------------------------- | ||
| 48 | |||
| 49 | const PIC1_CMD: u16 = 0x20; | ||
| 50 | const PIC1_DATA: u16 = 0x21; | ||
| 51 | const PIC2_CMD: u16 = 0xA0; | ||
| 52 | const PIC2_DATA: u16 = 0xA1; | ||
| 53 | |||
| 54 | const PIC_EOI: u8 = 0x20; | ||
| 55 | |||
| 56 | const ICW1: u8 = 0x11; // edge, cascade, icw4 | ||
| 57 | const ICW4: u8 = 0x01; // x86 mode | ||
| 58 | |||
| 59 | /// Remap PIC IRQs 0-15 to IDT vectors 32-47, then mask all. | ||
| 60 | unsafe 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). | ||
| 76 | fn 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 | |||
| 92 | const PIT_CMD: u16 = 0x43; | ||
| 93 | const PIT_CH0: u16 = 0x40; | ||
| 94 | const PIT_FREQ: u64 = 1_193_182; | ||
| 95 | |||
| 96 | /// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish). | ||
| 97 | fn 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. | ||
| 115 | struct 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 | |||
| 121 | unsafe impl Sync for KeyBuf {} | ||
| 122 | |||
| 123 | impl 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 { | ||
| 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 | |||
| 200 | static KEYBUF: KeyBuf = KeyBuf::new(); | ||
| 201 | |||
| 202 | // --------------------------------------------------------------------------- | ||
| 203 | // Interrupt stubs (assembly) and dispatch | ||
| 204 | // --------------------------------------------------------------------------- | ||
| 205 | |||
| 206 | core::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)] | ||
| 272 | struct 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] | ||
| 297 | unsafe 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). | ||
| 315 | fn timer_irq() { | ||
| 316 | eoi(0); | ||
| 317 | } | ||
| 318 | |||
| 319 | /// Keyboard IRQ — read scancode from port 0x60 and push to ring buffer. | ||
| 320 | fn 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). | ||
| 335 | pub fn read_scancode() -> Option<u8> { | ||
| 336 | KEYBUF.pop() | ||
| 337 | } | ||
| 338 | |||
| 339 | /// Global tick counter (incremented by timer IRQ, ~100 Hz). | ||
| 340 | pub static TICKS: AtomicU16 = AtomicU16::new(0); | ||
| 341 | |||
| 342 | /// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts. | ||
| 343 | pub 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) as u16, // 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 | // --------------------------------------------------------------------------- | ||
| 397 | extern "C" { | ||
| 398 | fn isr_stub_32(); | ||
| 399 | fn isr_stub_33(); | ||
| 400 | fn isr_stub_spurious(); | ||
| 401 | } | ||
