timer - PIT timer driver and ksleep

Header: kernel/include/kernel/timer.h
Source: kernel/arch/i386/hardware/timer.c

Programs the Intel 8253/8254 Programmable Interval Timer (PIT) channel 0 to fire IRQ 0 at a configurable frequency, and provides a busy-wait sleep function built on the resulting tick counter.

OSDev reference: Programmable Interval Timer (PIT)


PIT I/O ports

Port Direction Description
0x40 R/W Channel 0 data - divisor (lo byte then hi byte).
0x41 R/W Channel 1 data (unused; historically DRAM refresh).
0x42 R/W Channel 2 data (unused; drives PC speaker).
0x43 Write Mode/Command register.

How it works

The PIT’s input clock runs at 1 193 180 Hz (derived from the original IBM PC 14.318 MHz crystal divided by 12). To achieve a desired frequency f, a 16-bit divisor 1193180 / f is written to PIT channel 0 (port 0x40). The PIT then fires IRQ 0 every divisor clock cycles.

The command byte 0x36 written to port 0x43 selects:

  • Channel 0 (bits 7–6 = 00)
  • Lo-byte then hi-byte access (bits 5–4 = 11)
  • Mode 3: square-wave generator (bits 3–1 = 011)
  • Binary counting (bit 0 = 0)

At boot Makar calls init_timer(TIMER_HZ) with TIMER_HZ = 250, giving a tick rate of 250 Hz (one tick every 4 ms). The frequency lives in kernel/timer.h; user-facing time is reported through a stable USER_HZ (timer_to_user_ticks) so it stays HZ-independent like Linux.

Each IRQ 0 fires timer_callback, which:

  1. Increments the global tick counter.
  2. Invokes every registered per-tick hook with the current tick (see below) - this is how the boot spinner and the status-bar clock get driven without the timer knowing about the display layer.
  3. Every g_sched_quantum ticks (a tunable global, default 1 tick ≈ 4 ms, clamped to [SCHED_QUANTUM_MIN, SCHED_QUANTUM_MAX] = [1, 250]; an optional per-task sched_weight scales it), sends End-Of-Interrupt to the master PIC and calls task_yield(). This drives preemptive task switching - a busy-loop ring-0 task that never voluntarily yields will still surrender the CPU at the next quantum boundary. EOI is sent before the yield so further timer IRQs can fire while the new task runs.

Per-tick hooks

typedef void (*timer_tick_fn)(uint32_t tick);
void timer_register_tick_hook(timer_tick_fn fn);   /* up to TIMER_MAX_TICK_HOOKS */

So the timer IRQ stays display-agnostic (Linux’s timer IRQ never reaches into the console), interested modules subscribe instead of the timer calling into them. kernel_main registers t_spinner_tick (boot spinner) and vesa_tty_status_clock_tick (Alt+F5 status-bar clock) right after init_timer. Hooks run in IRQ context, so they must be short.


Functions

init_timer

void init_timer(uint32_t frequency);

Configure PIT channel 0 to interrupt at frequency Hz and enable CPU interrupts.

  1. Registers timer_callback on IRQ0.
  2. Computes the 16-bit divisor (1193180 / frequency).
  3. Writes command byte 0x36 to port 0x43.
  4. Writes the divisor to port 0x40 (lo byte first, then hi byte).
  5. Calls enable_interrupts() - this is the point at which hardware interrupts first become active in the boot sequence.
Parameter Description
frequency Desired interrupt rate in Hz. Must divide evenly into 1 193 180 or the rate will be approximate. Maximum is 1 193 180 Hz (divisor = 1); minimum is ~18 Hz (divisor = 65535).

timer_get_ticks

uint32_t timer_get_ticks(void);

Return the current tick count. The counter starts at 0 and increments by 1 on every timer interrupt. At 250 Hz it wraps after approximately 199 days of continuous uptime.

ksleep

void ksleep(uint32_t ticks);

Busy-wait until at least ticks timer ticks have elapsed since the call. At 250 Hz, ksleep(250) sleeps for approximately one second.

This is a spin-wait - the CPU executes a tight loop and does not yield. It is suitable for short post-boot delays but should be replaced with an interrupt-driven sleep once a scheduler exists.


Future work

  • Replace the spin-wait in ksleep with an interrupt-driven sleep queue once a process scheduler is in place.
  • Expose a monotonic clock interface (nanoseconds / seconds since boot) for use by higher-level subsystems and a future system-call layer.
  • Add a one-shot alarm / callback registration API for kernel timers.