use core::sync::atomic::{AtomicU16, AtomicU64, Ordering};
use alloc::sync::Arc;
use crate::{
cpu::{
self,
idt::{BasicInterruptHandler, InterruptStackFrame64},
interrupts::apic,
},
devices::clock::{ClockDevice, ClockTime, FEMTOS_PER_SEC, NANOS_PER_FEMTO},
sync::once::OnceLock,
};
static PIT_CLOCK: OnceLock<Arc<Pit>> = OnceLock::new();
const PIT_TICK_PERIOD_FEMTOS: u64 = 838095345;
const PIT_TICK_PERIOD_NANOS: u64 = PIT_TICK_PERIOD_FEMTOS / NANOS_PER_FEMTO;
#[allow(dead_code)]
pub mod pit_io {
pub const PORT_CONTROL: u16 = 0x43;
pub const PORT_CHANNEL_0: u16 = 0x40;
pub const PORT_CHANNEL_1: u16 = 0x41;
pub const PORT_CHANNEL_2: u16 = 0x42;
pub const SELECT_CHANNEL_0: u8 = 0b00 << 6;
pub const SELECT_CHANNEL_1: u8 = 0b01 << 6;
pub const SELECT_CHANNEL_2: u8 = 0b10 << 6;
pub const SELECT_READ_BACK: u8 = 0b11 << 6;
pub const ACCESS_LATCH_COUNT: u8 = 0b00 << 4;
pub const ACCESS_LOBYTE: u8 = 0b01 << 4;
pub const ACCESS_HIBYTE: u8 = 0b10 << 4;
pub const ACCESS_LOBYTE_HIBYTE: u8 = 0b11 << 4;
pub const MODE_INTERRUPT_ON_TERMINAL_COUNT: u8 = 0b000 << 1;
pub const MODE_HARDWARE_RETRIGGERABLE_ONE_SHOT: u8 = 0b001 << 1;
pub const MODE_RATE_GENERATOR: u8 = 0b010 << 1;
pub const MODE_SQUARE_WAVE_GENERATOR: u8 = 0b011 << 1;
pub const MODE_SOFTWARE_TRIGGERED_STROBE: u8 = 0b100 << 1;
pub const MODE_HARDWARE_TRIGGERED_STROBE: u8 = 0b101 << 1;
pub const MODE_BINARY: u8 = 0b0;
pub const MODE_BCD: u8 = 0b1;
pub const DEFAULT_INTERRUPT: u8 = 0;
}
pub fn disable() {
unsafe {
cpu::io_out(
pit_io::PORT_CONTROL,
pit_io::SELECT_CHANNEL_0
| pit_io::ACCESS_LOBYTE
| pit_io::MODE_INTERRUPT_ON_TERMINAL_COUNT
| pit_io::MODE_BINARY,
);
cpu::io_out(pit_io::PORT_CHANNEL_0, 1u8);
}
}
pub fn init() -> Arc<Pit> {
cpu::cpu().push_cli();
if PIT_CLOCK.try_get().is_some() {
panic!("PIT already initialized");
}
let clock = PIT_CLOCK.get_or_init(|| Arc::new(Pit::new()));
cpu::cpu().pop_cli();
clock.clone()
}
pub struct Pit {
total_counter: AtomicU64,
last_counter: AtomicU16,
}
impl Pit {
pub fn new() -> Pit {
const RELOAD_VALUE: u16 = 0x0000;
unsafe {
cpu::io_out(
pit_io::PORT_CONTROL,
pit_io::SELECT_CHANNEL_0
| pit_io::ACCESS_LOBYTE_HIBYTE
| pit_io::MODE_RATE_GENERATOR
| pit_io::MODE_BINARY,
);
cpu::io_out(pit_io::PORT_CHANNEL_0, (RELOAD_VALUE & 0xFF) as u8);
cpu::io_out(pit_io::PORT_CHANNEL_0, (RELOAD_VALUE >> 8) as u8);
}
apic::assign_io_irq(
pit_interrupt as BasicInterruptHandler,
pit_io::DEFAULT_INTERRUPT,
cpu::cpu(),
);
Pit {
last_counter: AtomicU16::new(0),
total_counter: AtomicU64::new(0),
}
}
fn read_counter(&self) -> u16 {
let lo;
let hi;
cpu::cpu().push_cli();
unsafe {
cpu::io_out(
pit_io::PORT_CONTROL,
pit_io::SELECT_CHANNEL_0 | pit_io::ACCESS_LATCH_COUNT | pit_io::MODE_BINARY,
);
lo = cpu::io_in::<u8>(pit_io::PORT_CHANNEL_0) as u16;
hi = cpu::io_in::<u8>(pit_io::PORT_CHANNEL_0) as u16;
}
cpu::cpu().pop_cli();
(hi << 8) | lo
}
fn get_elapsed(&self) -> u64 {
let counter = self.read_counter();
self.last_counter
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |_| Some(counter))
.unwrap()
.wrapping_sub(counter) as u64
}
fn tick_total_counter(&self) -> u64 {
self.total_counter
.fetch_add(self.get_elapsed(), Ordering::Relaxed);
self.total_counter.load(Ordering::Relaxed)
}
}
impl ClockDevice for Pit {
fn name(&self) -> &'static str {
"PIT"
}
fn get_time(&self) -> ClockTime {
let counter = self.tick_total_counter();
let seconds_divider = FEMTOS_PER_SEC / PIT_TICK_PERIOD_FEMTOS;
let seconds = counter / seconds_divider;
let nanoseconds = (counter % seconds_divider) * PIT_TICK_PERIOD_NANOS;
ClockTime {
seconds,
nanoseconds,
}
}
fn granularity(&self) -> u64 {
PIT_TICK_PERIOD_FEMTOS / NANOS_PER_FEMTO
}
fn require_calibration(&self) -> bool {
false
}
fn rating(&self) -> u64 {
40
}
}
extern "x86-interrupt" fn pit_interrupt(_stack_frame: InterruptStackFrame64) {
PIT_CLOCK.get().tick_total_counter();
apic::return_from_interrupt();
}