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}