04. System Timer - Precise Timing for Your OS¶
Now that we can communicate via UART, let's add timing capabilities to our OS using the Raspberry Pi's system timer.
Why Do We Need a Timer?¶
Timers are essential for:
- Delays: sleep() functions
- Scheduling: Task switching in multitasking
- Timeouts: Network or hardware communication
- Performance measurement: Benchmarking code
Understanding Raspberry Pi Timers¶
The Raspberry Pi 4B has multiple timing mechanisms:
1. System Timer (Legacy)¶
- Base Address:
0xFE003000(RPi 4) - Frequency: 1 MHz (fixed)
- Use: Simple delays, legacy compatibility
2. ARM Generic Timer (Preferred)¶
- Built into CPU: Part of ARM architecture
- Frequency: Typically 54 MHz on RPi 4
- Advantages: More precise, accessible via special registers
We'll use the ARM Generic Timer as it's more accurate and follows ARM standards.
ARM Generic Timer Registers¶
The ARM architecture provides special system registers for timing:
| Register | Description |
|---|---|
CNTPCT_EL0 |
Physical Count register (current tick count) |
CNTFRQ_EL0 |
Counter Frequency register (ticks per second) |
Reading Registers in Assembly¶
Understanding Inline Assembly¶
To read these special hardware registers, C code isn't enough. We need to "inject" assembly instructions directly into our C code.
asm volatile: "Compiler, do not touch or optimize this!"mrs: The assembly instruction (Move from System Register).%0: A placeholder for a variable."=r"(count): "Put the result into the C variablecount."
It's like opening a secret door in C to talk directly to the CPU.
Implementing the Timer Driver¶
Header File: include/timer.h¶
Driver Implementation: drivers/timer.c¶
1. Reading Timer Registers¶
2. Initialization¶
On the Raspberry Pi 4, timer_freq is typically 54,000,000 (54 MHz).
3. Sleep Function¶
How it works (Polling):
The CPU constantly checks the clock, asking "Are we there yet?" over and over.
graph TD
Start["Start Sleep"] --> GetStart["Read Start Time"]
GetStart --> CalcEnd["Calculate End Time"]
CalcEnd --> Check{"Current Time >= End Time?"}
Check -->|No| Check
Check -->|Yes| Done["Return"]
- Calculate how many ticks = 1 millisecond
- Compute target tick count
- Busy-wait until we reach that count
Busy-Wait Limitation
This is a busy-wait implementation. The CPU actively waits, wasting power. In a real OS, we'd use interrupts to sleep efficiently.
Using the Timer in Your Kernel¶
Update kernel/main.c:
Building and Testing¶
1. Build the Kernel¶
Verify that drivers/timer.c is compiled.
2. Deploy to Raspberry Pi¶
Copy kernel8.img to your SD card (along with firmware files).
3. Connect Serial and Boot¶
Open terminal:
Expected output:
You should see a message every second!
How Accurate Is It?¶
The ARM Generic Timer is quite precise: - Jitter: < 1 microsecond - Drift: Negligible for short durations
For comparison: - System Timer (1 MHz): 1 µs resolution - ARM Generic (54 MHz): ~18.5 ns resolution
Complete Source Code¶
The full implementation is available: - drivers/timer.c - include/timer.h - kernel/main.c
Troubleshooting¶
| Problem | Solution |
|---|---|
| Messages print too fast/slow | Check timer frequency with uart_puts |
| No output at all | Ensure timer_init() is called |
| Incorrect delays | Verify 64-bit arithmetic (not 32-bit overflow) |
What's Next?¶
With timing capabilities in place, we can now: - Implement interrupts (timer-based interrupts for preemptive multitasking) - Add watchdog timer (system recovery) - Create performance profiling tools
Stay tuned for the next article on Exception Handling and Interrupts!