06. Interrupts and Exception Handling¶
Interrupts are the foundation of responsive, event-driven operating systems. Let's implement ARM exception handling and timer interrupts to enable preemptive multitasking.
What are Interrupts?¶
Interrupts allow hardware to signal the CPU that an event has occurred, causing the CPU to pause current execution and handle the event immediately.
Use Cases¶
- Hardware events: Keyboard press, network packet arrival
- Timer-based scheduling: Preemptive multitasking
- Error handling: Memory access violations, undefined instructions
ARM Exception Levels¶
ARM CPUs have 4 Exception Levels (privilege levels):
| Level | Name | Use |
|---|---|---|
| EL0 | User | Unprivileged applications |
| EL1 | Kernel | Operating system (we run here) |
| EL2 | Hypervisor | Virtualization |
| EL3 | Secure Monitor | Trusted execution environment |
Our bare-metal OS runs at EL1 (kernel mode).
Exception Types¶
ARM defines 4 types of exceptions:
| Type | Description | Example |
|---|---|---|
| Synchronous | Caused by instruction execution | Undefined instruction, data abort |
| IRQ | Interrupt Request (normal interrupts) | Timer, GPIO, UART |
| FIQ | Fast Interrupt Request (high priority) | Critical hardware events |
| SError | System Error (asynchronous abort) | Memory errors |
IRQ vs FIQ¶
- IRQ: Standard interrupts, can be masked
- FIQ: Faster response (dedicated registers), higher priority
For most use cases, IRQ is sufficient.
Exception Vector Table¶
The ARM exception vector table defines where the CPU jumps when an exception occurs. It contains 16 entries (4 exception types × 4 sources):
Each entry must be 128 bytes (0x80) apart.
Implementing the Vector Table¶
Assembly: boot/vectors.S¶
IRQ Handler (Assembly)¶
The IRQ handler must:
1. Save all registers
2. Call C handler
3. Restore all registers
4. Return from exception (eret)
Installing the Vector Table¶
C Interrupt Handler¶
File: kernel/interrupts.c
Timer Interrupts¶
The ARM Generic Timer can generate interrupts when a compare value is reached.
Registers¶
| Register | Purpose |
|---|---|
CNTV_CVAL_EL0 |
Compare value (trigger when count reaches this) |
CNTV_CTL_EL0 |
Control (enable, mask, status) |
CNTVCT_EL0 |
Current counter value |
Timer Interrupt Setup¶
Handling Timer Interrupts¶
Building and Testing¶
1. Build¶
2. Deploy¶
Copy interrupt_demo.img to SD card (rename to kernel8.img).
3. Expected Output¶
Messages appear every 1 second, triggered by the timer interrupt!
Wait For Interrupt (WFI)¶
Instead of busy-waiting, use the wfi instruction to enter low-power mode:
The CPU sleeps until an interrupt occurs, saving power.
Complete Source Code¶
- boot/vectors.S - Exception vector table
- kernel/interrupts.c - Interrupt handlers
- include/interrupt.h - API
- examples/06_interrupt_demo.c - Demo
Troubleshooting¶
| Problem | Solution |
|---|---|
| No interrupts firing | Check vbar_el1 is set correctly |
| System hangs on exception | Verify vector table alignment (0x800) |
| Timer doesn't trigger | Ensure IRQ is enabled (daifclr) |
| Random crashes | Check register save/restore in handler |
Important Notes¶
UART in Interrupts
Using UART (uart_puts) from interrupt context is not ideal for production. It can block and cause timing issues. Use a ring buffer instead.
Exception Level
This code assumes you're running at EL1. Check with: mrs x0, CurrentEL
What's Next?¶
With interrupts working, we can now: - Implement preemptive multitasking (timer-based task switching) - Add GPIO interrupts for button presses - Handle exceptions properly (page faults, undefined instructions)
The next article covers Framebuffer and Graphics, which will allow us to display output on screen instead of just serial console!