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}