ostd/arch/x86/timer/
pit.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// SPDX-License-Identifier: MPL-2.0

#![expect(dead_code)]

//! The Programmable Interval Timer (PIT) chip (Intel 8253/8254) basically consists of an oscillator,
//! a prescaler and 3 independent frequency dividers. Each frequency divider has an output, which is
//! used to allow the timer to control external circuitry (for example, IRQ 0).
//!
//! Reference: <https://wiki.osdev.org/Programmable_Interval_Timer>
//!

use crate::{
    arch::{
        device::io_port::WriteOnlyAccess,
        kernel::{MappedIrqLine, IRQ_CHIP},
        timer::TIMER_FREQ,
    },
    io::{sensitive_io_port, IoPort},
    trap::irq::IrqLine,
};

/// PIT Operating Mode.
///
/// Usually, only the rate generator, which is used to determine the base frequency of other timers
/// (e.g. APIC Timer), and the Square wave generator, which is used to generate interrupts directly, are used.
///
/// Note that if IOAPIC is used to manage interrupts and square wave mode is enabled, the frequency at which
/// clock interrupts are generated is `Frequency/2`.
#[repr(u8)]
pub enum OperatingMode {
    /// Triggers an interrupt (only on channel 0) when the counter is terminated (1 -> 0).
    /// The data port needs to be reset before the next interrupt.
    /// ```text,ignore
    ///            software reload counter
    ///                      ⬇
    ///               +------+             +----
    ///               |      |             |
    /// --------------+      +-------------+
    /// ⬆             ⬆                    ⬆
    /// init()   counter 1 -> 0         counter 1 -> 0
    /// ```
    InterruptOnTerminalCount = 0b000,
    /// This mode is similar to `InterruptOnTerminalCount` mode, however counting doesn't start until
    /// a rising edge of the gate input is detected. For this reason it is not usable for PIT channels
    /// 0 or 1(where the gate input can't be changed).
    OneShotHardwareRetriggerable = 0b001,
    /// Rate generator, which produces a pulse at a fixed frequency.
    /// ```text,ignore
    /// init()   counter 2 -> 1    counter 2 -> 1
    /// ⬇             ⬇                ⬇
    /// --------------+  +-------------+
    ///               |  |             |
    ///               +--+             +--
    ///                  ⬆
    ///     counter 1 -> 0, auto reload counter
    /// ```
    RateGenerator = 0b010,
    /// In this mode, the current count is **decremented twice** on each falling edge of the input signal.
    /// The output will change state and then set to reload value.
    /// ```text,ignore
    /// init()  auto reload counter
    /// ⬇             ⬇
    /// --------------+              +--------------
    ///               |              |
    ///               +--------------+
    ///                              ⬆
    ///                       auto reload counter
    /// ```
    SquareWaveGenerator = 0b011,
    /// Similar to a Rate generator, but requires a software reset to start counting.
    /// ```text,ignore
    /// init()   counter: 1  software reload counter
    /// ⬇             ⬇              ⬇
    /// --------------+ +---------------------------+ +--
    ///               | |                           | |
    ///               +-+                           +-+
    ///                 ⬆
    ///              counter: 0
    /// ```
    SoftwareTriggeredStrobe = 0b100,
    /// This mode is similar to `SoftwareTriggeredStrobe` mode, except that it waits for the rising
    /// edge of the gate input to trigger (or re-trigger) the delay period (like `OneShotHardwareRetriggerable`
    /// mode).
    HardwareTriggeredStrobe = 0b101,
    // 0b110 -> Rate Generator
    // 0b111 -> Square Wave Generator
}

/// This bits tell the PIT what access mode is used for the selected channel.
#[repr(u8)]
enum AccessMode {
    /// When this command is sent, the current count is copied into latch register which can be read
    /// through the data port corresponding to the selected channel (I/O ports 0x40 to 0x42).
    LatchCountValueCommand = 0b00,
    /// Only the lowest 8 bits of the count value are used in this mode.
    LowByteOnly = 0b01,
    /// Only the highest 8 bits of the count value are used in this mode.
    HighByteOnly = 0b10,
    /// 16 bits are used in this mode. User should sent the lowest 8 bits followed by the highest 8 bits
    /// to the same data port.
    LowAndHighByte = 0b11,
}

/// Used to select the configured channel in the `MODE_COMMAND_PORT` of the PIT.
#[repr(u8)]
enum Channel {
    /// Channel 0. For more details, check `CHANNEL0_PORT` static variable
    Channel0 = 0b00,
    /// Channel 1. For more details, check `CHANNEL1_PORT` static variable
    Channel1 = 0b01,
    /// Channel 2. For more details, check `CHANNEL2_PORT` static variable
    Channel2 = 0b10,
    /// The read back command is a special command sent to the mode/command register.
    /// The register uses the following format if set to read back command:
    /// ```text
    /// Bits         Usage
    /// 7 and 6      Must be set for the read back command
    /// 5            Latch count flag (0 = latch count, 1 = don't latch count)
    /// 4            Latch status flag (0 = latch status, 1 = don't latch status)
    /// 3            Read back timer channel 2 (1 = yes, 0 = no)
    /// 2            Read back timer channel 1 (1 = yes, 0 = no)
    /// 1            Read back timer channel 0 (1 = yes, 0 = no)
    /// 0            Reserved
    /// ```
    /// Bits 1 to 3 of the read back command select which PIT channels are affected,
    /// and allow multiple channels to be selected at the same time.
    ///
    /// If bit 5 is clear, then any/all PIT channels selected with bits 1 to 3 will
    /// have their current count copied into their latch register.
    ///
    /// If bit 4 is clear, then for any/all PIT channels selected with bits 1 to 3,
    /// the next read of the corresponding data port will return a status byte.
    ///
    /// Ref: <https://wiki.osdev.org/Programmable_Interval_Timer#Read_Back_Command>.
    ReadBackCommand = 0b11,
}

sensitive_io_port! {
    unsafe {
        /// The output from PIT channel 0 is connected to the PIC chip and generate "IRQ 0".
        /// If connected to PIC, the IRQ0 will generate by the **rising edge** of the output voltage.
        static CHANNEL0_PORT: IoPort<u8, WriteOnlyAccess> = IoPort::new(0x40);

        /// The output from PIT channel 1 was once used for refreshing the DRAM or RAM so that
        /// the capacitors don't forget their state.
        ///
        /// On later machines, the DRAM refresh is done with dedicated hardware and this channel
        /// is no longer used.
        static CHANNEL1_PORT: IoPort<u8, WriteOnlyAccess> = IoPort::new(0x41);

        /// The output from PIT channel 2 is connected to the PC speaker, so the frequency of the
        /// output determines the frequency of the sound produced by the speaker. For more information,
        /// check <https://wiki.osdev.org/PC_Speaker>.
        static CHANNEL2_PORT: IoPort<u8, WriteOnlyAccess> = IoPort::new(0x42);

        /// PIT command port.
        /// ```text
        /// Bits         Usage
        /// 6 and 7      channel
        /// 4 and 5      Access mode
        /// 1 to 3       Operating mode
        /// 0            BCD/Binary mode: 0 = 16-bit binary, 1 = four-digit BCD
        /// ```
        static MODE_COMMAND_PORT: IoPort<u8, WriteOnlyAccess> = IoPort::new(0x43);
    }
}

const TIMER_RATE: u32 = 1193182;
const TIMER_INTERRUPT: u8 = 0; // ISA interrupt.

pub(crate) fn init(operating_mode: OperatingMode) {
    // Set PIT mode
    // Bit 0 is BCD/binary mode, which is always set to binary mode(value: 0)
    MODE_COMMAND_PORT.write(
        ((operating_mode as u8) << 1)
            | ((AccessMode::LowAndHighByte as u8) << 4)
            | ((Channel::Channel0 as u8) << 6),
    );

    // Set timer frequency
    const CYCLE: u32 = TIMER_RATE / TIMER_FREQ as u32;
    CHANNEL0_PORT.write((CYCLE & 0xFF) as _);
    CHANNEL0_PORT.write((CYCLE >> 8) as _);
}

/// Enables the interrupt line that is connected to the PIT.
pub(crate) fn enable_interrupt(irq_line: IrqLine) -> MappedIrqLine {
    IRQ_CHIP
        .get()
        .unwrap()
        .map_isa_pin_to(irq_line, TIMER_INTERRUPT)
        .unwrap()
}