complete_pic/
pic8259.rs

1//! 8259 PIC interface
2//!
3//! # What is the 8259 PIC?
4//! The 8259 PIC (or Programmable Interrupt Controller) is a crucial device in the x86 architecture.
5//! Its purpose is to manage hardware interrupts by sending them to the appropriate system interrupt, allowing the system to respond to hardware devices
6//! without losing time. The 8259 PIC makes handling hardware interrupts much easier. Without it, the hardware devices would have to be manually polled
7//! to check if they have anything they want to do. Time would be wasted trying to go to the hardware device and figure out what it wants to do.
8//!
9//! ## How does the 8259 PIC work?
10//! In a modern system, there are 2 8259 PICs, each with 8 inputs. The first is called the "master" while the other is called the "slave". If any input on the PIC is raised,
11//! it will signal that the input needs servicing by setting an internal bit. The PIC then does two checks. It checks whether that input is masked or not, and whether another interrupt
12//! is already pending. If the input is unmasked and no interrupt is pending, the PIC will raise the interrupt line. The slave PIC will send the IRQ number to the master so it can connect to
13//! the interrupt line. The master PIC will then check which PIC is responsible for answering the interrupts. If the master PIC is responsible, it will send the interrupt number to the processor.
14//! If the slave PIC is responsible, it will ask the slave to send the interrupt number to the processor. The PIC that answers then looks for the "vector offset" and adds it to the input line to
15//! compute the interrupt number. The processor then acts on that interrupt number.
16//!
17//! ## The 8259 PIC today
18//! The 8259 PIC is now a legacy device that has been replaced by the APIC (Advanced Programmable Interrupt Controller). The APIC is usable within multiprocessor systems.
19//! The APIC can do more sophisticated and complex things than the 8259 PIC.
20//!
21//! ## Where can I read more?
22//! The following links are useful to learning more about the 8259 PIC and interrupts:
23//! - [8259 PIC on OSDev Wiki](https://wiki.osdev.org/PIC)
24//! - [8259 PIC on Wikipedia](https://en.wikipedia.org/wiki/Intel_8259)
25//! - [Interrupts](https://wiki.osdev.org/IRQ)
26//!
27//! # Public API
28//!
29//! This module is based off of the already existing [pic8259](https://github.com/rust-osdev/pic8259) crate. Many of the public functions are marked as `unsafe` because it is
30//! very easy to cause undefined behavior by passing incorrect values that misconfigure the 8259 PIC or using the 8259 PIC incorrectly.
31//!
32//! # Usage
33//!
34//! Before utilizing this module, it is recommended that you wrap the `ChainedPics` struct in a `Mutex` to get safe mutable access to it. This can be done by using the `spin` crate.
35//! Make sure to add the `spin` crate to your `Cargo.toml` under `[dependencies]`. It should look like this:
36//!
37//! ```toml
38//! [dependencies]
39//! complete_pic = { version = "0.3.0", default-features = false, features = ["8259pic"] }
40//! spin = "0.9.8"
41//! ```
42//!
43//! Next, declare a `spin::Mutex<ChainedPics>` in a `static` variable:
44//!
45//! ```rust
46//! use complete_pic::pic8269::ChainedPics;
47//! use spin::Mutex;
48//!
49//! const PIC1_OFFSET: u8 = 32;
50//! const PIC2_OFFSET: u8 = PIC1_OFFSET + 8;
51//!
52//! // Map PIC interrupts to 0x20 through 0x2f.
53//! static PICS: Mutex<ChainedPics> = Mutex::new(unsafe { ChainedPics::new(PIC1_OFFSET, PIC2_OFFSET) });
54//! ```
55//!
56//! Next, initialize the PICs (make sure interrupts are disabled):
57//!
58//! ```rust
59//! unsafe { PICS.lock().initialize(); }
60//! # enable interrupts after initializing the PIC
61//! ```
62//!
63//! When you have finished handling an interrupt, call [`ChainedPics::notify_end_of_interrupt`]. This example handles the Intel Programmable Interval Timer (PIT), which uses the first IRQ index:
64//!
65//! ```rust
66//! #![feature(abi_x86_interrupt)]
67//!
68//! extern "x86-interrupt" fn timer_interrupt_handler(...) {
69//!    unsafe {
70//!        PICS.lock().notify_end_of_interrupt(PIC1_OFFSET);
71//!    }  
72//! }
73//! ```
74//!
75//! # Note
76//!
77//! Some boot protocols might mask all the IRQs from the legacy 8259 PIC, like Limine. Consult the documentation of the boot protocol you are using,
78//! so you don't encounter unexpected, confusing behavior. You can always change the interrupt masks of both PICs at the same time
79//! using [`ChainedPics::write_interrupt_masks`](ChainedPics::write_interrupt_masks).
80
81use x86_64::instructions::port::Port;
82
83/// The command I/O port of the master PIC.
84const MASTER_CMD: u16 = 0x20;
85
86/// The data I/O port of the master PIC.
87const MASTER_DATA: u16 = 0x21;
88
89/// The command I/O port of the slave PIC.
90const SLAVE_CMD: u16 = 0xA0;
91
92/// The data I/O port of the slave PIC.
93const SLAVE_DATA: u16 = 0xA1;
94
95/// PIC initialization command.
96const PIC_INIT: u8 = 0x11;
97
98/// PIC End of Interrupt command.
99const PIC_EIO: u8 = 0x20;
100
101/// The PIC 8086 mode.
102const PIC_MODE_8086: u8 = 0x01;
103
104/// An individual PIC chip.
105struct Pic {
106    /// The vector offset of the PIC chip.
107    offset: u8,
108
109    /// The PIC chip's command I/O port.
110    command: Port<u8>,
111
112    /// The PIC chip's data I/O port.
113    data: Port<u8>,
114}
115
116impl Pic {
117    /// Create an instance of a PIC chip by providing its
118    /// offset and the command and data I/O port addresses.
119    const fn new(offset: u8, command: u16, data: u16) -> Self {
120        Self {
121            offset,
122            command: Port::new(command),
123            data: Port::new(data),
124        }
125    }
126
127    /// Check if this PIC is in charge of handling the IRQ specified by the given ID
128    /// (each PIC handles 8 interrupts).
129    const fn handles_interrupt(&self, irq_id: u8) -> bool {
130        self.offset <= irq_id && irq_id < self.offset + 8
131    }
132
133    /// Signal that an IRQ has been handled and that the PIC is ready for more IRQs
134    unsafe fn end_of_interrupt(&mut self) {
135        self.command.write(PIC_EIO);
136    }
137
138    /// Read the interrupt mask of this PIC. When no command is issued, we can access the PIC's
139    /// interrupt mask via its data I/O port.
140    unsafe fn read_interrupt_mask(&mut self) -> u8 {
141        self.data.read()
142    }
143
144    /// Write to the interrupt mask of this PIC. When no command is issued, we can access the PIC's
145    /// interrupt mask via its data I/O port.
146    unsafe fn write_interrupt_mask(&mut self, mask: u8) {
147        self.data.write(mask);
148    }
149}
150
151/// The two 8259 PICs, chained together.
152pub struct ChainedPics {
153    pics: [Pic; 2],
154}
155
156impl ChainedPics {
157    /// Create an interface for the two 8259 PICs, specifying the desired interrupt offsets for both.
158    ///
159    /// # Safety
160    ///
161    /// It is important to pass the correct offsets. The default PIC configuration, which
162    /// sends interrupt vector numbers in the range of 0 to 15, is not usable in
163    /// protected mode since the numbers in that range are occupied by CPU exceptions in protected mode.
164    /// If you return to real mode from protected mode (for whatever reason), you must restore the PICs to their
165    /// default configurations.
166    pub const unsafe fn new(master_offset: u8, slave_offset: u8) -> Self {
167        Self {
168            pics: [
169                Pic::new(master_offset, MASTER_CMD, MASTER_DATA),
170                Pic::new(slave_offset, SLAVE_CMD, SLAVE_DATA),
171            ],
172        }
173    }
174
175    /// Initialize both of the PICs. You can read more about the initialization process by checking out
176    /// the following links:
177    ///
178    /// - <https://k.lse.epita.fr/internals/8259a_controller.html>
179    /// - <https://www.eeeguide.com/8259-programmable-interrupt-controller>
180    /// - <https://www.thesatya.com/8259.html>
181    ///
182    /// # Safety
183    ///
184    /// Please read the Safety section of [`ChainedPics::new`].
185    pub unsafe fn initialize(&mut self) {
186        // We need to add a delay between writes to our PICs, especially on
187        // older motherboards. But we don't necessarily have any kind of
188        // timers yet, because most of them require interrupts. Various
189        // older versions of Linux and other PC operating systems have
190        // worked around this by writing garbage data to port 0x80, which
191        // allegedly takes long enough to make everything work on most
192        // hardware.
193        let mut wait_port: Port<u8> = Port::new(0x80);
194        let mut wait = || wait_port.write(0);
195
196        // Save the original interrupts masks.
197        let saved_masks = self.read_interrupt_masks();
198
199        // Send each PIC the initialization command. This tells the PICs that
200        // a 3-byte initialization sequence will be sent to its data port.
201        self.pics[0].command.write(PIC_INIT);
202        wait();
203        self.pics[1].command.write(PIC_INIT);
204        wait();
205
206        // Byte 1: Setup the base offsets.
207        self.pics[0].data.write(self.pics[0].offset);
208        wait();
209        self.pics[1].data.write(self.pics[1].offset);
210        wait();
211
212        // Byte 2: Configure chaining between the two PIC chips.
213        self.pics[0].data.write(4);
214        wait();
215        self.pics[1].data.write(2);
216        wait();
217
218        // Byte 3: Set the PIC mode.
219        self.pics[0].data.write(PIC_MODE_8086);
220        wait();
221        self.pics[1].data.write(PIC_MODE_8086);
222        wait();
223
224        // Restore the saved masks.
225        self.write_interrupt_masks(saved_masks[0], saved_masks[1]);
226    }
227
228    /// Read the interrupt mask of the master PIC.
229    pub unsafe fn read_master_interrupt_mask(&mut self) -> u8 {
230        self.pics[0].read_interrupt_mask()
231    }
232
233    /// Read the interrupt mask of the slave PIC.
234    pub unsafe fn read_slave_interrupt_mask(&mut self) -> u8 {
235        self.pics[1].read_interrupt_mask()
236    }
237
238    /// Read the interrupt masks of both PICs.
239    pub unsafe fn read_interrupt_masks(&mut self) -> [u8; 2] {
240        [
241            self.pics[0].read_interrupt_mask(),
242            self.pics[1].read_interrupt_mask(),
243        ]
244    }
245
246    /// Write to the interrupt mask of the master PIC.
247    pub unsafe fn write_master_interrupt_mask(&mut self, mask: u8) {
248        self.pics[0].write_interrupt_mask(mask);
249    }
250
251    /// Write to the interrupt mask of the slave PIC.
252    pub unsafe fn write_slave_interrupt_mask(&mut self, mask: u8) {
253        self.pics[0].write_interrupt_mask(mask);
254    }
255
256    /// Write to the interrupt masks of both PICs.
257    pub unsafe fn write_interrupt_masks(&mut self, master_mask: u8, slave_mask: u8) {
258        self.pics[0].write_interrupt_mask(master_mask);
259        self.pics[1].write_interrupt_mask(slave_mask);
260    }
261
262    /// Convenience function for unmasking both PICs.
263    pub unsafe fn unmask(&mut self) {
264        self.write_interrupt_masks(u8::MIN, u8::MIN);
265    }
266
267    /// Convenience function for disabling both PICs by masking all interrupts.
268    pub unsafe fn disable(&mut self) {
269        self.write_interrupt_masks(u8::MAX, u8::MAX);
270    }
271
272    /// Check if the master or slave PIC handles the IRQ specified by the given ID.
273    pub fn handles_interrupt(&self, irq_id: u8) -> bool {
274        self.pics.iter().any(|p| p.handles_interrupt(irq_id))
275    }
276
277    /// Figure out which, if any, PIC in the chain needs to know about this interrupt. If the IRQ originated
278    /// from the master PIC, we only need to send the EOI command to the master PIC. Otherwise, the EOI
279    /// command needs to be sent to both PICs in the chain.
280    ///
281    /// # Safety
282    ///
283    /// It is important to pass the correct interrupt vector number. If the incorrect interrupt vector number
284    /// is passed, it can lead to deleting an unsent interrupt or a hanging system.    
285    pub unsafe fn notify_end_of_interrupt(&mut self, irq_id: u8) {
286        if self.handles_interrupt(irq_id) {
287            if self.pics[1].handles_interrupt(irq_id) {
288                self.pics[1].end_of_interrupt();
289            }
290
291            self.pics[0].end_of_interrupt();
292        }
293    }
294
295    /// Restore the vector offsets to the defaults, which do not conflict with anything in real mode.
296    #[doc(hidden)]
297    pub fn restore(&mut self) {
298        self.pics[0].offset = 0x00;
299        self.pics[1].offset = 0x08;
300    }
301}