x86_q35/pit.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
4
5//! Support for legacy 8253-compatible timer.
6//!
7//! This module implements support for the 8253 programmable interrupt timer, aka the "PIT". This
8//! device provides a source of periodic interrupts that can be used for timing purposes.
9//!
10//! The 8253 is rather old and has lots of quirks which are not found in more modern timer circuits.
11//! As a result, some amount of jitter/drift is unavoidable when using the PIT. Sadly this is
12//! unavoidable due to hardware limitation. For instance, the PIT's internal frequency is a rational
13//! number but not an integer, so all calculations are imprecise due to integer rounding.
14//!
15//! This implementation is based on guidance from the following sources:
16//!
17//! * <https://wiki.osdev.org/Programmable_Interval_Timer>
18//! * <https://en.wikipedia.org/wiki/Intel_8253>
19
20use core::cell::Cell;
21
22use kernel::hil::time::{Alarm, AlarmClient, Frequency, Ticks, Ticks32, Time};
23use kernel::ErrorCode;
24use tock_cells::numeric_cell_ext::NumericCellExt;
25use tock_cells::optional_cell::OptionalCell;
26use tock_registers::{register_bitfields, LocalRegisterCopy};
27use x86::registers::io;
28
29/// Frequency of the PIT's internal oscillator
30///
31/// According to Wikipedia, this should be "one third of the NTSC color subcarrier frequency."
32///
33/// Why such a specific value? This frequency is heavily used in analog television circuitry. At the
34/// time the 8253 was first introduced, it was very easy and cheap to obtain a crystal oscillator at
35/// exactly this frequency.
36const OSCILLATOR_FREQUENCY: u32 = 3579545 / 3;
37
38/// Frequency of the PIT timer
39///
40/// Parameter `R` is the PIT's reload value for channel 0.
41///
42/// Internal oscillator frequency is a rational, non-integer number. This means there is some loss
43/// of precision because Tock uses integers to represent frequency.
44pub struct PitFreq<const R: u16>;
45
46impl<const R: u16> Frequency for PitFreq<R> {
47 fn frequency() -> u32 {
48 OSCILLATOR_FREQUENCY / (R as u32)
49 }
50}
51
52/// Computes a PIT reload value for the given frequency.
53///
54/// The actual interrupt frequency will always be slightly different from the requested `freq` due
55/// to hardware limitations. This function tries to get as close as possible.
56pub const fn reload_value(freq: u32) -> u16 {
57 // Lowest possible frequency is about 18 Hz
58 if freq <= 18 {
59 // PIT interprets zero as "maximum possible value"
60 return 0x0000;
61 }
62
63 // Highest possible frequency is OSCILLATOR_FREQUENCY
64 if freq >= OSCILLATOR_FREQUENCY {
65 return 0x0001;
66 }
67
68 let mut r = OSCILLATOR_FREQUENCY / freq;
69
70 // If remainder is more than half, then round up to get as close as possible
71 if (OSCILLATOR_FREQUENCY % freq) >= (OSCILLATOR_FREQUENCY / 2) {
72 r += 1;
73 }
74
75 r as u16
76}
77
78/// Reload value corresponding to 1 KHz
79pub const RELOAD_1KHZ: u16 = reload_value(1000);
80
81/// I/O port address for channel 0
82const PIT_CD0: u16 = 0x0040;
83
84/// I/O port address for mode/command register
85const PIT_MCR: u16 = 0x0043;
86
87// Bitfield for the mode/command register
88register_bitfields!(u8,
89 PIT_MCR [
90 BCD OFFSET(0) NUMBITS(1) [],
91 MODE OFFSET(1) NUMBITS(3) [
92 M2 = 0b010, // Rate generator
93 ],
94 ACCESS OFFSET(4) NUMBITS(2) [
95 LOHI = 0b11, // Lobyte/hibyte access mode
96 ],
97 CHANNEL OFFSET(6) NUMBITS(2) [
98 C0 = 0b00, // Channel 0
99 ]
100 ]
101);
102
103/// Timer based on 8253 "PIT" hardware
104///
105/// Although the PIT contains an internal counter, it is not a suitable source of ticks because it
106/// runs at a high, fixed frequency and wraps very quickly. So instead, we configure the PIT to
107/// generate periodic interrupts, and we increment an in-memory counter each time an interrupt
108/// fires.
109///
110/// Parameter `R` is the reload value to use. This is loaded into a hardware register and determines
111/// the interrupt frequency. Use [`reload_value`] to compute a reload value for the desired
112/// frequency.
113pub struct Pit<'a, const R: u16> {
114 alarm: OptionalCell<u32>,
115 client: OptionalCell<&'a dyn AlarmClient>,
116 now: Cell<usize>,
117}
118
119impl<const R: u16> Pit<'_, R> {
120 /// Creates a new PIT timer object.
121 ///
122 /// ## Safety
123 ///
124 /// There must never be more than a single instance of `Pit` alive at any given time.
125 pub unsafe fn new() -> Self {
126 Pit {
127 alarm: OptionalCell::empty(),
128 client: OptionalCell::empty(),
129 now: Cell::new(0),
130 }
131 }
132
133 /// Configures the PIT to start generating periodic interrupts.
134 pub fn start(&self) {
135 // Safety assumptions:
136 // * We are currently running with I/O privileges
137 // * There is an actual PIT device at the exected I/O addresses and not something else
138 // * Interrupts will be handled properly
139 // * Nobody else is accessing the PIT at the same time (shouldn't be possible unless the
140 // caller disregards the "Safety" section of `Pit::new`)
141
142 // Set mode 2 and program reload value
143 let mut pit_mcr = LocalRegisterCopy::<u8, PIT_MCR::Register>::new(0);
144 pit_mcr.modify(PIT_MCR::MODE::M2 + PIT_MCR::ACCESS::LOHI + PIT_MCR::CHANNEL::C0);
145
146 unsafe {
147 io::outb(PIT_MCR, pit_mcr.get());
148 io::outb(PIT_CD0, R as u8);
149 io::outb(PIT_CD0, (R >> 8) as u8);
150 }
151 }
152
153 /// Handler to call when a PIT interrupt occurs.
154 ///
155 /// This will increment the internal in-memory timer and dispatch any alarms.
156 pub fn handle_interrupt(&self) {
157 self.now.increment();
158 let now = self.now.get() as u32;
159
160 self.alarm.take().map(|alarm| {
161 if now >= alarm {
162 // Alarm has elapsed, so signal the client
163 self.client.map(|client| client.alarm());
164 } else {
165 // Alarm is still pending, check back later
166 self.alarm.replace(alarm);
167 }
168 });
169 }
170}
171
172impl<const R: u16> Time for Pit<'_, R> {
173 type Frequency = PitFreq<R>;
174
175 type Ticks = Ticks32;
176 fn now(&self) -> Self::Ticks {
177 let now = self.now.get();
178 (now as u32).into()
179 }
180}
181
182impl<'a, const R: u16> Alarm<'a> for Pit<'a, R> {
183 fn set_alarm_client(&self, client: &'a dyn AlarmClient) {
184 self.client.set(client);
185 }
186
187 fn set_alarm(&self, reference: Self::Ticks, dt: Self::Ticks) {
188 self.alarm.replace(reference.into_u32() + dt.into_u32());
189 }
190
191 fn get_alarm(&self) -> Self::Ticks {
192 self.alarm.map_or(0, |a| a).into()
193 }
194
195 fn disarm(&self) -> Result<(), ErrorCode> {
196 self.alarm.take();
197 Ok(())
198 }
199
200 fn is_armed(&self) -> bool {
201 self.alarm.is_some()
202 }
203
204 fn minimum_dt(&self) -> Self::Ticks {
205 1.into()
206 }
207}