kernel/devices/clock/hardware_timer/
pit.rs

1use core::sync::atomic::{AtomicU16, AtomicU64, Ordering};
2
3use alloc::sync::Arc;
4
5use crate::{
6    cpu::{
7        self,
8        idt::{BasicInterruptHandler, InterruptStackFrame64},
9        interrupts::apic,
10    },
11    devices::clock::{ClockDevice, ClockTime, FEMTOS_PER_SEC, NANOS_PER_FEMTO},
12    sync::once::OnceLock,
13};
14
15static PIT_CLOCK: OnceLock<Arc<Pit>> = OnceLock::new();
16
17const PIT_TICK_PERIOD_FEMTOS: u64 = 838095345;
18const PIT_TICK_PERIOD_NANOS: u64 = PIT_TICK_PERIOD_FEMTOS / NANOS_PER_FEMTO;
19
20#[allow(dead_code)]
21pub mod pit_io {
22    // Port Addresses
23    pub const PORT_CONTROL: u16 = 0x43;
24    pub const PORT_CHANNEL_0: u16 = 0x40;
25    pub const PORT_CHANNEL_1: u16 = 0x41;
26    pub const PORT_CHANNEL_2: u16 = 0x42;
27
28    // Channel Selection
29    pub const SELECT_CHANNEL_0: u8 = 0b00 << 6;
30    pub const SELECT_CHANNEL_1: u8 = 0b01 << 6;
31    pub const SELECT_CHANNEL_2: u8 = 0b10 << 6;
32    pub const SELECT_READ_BACK: u8 = 0b11 << 6;
33
34    // Access Modes
35    pub const ACCESS_LATCH_COUNT: u8 = 0b00 << 4;
36    pub const ACCESS_LOBYTE: u8 = 0b01 << 4;
37    pub const ACCESS_HIBYTE: u8 = 0b10 << 4;
38    pub const ACCESS_LOBYTE_HIBYTE: u8 = 0b11 << 4;
39
40    // Operating Modes
41    pub const MODE_INTERRUPT_ON_TERMINAL_COUNT: u8 = 0b000 << 1;
42    pub const MODE_HARDWARE_RETRIGGERABLE_ONE_SHOT: u8 = 0b001 << 1;
43    pub const MODE_RATE_GENERATOR: u8 = 0b010 << 1;
44    pub const MODE_SQUARE_WAVE_GENERATOR: u8 = 0b011 << 1;
45    pub const MODE_SOFTWARE_TRIGGERED_STROBE: u8 = 0b100 << 1;
46    pub const MODE_HARDWARE_TRIGGERED_STROBE: u8 = 0b101 << 1;
47
48    // BCD/Binary Mode
49    pub const MODE_BINARY: u8 = 0b0;
50    pub const MODE_BCD: u8 = 0b1;
51
52    pub const DEFAULT_INTERRUPT: u8 = 0;
53}
54
55pub fn disable() {
56    // disable PIT (timer)
57    unsafe {
58        // Disable the PIT, we are using HPET instead
59        // Not sure if this is an intended way to do it, but what we do here is:
60        // 1. Select channel 0 (main one)
61        // 2. Set access mode to lobyte (we only need this)
62        // 3. Set operating mode to `interrupt on terminal count` (one shot)
63        // 4. Reload value with 1 only, which will just trigger the interrupt immediately
64        //    and then never again.
65        //
66        // Docs on Mode 0 (interrupt on terminal count):
67        //  the mode/command register is written the output signal goes low and the PIT waits
68        //  for the reload register to be set by software.
69        //  When the current count decrements from one to zero, the output goes high and remains
70        //  high until another mode/command register is written or the reload register is set again.
71        //
72        // How this works is that we select this mode, with the reload value of 1, which will
73        // trigger the interrupt immediately, and then never again.
74        // Since we don't have interrupt handler now, we just ignore it.
75        cpu::io_out(
76            pit_io::PORT_CONTROL,
77            pit_io::SELECT_CHANNEL_0
78                | pit_io::ACCESS_LOBYTE
79                | pit_io::MODE_INTERRUPT_ON_TERMINAL_COUNT
80                | pit_io::MODE_BINARY,
81        );
82        cpu::io_out(pit_io::PORT_CHANNEL_0, 1u8);
83    }
84}
85
86pub fn init() -> Arc<Pit> {
87    // make sure we don't get interrupted before `PIT_CLOCK`
88    // is initialized
89    cpu::cpu().push_cli();
90
91    // just to make sure that we don't initialize it twice
92    if PIT_CLOCK.try_get().is_some() {
93        panic!("PIT already initialized");
94    }
95
96    let clock = PIT_CLOCK.get_or_init(|| Arc::new(Pit::new()));
97
98    cpu::cpu().pop_cli();
99
100    clock.clone()
101}
102
103pub struct Pit {
104    total_counter: AtomicU64,
105    last_counter: AtomicU16,
106}
107
108impl Pit {
109    pub fn new() -> Pit {
110        // this will be 0x10000 which is the highest value
111        // we don't care about the interrupt, and instead we just want to use
112        // the counter to know the clock timer, so a larger value is better
113        // since it will give us more time to check the value without being wrapped.
114        const RELOAD_VALUE: u16 = 0x0000;
115
116        // SAFETY: this is unsafe because it does I/O operations
117        // on the hardware
118        unsafe {
119            cpu::io_out(
120                pit_io::PORT_CONTROL,
121                pit_io::SELECT_CHANNEL_0
122                    | pit_io::ACCESS_LOBYTE_HIBYTE
123                    | pit_io::MODE_RATE_GENERATOR
124                    | pit_io::MODE_BINARY,
125            );
126
127            cpu::io_out(pit_io::PORT_CHANNEL_0, (RELOAD_VALUE & 0xFF) as u8);
128            cpu::io_out(pit_io::PORT_CHANNEL_0, (RELOAD_VALUE >> 8) as u8);
129        }
130
131        apic::assign_io_irq(
132            pit_interrupt as BasicInterruptHandler,
133            pit_io::DEFAULT_INTERRUPT,
134            cpu::cpu(),
135        );
136
137        Pit {
138            last_counter: AtomicU16::new(0),
139            total_counter: AtomicU64::new(0),
140        }
141    }
142
143    fn read_counter(&self) -> u16 {
144        let lo;
145        let hi;
146
147        cpu::cpu().push_cli();
148
149        // SAFETY: this is unsafe because it does I/O operations
150        // on the hardware
151        unsafe {
152            cpu::io_out(
153                pit_io::PORT_CONTROL,
154                pit_io::SELECT_CHANNEL_0 | pit_io::ACCESS_LATCH_COUNT | pit_io::MODE_BINARY,
155            );
156
157            lo = cpu::io_in::<u8>(pit_io::PORT_CHANNEL_0) as u16;
158            hi = cpu::io_in::<u8>(pit_io::PORT_CHANNEL_0) as u16;
159        }
160        cpu::cpu().pop_cli();
161
162        (hi << 8) | lo
163    }
164
165    fn get_elapsed(&self) -> u64 {
166        let counter = self.read_counter();
167
168        self.last_counter
169            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |_| Some(counter))
170            .unwrap()
171            .wrapping_sub(counter) as u64
172    }
173
174    /// Ticks and returns the total number of ticks since creation
175    fn tick_total_counter(&self) -> u64 {
176        self.total_counter
177            .fetch_add(self.get_elapsed(), Ordering::Relaxed);
178
179        self.total_counter.load(Ordering::Relaxed)
180    }
181}
182
183impl ClockDevice for Pit {
184    fn name(&self) -> &'static str {
185        "PIT"
186    }
187
188    fn get_time(&self) -> ClockTime {
189        let counter = self.tick_total_counter();
190
191        let seconds_divider = FEMTOS_PER_SEC / PIT_TICK_PERIOD_FEMTOS;
192        let seconds = counter / seconds_divider;
193        let nanoseconds = (counter % seconds_divider) * PIT_TICK_PERIOD_NANOS;
194
195        ClockTime {
196            seconds,
197            nanoseconds,
198        }
199    }
200
201    fn granularity(&self) -> u64 {
202        PIT_TICK_PERIOD_FEMTOS / NANOS_PER_FEMTO
203    }
204
205    fn require_calibration(&self) -> bool {
206        false
207    }
208
209    fn rating(&self) -> u64 {
210        40
211    }
212}
213
214extern "x86-interrupt" fn pit_interrupt(_stack_frame: InterruptStackFrame64) {
215    PIT_CLOCK.get().tick_total_counter();
216
217    // nothing to do here really
218    apic::return_from_interrupt();
219}