hal_x86_64/time/pit.rs
1#![warn(missing_docs)]
2use super::InvalidDuration;
3use crate::cpu::{self, Port};
4use core::{
5 convert::TryFrom,
6 sync::atomic::{AtomicBool, Ordering},
7 time::Duration,
8};
9use mycelium_util::{
10 bits::{bitfield, enum_from_bits},
11 sync::{blocking::Mutex, spin::Spinlock},
12};
13
14/// Intel 8253/8254 Programmable Interval Timer (PIT).
15///
16/// The PIT is a simple timer, with three channels. The most interesting is
17/// channel 0, which is capable of firing an interrupt to the [8259 PIC] or [I/O
18/// APIC] on ISA interrupt vector 0. Channel 1 was used to time the DRAM refresh
19/// rate on ancient IBM PCs and is now generally unused (and may not be
20/// implemented in hardware), and channel 2 was connected to the IBM PC speaker
21/// and could be used to play sounds.
22///
23/// The PIT has a non-configurable [base frequency] of 1.193182 MHz, for
24/// [extremely cool reasons][reasons], but a 16-bit divisor can be used to
25/// determine what multiple of this base frequency each channel fires at.
26///
27/// [8259 PIC]: crate::interrupt::pic
28/// [I/O APIC]: crate::interrupt::apic::IoApic
29/// [base frequency]: Self::BASE_FREQUENCY_HZ
30/// [reasons]: https://en.wikipedia.org/wiki/Programmable_interval_timer#IBM_PC_compatible
31#[derive(Debug)]
32pub struct Pit {
33 /// PIT channel 0.
34 ///
35 /// The output from PIT channel 0 is connected to the PIC chip, so that it
36 /// generates an IRQ 0. Typically during boot the BIOS sets channel 0 with
37 /// a count of 65535 or 0 (which translates to 65536), which gives an output
38 /// frequency of 18.2065 Hz (or an IRQ every 54.9254 ms). Channel 0 is
39 /// probably the most useful PIT channel, as it is the only channel that is
40 /// connected to an IRQ. It can be used to generate an infinte series of
41 /// "timer ticks" at a frequency of your choice (as long as it is higher
42 /// than 18 Hz), or to generate single CPU interrupts (in "one shot" mode)
43 /// after programmable short delays (less than an 18th of a second).
44 ///
45 /// When choosing an operating mode, below, it is useful to remember that
46 /// the IRQ0 is generated by the rising edge of the Channel 0 output voltage
47 /// (ie. the transition from "low" to "high", only).
48 channel0: Port,
49 /// PIT channel 1.
50 ///
51 /// The output for PIT channel 1 was once used (in conjunction with the DMA
52 /// controller's channel 0) for refreshing the DRAM (Dynamic Random Access
53 /// Memory) or RAM. Typically, each bit in RAM consists of a capacitor which
54 /// holds a tiny charge representing the state of that bit, however (due to
55 /// leakage) these capacitors need to be "refreshed" periodically so that
56 /// they don't forget their state.
57 ///
58 /// On later machines, the DRAM refresh is done with dedicated hardware and
59 /// the PIT (and DMA controller) is no longer used. On modern computers
60 /// where the functionality of the PIT is implemented in a large scale
61 /// integrated circuit, PIT channel 1 is no longer usable and may not be
62 /// implemented at all.
63 ///
64 /// In general, this channel should not be used.
65 #[allow(dead_code)] // currently, there are no APIs for accessing channel 1
66 // TODO(eliza): add APIs for using channel 1 (if it's available)?
67 channel1: Port,
68 /// PIT channel 2.
69 ///
70 /// The output of PIT channel 2 is connected to the PC speaker, so the
71 /// frequency of the output determines the frequency of the sound produced
72 /// by the speaker. This is the only channel where the gate input can be
73 /// controlled by software (via bit 0 of I/O port 0x61), and the only
74 /// channel where its output (a high or low voltage) can be read by software
75 /// (via bit 5 of I/O port 0x61).
76 #[allow(dead_code)] // currently, there are no APIs for accessing channel 2
77 // TODO(eliza): add APIs for using channel 2 (if it's available)?
78 channel2: Port,
79 /// PIT command port.
80 command: Port,
81
82 /// If PIT channel 0 is configured in periodic mode, this stores the period
83 /// as a `Duration` so that we can reset to periodic mode after firing a
84 /// sleep interrupt.
85 channel0_interval: Option<Duration>,
86}
87
88/// Errors returned by [`Pit::start_periodic_timer`] and [`Pit::sleep_blocking`].
89#[derive(Debug, thiserror::Error)]
90pub enum PitError {
91 /// The periodic timer is already running.
92 #[error("PIT periodic timer is already running")]
93 AlreadyRunning,
94 /// A [`Pit::sleep_blocking`] call is in progress.
95 #[error("a PIT sleep is already in progress")]
96 SleepInProgress,
97 /// The provided duration was invalid.
98 #[error(transparent)]
99 InvalidDuration(#[from] InvalidDuration),
100}
101
102bitfield! {
103 struct Command<u8> {
104 /// BCD/binary mode.
105 ///
106 /// The "BCD/Binary" bit determines if the PIT channel will operate in
107 /// binary mode or BCD mode (where each 4 bits of the counter represent
108 /// a decimal digit, and the counter holds values from 0000 to 9999).
109 /// 80x86 PCs only use binary mode (BCD mode is ugly and limits the
110 /// range of counts/frequencies possible). Although it should still be
111 /// possible to use BCD mode, it may not work properly on some
112 /// "compatible" chips. For the "read back" command and the "counter
113 /// latch" command, this bit has different meanings.
114 const BCD_BINARY: bool;
115 /// Operating mode.
116 ///
117 /// The operating mode bits specify which mode the selected PIT
118 /// channel should operate in. For the "read back" command and the
119 /// "counter latch" command, these bits have different meanings.
120 /// There are 6 different operating modes. See the [`OperatingMode`]
121 /// enum for details on the PIT operating modes.
122 const MODE: OperatingMode;
123 /// Access mode.
124 ///
125 /// The access mode bits tell the PIT what access mode you wish to use
126 /// for the selected channel, and also specify the "counter latch"
127 /// command to the CTC. These bits must be valid on every write to the
128 /// mode/command register. For the "read back" command, these bits have
129 /// a different meaning. For the remaining combinations, these bits
130 /// specify what order data will be read and written to the data port
131 /// for the associated PIT channel. Because the data port is an 8 bit
132 /// I/O port and the values involved are all 16 bit, the PIT chip needs
133 /// to know what byte each read or write to the data port wants. For
134 /// "low byte only", only the lowest 8 bits of the counter value is read
135 /// or written to/from the data port. For "high byte only", only the
136 /// highest 8 bits of the counter value is read or written. For the
137 /// "low byte/high byte" mode, 16 bits are always transferred as a pair, with
138 /// the lowest 8 bits followed by the highest 8 bits (both 8 bit
139 /// transfers are to the same IO port, sequentially – a word transfer
140 /// will not work).
141 const ACCESS: AccessMode;
142 /// Channel select.
143 ///
144 /// The channel select bits select which channel is being configured,
145 /// and must always be valid on every write of the mode/command
146 /// register, regardless of the other bits or the type of operation
147 /// being performed. The ["read back"] (both bits set) is not supported on
148 /// the old 8253 chips but should be supported on all AT and later
149 /// computers except for PS/2 (i.e. anything that isn't obsolete will
150 /// support it).
151 ///
152 /// ["read back"]: ChannelSelect::ReadBack
153 const CHANNEL: ChannelSelect;
154 }
155}
156
157enum_from_bits! {
158 #[derive(Debug, Eq, PartialEq)]
159 enum ChannelSelect<u8> {
160 Channel0 = 0b00,
161 Channel1 = 0b01,
162 Channel2 = 0b10,
163 /// Readback command (8254 only)
164 Readback = 0b11,
165 }
166}
167
168enum_from_bits! {
169 #[derive(Debug, Eq, PartialEq)]
170 enum AccessMode<u8> {
171 /// Latch count value command
172 LatchCount = 0b00,
173 /// Access mode: low byte only
174 LowByte = 0b01,
175 /// Access mode: high byte only
176 HighByte = 0b10,
177 /// Access mode: both bytes
178 Both = 0b11,
179 }
180}
181
182enum_from_bits! {
183 #[derive(Debug, Eq, PartialEq)]
184 enum OperatingMode<u8> {
185 /// Mode 0 (interrupt on terminal count)
186 Interrupt = 0b000,
187 /// Mode 1 (hardware re-triggerable one-shot)
188 HwOneshot = 0b001,
189 /// Mode 2 (rate generator)
190 RateGenerator = 0b010,
191 /// Mode 3 (square wave generator)
192 SquareWave = 0b011,
193 /// Mode 4 (software triggered strobe)
194 SwStrobe = 0b100,
195 /// Mode 5 (hardware triggered strobe)
196 HwStrobe = 0b101,
197 /// Mode 2 (rate generator, same as `0b010`)
198 ///
199 /// I'm not sure why both of these exist, but whatever lol.
200 RateGenerator2 = 0b110,
201 /// Mode 3 (square wave generator, same as `0b011`)
202 ///
203 /// Again, I don't know why two bit patterns configure the same behavior but
204 /// whatever lol.
205 SquareWave2 = 0b111,
206 }
207}
208
209/// The PIT.
210///
211/// Since a system only has a single PIT, the `Pit` type cannot be constructed
212/// publicly and is represented as a singleton. It's stored in a [`Mutex`] in
213/// order to ensure that multiple CPU cores don't try to write conflicting
214/// configurations to the PIT's configuration ports.
215pub static PIT: Mutex<Pit, Spinlock> = Mutex::new_with_raw_mutex(Pit::new(), Spinlock::new());
216
217/// Are we currently sleeping on an interrupt?
218static SLEEPING: AtomicBool = AtomicBool::new(false);
219
220impl Pit {
221 /// The PIT's base frequency runs at roughly 1.193182 MHz, for [extremely
222 /// cool reasons][reasons].
223 ///
224 /// [reasons]: https://en.wikipedia.org/wiki/Programmable_interval_timer#IBM_PC_compatible
225 pub const BASE_FREQUENCY_HZ: usize = 1193180;
226 const TICKS_PER_MS: usize = Self::BASE_FREQUENCY_HZ / 1000;
227
228 const fn new() -> Self {
229 const BASE: u16 = 0x40;
230 Self {
231 channel0: Port::at(BASE),
232 channel1: Port::at(BASE + 1),
233 channel2: Port::at(BASE + 2),
234 command: Port::at(BASE + 3),
235 channel0_interval: None,
236 }
237 }
238
239 /// Sleep (by spinning) for `duration`.
240 ///
241 /// This function sets a flag indicating that a sleep is in progress, and
242 /// configures the PIT to fire an interrupt on channel 0 in `duration`. It then
243 /// spins until the flag is cleared by an interrupt handler.
244 ///
245 /// # Usage Notes
246 ///
247 /// This is a low-level way of sleeping, and is not recommended for use as a
248 /// system's primary method of sleeping for a duration. Instead, a timer wheel
249 /// or other way of tracking multiple sleepers should be constructed and
250 /// advanced based on a periodic timer. This function is provided primarily to
251 /// allow using the PIT to calibrate other timers as part of initialization
252 /// code, rather than for general purpose use in an operating system.
253 ///
254 /// In particular, using this function is subject to the following
255 /// considerations:
256 ///
257 /// - An interrupt handler for the PIT interrupt which clears the sleeping flag
258 /// must be installed. This is done automatically by the [`Controller::init`]
259 /// function in the [`interrupt`] module. If that interrupt handler is not
260 /// present, this function will spin forever!
261 /// - If the PIT is currently in periodic mode, it will be put in oneshot mode
262 /// when this function is called. This will temporarily disable the existing
263 /// periodic timer.
264 /// - This function returns an error if another CPU core is already sleeping. It
265 /// should generally be used only prior to the initialization of application
266 /// processors.
267 ///
268 /// # Returns
269 ///
270 /// - [`Ok`]`(())` after `duration` if a sleep was successfully completed.
271 /// - [`Err`]`(`[`InvalidDuration`]`)` if the provided duration was
272 /// too long.
273 ///
274 /// [`Controller::init`]: crate::interrupt::Controller::init
275 /// [`interrupt`]: crate::interrupt
276 pub fn sleep_blocking(&mut self, duration: Duration) -> Result<(), PitError> {
277 SLEEPING
278 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
279 .map_err(|_| PitError::SleepInProgress)?;
280 self.interrupt_in(duration)
281 .map_err(PitError::InvalidDuration)?;
282
283 // Tracing here is fine, because we are already sleeping...
284 tracing::debug!("started PIT sleep");
285
286 // spin until the sleep interrupt fires.
287 while SLEEPING.load(Ordering::Acquire) {
288 cpu::wait_for_interrupt();
289 }
290
291 // if we were previously in periodic mode, re-enable it.
292 if let Some(interval) = self.channel0_interval {
293 self.start_periodic_timer(interval)?;
294 }
295
296 Ok(())
297 }
298
299 /// Configures PIT channel 0 in periodic mode, to fire an interrupt every
300 /// time the provided `interval` elapses.
301 ///
302 /// # Returns
303 ///
304 /// - [`Ok`]`(())` if the timer was successfully configured in periodic
305 /// mode.
306 /// - [`Err`]`(`[`InvalidDuration`]`)` if the provided [`Duration`] was
307 /// too long.
308 #[tracing::instrument(
309 name = "Pit::start_periodic_timer"
310 level = tracing::Level::DEBUG,
311 skip(self),
312 fields(?interval),
313 err,
314 )]
315 pub fn start_periodic_timer(&mut self, interval: Duration) -> Result<(), PitError> {
316 if SLEEPING.load(Ordering::Acquire) {
317 return Err(PitError::SleepInProgress);
318 }
319
320 let interval_ms = usize::try_from(interval.as_millis()).map_err(|_| {
321 PitError::invalid_duration(
322 interval,
323 "PIT periodic timer interval as milliseconds would exceed a `usize`",
324 )
325 })?;
326 let interval_ticks = Self::TICKS_PER_MS * interval_ms;
327 let divisor = u16::try_from(interval_ticks).map_err(|_| {
328 PitError::invalid_duration(interval, "PIT channel 0 divisor would exceed a `u16`")
329 })?;
330
331 // store the periodic timer interval so we can reset later.
332 self.channel0_interval = Some(interval);
333
334 // Send the PIT the following command:
335 let command = Command::new()
336 // use the binary counter
337 .with(Command::BCD_BINARY, false)
338 // generate a square wave (set the frequency divisor)
339 .with(Command::MODE, OperatingMode::SquareWave)
340 // we are sending both bytes of the divisor
341 .with(Command::ACCESS, AccessMode::Both)
342 // and we're configuring channel 0
343 .with(Command::CHANNEL, ChannelSelect::Channel0);
344 self.send_command(command);
345 self.set_divisor(divisor);
346
347 tracing::info!(
348 ?interval,
349 interval_ms,
350 interval_ticks,
351 divisor,
352 "started PIT periodic timer"
353 );
354
355 Ok(())
356 }
357
358 /// Configure the PIT to send an IRQ 0 interrupt in `duration`.
359 ///
360 /// This configures the PIT in mode 0 (oneshot mode). Once the interrupt has
361 /// fired, in order to use the periodic timer, the pit must be put back into
362 /// periodic mode by calling [`Pit::start_periodic_timer`].
363 pub fn interrupt_in(&mut self, duration: Duration) -> Result<(), InvalidDuration> {
364 let duration_ms = usize::try_from(duration.as_millis()).map_err(|_| {
365 InvalidDuration::new(
366 duration,
367 "PIT interrupt duration as milliseconds would exceed a `usize`",
368 )
369 })?;
370 let target_time = Self::TICKS_PER_MS * duration_ms;
371 let divisor = u16::try_from(target_time).map_err(|_| {
372 InvalidDuration::new(
373 duration,
374 "PIT interrupt target tick count would exceed a `u16`",
375 )
376 })?;
377
378 let command = Command::new()
379 // use the binary counter
380 .with(Command::BCD_BINARY, false)
381 // generate a square wave (set the frequency divisor)
382 .with(Command::MODE, OperatingMode::Interrupt)
383 // we are sending both bytes of the divisor
384 .with(Command::ACCESS, AccessMode::Both)
385 // and we're configuring channel 0
386 .with(Command::CHANNEL, ChannelSelect::Channel0);
387 self.send_command(command);
388 self.set_divisor(divisor);
389
390 Ok(())
391 }
392
393 pub(crate) fn handle_interrupt() -> bool {
394 SLEEPING.swap(false, Ordering::AcqRel)
395 }
396
397 fn set_divisor(&mut self, divisor: u16) {
398 let low = divisor as u8;
399 let high = (divisor >> 8) as u8;
400 unsafe {
401 self.channel0.writeb(low); // write the low byte
402 self.channel0.writeb(high); // write the high byte
403 }
404 }
405
406 fn send_command(&self, command: Command) {
407 // tracing::debug!(?command, "Pit::send_command");
408 unsafe {
409 self.command.writeb(command.bits());
410 }
411 }
412}
413
414// === impl PitError ===
415
416impl PitError {
417 fn invalid_duration(duration: Duration, msg: &'static str) -> Self {
418 Self::InvalidDuration(InvalidDuration::new(duration, msg))
419 }
420}