Debugging: Interrupt Storms
What Is an Interrupt Storm?
An interrupt storm occurs when an interrupt fires repeatedly without being properly cleared. Symptoms:
- Keys briefly appear on screen then vanish
- Screen flickers constantly
- System appears frozen or rebooting
- CPU at 100% handling interrupts
What Happened
After getting the keyboard handler working for the first time, this was the result: each key press caused a brief flicker of the character, then the screen would stroble with characters and nothing was readable. It looked like the system was rebooting, but it wasn’t — it was the handler running thousands of times per second.
The Cause: Not Clearing the Buffer
The keyboard sends a scan code to port 0x60. The keyboard’s IRQ line stays asserted (high) as long as data is in the buffer. The PIC won’t clear the interrupt until the IRQ line goes low. The IRQ line won’t go low until the data is read.
The broken code:
#![allow(unused)]
fn main() {
pub extern "C" fn rust_keyboard_handler() {
unsafe {
let status = inb(KEYBOARD_STATUS_PORT); // Check if data is ready
if (status & 0x01) == 0 {
pic::send_eoi(1);
return; // ← BUG: We returned WITHOUT reading port 0x60!
}
let scancode = inb(KEYBOARD_DATA_PORT);
pic::send_eoi(1);
// ...
}
}
}
If the status register says “no data ready” but there’s still stale data in the buffer, the IRQ line stays asserted. We send EOI, the PIC thinks the interrupt is done, but the keyboard immediately re-fires the interrupt. The handler runs again. And again. Forever.
The Fix: Always Read Port 0x60
#![allow(unused)]
fn main() {
pub extern "C" fn rust_keyboard_handler() {
unsafe {
// ALWAYS read from 0x60 — this clears the keyboard buffer
// and de-asserts the IRQ line, regardless of whether data is "ready"
let scancode = inb(KEYBOARD_DATA_PORT);
// NOW send EOI
pic::send_eoi(1);
// Process the scan code
if scancode < 128 && scancode != 0 {
let ascii = SCANCODE_TO_ASCII[scancode as usize];
if ascii != 0 {
crate::print!("{}", ascii as char);
}
}
}
}
}
Reading from port 0x60 always clears the keyboard buffer. It doesn’t matter if the status says data is ready or not — reading it clears any pending data and de-asserts the IRQ line.
The Lessons
- Always read port 0x60 — This is how the hardware works. The read is the acknowledgement.
- Order matters — Read the port first, then send EOI. Sending EOI before the read means the PIC might fire again before the buffer is cleared.
- Status checks can lie — The status register tells you if new data is ready, not if the buffer is fully clear.
- Debug incrementally — When the handler misbehaves, strip it back to the absolute minimum (just read + EOI, nothing else) and add things back one at a time.