mirror of
https://github.com/Plexi09/Nadir-Kernel.git
synced 2026-10-20 15:07:27 +02:00
Nadir 8254 PIT timer driver
This commit is contained in:
parent
47e83cf795
commit
6ee1900c1a
2 changed files with 151 additions and 0 deletions
56
include/pit.h
Normal file
56
include/pit.h
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
/* Nadir 8254 PIT timer driver (see pit.c).
|
||||||
|
*
|
||||||
|
* Runs in 64-bit long mode at Ring 0 (CPL 0): programs the PIT via `outb`
|
||||||
|
* to ISA ports PIT_CMD (0x43) and PIT_CH0 (0x40), and handles IRQ0
|
||||||
|
* (8259 PIC IRQ0 -> CPU vector 32 after pic_remap).
|
||||||
|
*
|
||||||
|
* Ports used: 0x43 (command/mode register, write-only), 0x40 (channel 0
|
||||||
|
* data, write-only for the divisor latch).
|
||||||
|
*
|
||||||
|
* Preconditions: caller owns interrupt policy — pit_init() neither enables
|
||||||
|
* (sti) nor disables (cli) interrupts; target init order is
|
||||||
|
* pic_remap -> idt_install_irqs -> pit_init -> pic_unmask(0) -> sti.
|
||||||
|
* pit_sleep() requires interrupts enabled, otherwise `hlt` never wakes.
|
||||||
|
*
|
||||||
|
* Failure modes: divisor is fixed at compile time (PIT_DIVISOR); a zero
|
||||||
|
* divisor would mean "65536" to the hardware and the wrong rate, so it is
|
||||||
|
* rejected with a compile-time _Static_assert. Frequency is fixed at 100Hz;
|
||||||
|
* any other rate needs a deliberate driver change, not a runtime argument.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NADIR_PIT_H
|
||||||
|
#define NADIR_PIT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* PIT input clock: crystal-derived base rate shared by all IBM-PC PITs. */
|
||||||
|
#define PIT_BASE_HZ 1193182U
|
||||||
|
/* Driver tick rate: 100Hz = 10ms per tick. */
|
||||||
|
#define PIT_HZ 100U
|
||||||
|
/* Divisor latched into channel 0 (1193182/100 = 11931). */
|
||||||
|
#define PIT_DIVISOR (PIT_BASE_HZ / PIT_HZ)
|
||||||
|
/* 8259 PIC IRQ line carrying channel-0 ticks (remapped to vector 32). */
|
||||||
|
#define PIT_IRQ 0U
|
||||||
|
|
||||||
|
/* Program channel 0 for mode 3 square-wave at PIT_HZ and register
|
||||||
|
* pit_on_tick as the IRQ0 handler (if irq_register_handler is linked in).
|
||||||
|
* Ring 0 only. Idempotent: second and later calls do nothing. Leaves IF
|
||||||
|
* exactly as found; the caller decides when to sti. */
|
||||||
|
void pit_init(void);
|
||||||
|
|
||||||
|
/* IRQ0 handler: increments the tick counter. Called from IRQ context
|
||||||
|
* (interrupts already gated by the CPU/IRQ stub); must stay async-safe
|
||||||
|
* and never block. */
|
||||||
|
void pit_on_tick(void);
|
||||||
|
|
||||||
|
/* Monotonic tick count since pit_init (10ms per tick at 100Hz). Wraps only
|
||||||
|
* after ~5.8 billion years; unsigned subtraction stays correct across it. */
|
||||||
|
uint64_t pit_ticks(void);
|
||||||
|
|
||||||
|
/* Busy-wait `ticks` ticks using `hlt` to save power between interrupts.
|
||||||
|
* Requires interrupts enabled (IF=1) and the PIT IRQ unmasked, otherwise
|
||||||
|
* the CPU sleeps forever. Uses unsigned delta so a counter wrap mid-sleep
|
||||||
|
* still terminates. */
|
||||||
|
void pit_sleep(uint64_t ticks);
|
||||||
|
|
||||||
|
#endif /* NADIR_PIT_H */
|
||||||
95
kernel/arch/x86_64/pit.c
Normal file
95
kernel/arch/x86_64/pit.c
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/* 8254 PIT timer driver (see pit.h).
|
||||||
|
*
|
||||||
|
* Programs channel 0 in mode 3 (square-wave generator) at 100Hz on IRQ0.
|
||||||
|
*
|
||||||
|
* Why 1193182Hz base: the PIT input clock is the IBM-PC crystal frequency
|
||||||
|
* (14.31818MHz / 12); every PC-compatible PIT ticks at this rate, so the
|
||||||
|
* divisor for N Hz is always 1193182 / N.
|
||||||
|
* Why mode 3: square-wave output stays high/low for half the period and
|
||||||
|
* retriggers the IRQ line cleanly each cycle; mode 2 (rate generator)
|
||||||
|
* emits narrow pulses that are fine too, but mode 3 is the conventional
|
||||||
|
* choice for periodic system ticks on channel 0.
|
||||||
|
* Why volatile + no locking: single-core kernel; s_ticks is written only
|
||||||
|
* in IRQ0 context and read in mainline code. `volatile` keeps the compiler
|
||||||
|
* from caching the counter across the hlt loop, and on x86-64 an aligned
|
||||||
|
* 64-bit load/store is atomic, so no spinlock is needed while IF gates the
|
||||||
|
* writer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "idt.h"
|
||||||
|
#include "io.h"
|
||||||
|
#include "pit.h"
|
||||||
|
|
||||||
|
#define PIT_CMD 0x43U
|
||||||
|
#define PIT_CH0 0x40U
|
||||||
|
/* Ch0, lobyte/hibyte access, mode 3 square-wave, binary (not BCD). */
|
||||||
|
#define PIT_CMD_CH0_MODE3 0x36U
|
||||||
|
|
||||||
|
/* Compile-time guard: a zero divisor would mean 65536 to the hardware and
|
||||||
|
* silently run at ~18Hz instead of PIT_HZ. Frequency is fixed on purpose. */
|
||||||
|
_Static_assert(PIT_DIVISOR != 0, "PIT divisor must not be zero");
|
||||||
|
_Static_assert(PIT_DIVISOR <= 0xFFFFU, "PIT divisor must fit in 16 bits");
|
||||||
|
|
||||||
|
/* Port I/O is owned by include/io.h (single owner); irq_register_handler
|
||||||
|
* is owned by idt.c via include/idt.h. */
|
||||||
|
|
||||||
|
/* Monotonic tick counter: written in IRQ context. */
|
||||||
|
static volatile uint64_t s_ticks;
|
||||||
|
/* pit_init is idempotent; re-entry after the first programming is a no-op. */
|
||||||
|
static uint8_t s_initialized;
|
||||||
|
|
||||||
|
void pit_init(void)
|
||||||
|
{
|
||||||
|
uint16_t divisor;
|
||||||
|
uint8_t lo;
|
||||||
|
uint8_t hi;
|
||||||
|
|
||||||
|
if (s_initialized != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No cli/sti here on purpose: latch programming is two plain outb
|
||||||
|
* writes (safe with IF=0 at boot) and interrupt policy belongs to the
|
||||||
|
* caller (kmain integrator), which unmasks IRQ0 and stis afterwards. */
|
||||||
|
divisor = (uint16_t)PIT_DIVISOR;
|
||||||
|
lo = (uint8_t)(divisor & 0xFFU);
|
||||||
|
hi = (uint8_t)((divisor >> 8) & 0xFFU);
|
||||||
|
|
||||||
|
/* Latch order matters: command byte first selects ch0 + lobyte/hibyte
|
||||||
|
* mode, then the divisor follows low byte first, high byte second. */
|
||||||
|
outb(PIT_CMD, PIT_CMD_CH0_MODE3);
|
||||||
|
outb(PIT_CH0, lo);
|
||||||
|
outb(PIT_CH0, hi);
|
||||||
|
|
||||||
|
/* Strong idt.h registration: IRQ0 -> vector 32 wires pit_on_tick. */
|
||||||
|
irq_register_handler(PIT_IRQ, pit_on_tick);
|
||||||
|
|
||||||
|
s_initialized = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pit_on_tick(void)
|
||||||
|
{
|
||||||
|
/* Single increment async-safe, no logging or port I/O here. The PIC
|
||||||
|
* EOI is owned by the IRQ dispatcher not this driver. */
|
||||||
|
s_ticks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t pit_ticks(void)
|
||||||
|
{
|
||||||
|
return s_ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pit_sleep(uint64_t ticks)
|
||||||
|
{
|
||||||
|
uint64_t start = s_ticks;
|
||||||
|
|
||||||
|
/* hlt (not a spin loop) so the CPU sleeps until the next interrupt.
|
||||||
|
* Assumes IF=1 and IRQ0 unmasked; with IF=0 this would halt forever,
|
||||||
|
* which is why pit_init leaves interrupt policy to the caller. The
|
||||||
|
* unsigned delta keeps working if s_ticks wraps mid-sleep. */
|
||||||
|
while ((s_ticks - start) < ticks) {
|
||||||
|
__asm__ volatile("hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue