x86_pic/lib.rs
1#![no_std]
2//! Support for the 8259 Programmable Interrupt Controller, which handles basic I/O interrupts.
3//! In multicore mode, we would apparently need to replace this with an APIC interface.
4//!
5//! A single PIC handles up to eight vectored priority interrupts for the CPU. By cascading 8259
6//! chips, we can increase interrupts up to 64 interrupt lines, however we only have two chained
7//! instances that can handle 16 lines. Can be programmed either in edge triggered, or in level
8//! triggered mode. PIC uses CHANNEL0 from the PIT (Programmable Interval Timer), the frequency of
9//! which can be adjusted based on it's configuration. Individual bits of IRQ register within the
10//! PIC can be masked out by the software.
11//!
12//! # Typical Application.
13//!
14//! ```
15//! // Somewhere alongside other statics...
16//! use x86-pic::ChainedPics;
17//! use spin::Mutex;
18//! pub static PROGRAMMABLE_INTERRUPT_CONTROLLER: Mutex<Option<ChainedPics>> = Mutex::new(None);
19//! ```
20//! # Fully nested
21//! ```
22//! ...
23//! // Somewhere in the OS initialization...
24//!
25//! /* Memory init code, IDT and stuff... */
26//!
27//! let mut pic = ChainedPics::new_contiguous(32); // i.e IRQ0 => interrupt vector 32.
28//! pic.initialize();
29//!
30//! PROGRAMMABLE_INTERRUPT_CONTROLLER.lock().replace(pic);
31//! /* Here we can enable interrupts freely :) */
32//!
33//! ...
34//! // Somewhere in interrupt handling module.
35//!
36//! #[no_mangle]
37//! unsafe extern "x86-interrupt" fn timer_interrupt_handler(mut stack_frame: InterruptStackFrame) {
38//!
39//! /*
40//! * Some interrupt handling logic and stuff.
41//! * */
42//!
43//! // Sending the EOI.
44//! PROGRAMMABLE_INTERRUPT_CONTROLLER.lock().as_mut().map(|pic| {
45//! pic.notify_end_of_interrupt(32)
46//! });
47//! }
48//! ...
49//!
50//! #[no_mangle]
51//! unsafe extern "x86-interrupt" fn parallel_port1_handler(mut stack_frame: InterruptStackFrame) {
52//! let mut pic = PROGRAMMABLE_INTERRUPT_CONTROLLER.lock();
53//!
54//! // Checking if the interrupt was legit. (please look `x86_pic::ChainedPics::is_spurious`)
55//! if pic.as_mut().map(|pic| !pic.is_spurious(39)).unwrap_or(false) {
56//!
57//! /*
58//! * Some interrupt handling logic and stuff.
59//! * */
60//! println!("The interrupt was legit!");
61//!
62//! // Sending the EOI.
63//! pic.as_mut().map(|pic| {
64//! pic.notify_end_of_interrupt(39)
65//! });
66//! } else {
67//! println!("A spurious interrupt! Do not handle that!");
68//! }
69//! }
70//! ```
71
72mod commands;
73mod chip;
74mod regs;
75mod post;
76
77use commands::*;
78use chip::*;
79
80pub use regs::*;
81
82/// **Operation Mode for PIC Controller**.
83///
84/// PIC supports several operation mode, most of which are most likely to be ignored on x86
85/// architecture, however some of them can be used to obtain some interesting results. See more
86/// information for each of them below.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum PicOperationMode {
89 /// Fully Nested Mode (Default Mode)
90 ///
91 /// This mode is entered after initialization unless another mode is programmed. The interrupt
92 /// requests are ordered in priority from 0 through 7, where 0 is the highest priority. When
93 /// interrupt is acknowledged the highest priority interrupt will be issued before the rest.
94 ///
95 /// On a regular x86 PC with two chained PICs, the IRQ0, which is an output of a PIT timer,
96 /// will be handled before others IRQ lines. Slave PIC has even smaller priority than first 7
97 /// masters IRQ lines, because it is mapped after the master PIC.
98 ///
99 /// # Use Case
100 ///
101 /// Use when the default priority level suits your needs. For example, if a PS/2 keyboard interrupt
102 /// (IRQ1) will be always services before the real time clock (IRQ8).
103 FullyNested,
104 /// Automatic Rotation Mode (Equal Priority Mode)
105 ///
106 /// Rotates the priority by the value specified in the current highest priority interrupt
107 /// within the ISR register. Basically each time the highest priority interrupt occur, it will
108 /// then be defined as a lowest priority interrupt. This way giving all interrupt sources an
109 /// equal service time from the CPU.
110 ///
111 /// # Use Case
112 ///
113 /// Use if you think that all interrupts deserve to be handled equally. This sometimes might
114 /// cause troubles with timers, specifically the PIT and RTC.
115 AutomaticRotation,
116 /// Special Mask Mode (Manual Priority Mode)
117 ///
118 /// Some applications might want to have a different priority mapping for the full software
119 /// control over the sequence of interrupts. During this mode the mask register is now used to
120 /// temporary disable certain interrupt levels as well as manually changing the priority level.
121 ///
122 /// # Use Case
123 ///
124 /// Critical sections that wish to disable some interrupts from the PIC but not all of them, or
125 /// some applications with specific timing requirements that require to temporary inhibit some
126 /// of interrupt levels to make sure that lower priority interrupts will meet timings accordingly.
127 SpecialMask,
128 /// Polled Mode (No interrupts)
129 ///
130 /// Do not use interrupts to obtain information from the peripherals but only listen for
131 /// upcoming changes. After the polled mode is enabled, data bus will provide a binary value of a
132 /// highest priority issued interrupt. Each read from the data port will be treated as an
133 /// interrupt acknowledge.
134 ///
135 /// # Use Case
136 ///
137 /// Probably the most useless one. Since it is very quick to turn this mode on and off, it can
138 /// be used to handle several interrupts in one handler by reading all values from the data
139 /// port until it will be equal to zero.
140 PolledMode,
141}
142
143/// A x86 setup of **Chained PICs**.
144///
145/// In most PCs there are one master and one slave PIC configuration, each having 8 inputs
146/// servicing 16 interrupts. This structure allows to easily initialize and control the x86
147/// configuration of PICs and configure all 16 interrupts for further handling.
148///
149/// Provides a minimal set of functions required to properly handle interrupts based on the
150/// currently used mode for each PIC.
151pub struct ChainedPics {
152 initialized: bool,
153 pub master: Pic,
154 pub slave: Pic,
155}
156
157impl ChainedPics {
158 /// Creates a new instance of Chained Pics.
159 ///
160 /// The master offset and slave offset are two offsets that are pointing to the first
161 /// interrupt vector of each 8259 chip.
162 ///
163 /// # Panics
164 ///
165 /// This function will panic if the provided offsets will overlap with each other or
166 /// collide with CPU exceptions.
167 pub const fn new(master_offset: u8, slave_offset: u8) -> Self {
168 assert!(master_offset >= 32 && slave_offset >= 32, "Both master and slave offsets must not overlap with CPU exceptions.");
169 assert!(master_offset.abs_diff(slave_offset) >= 8, "The master and slave offsets are overlapping with each other.");
170
171 unsafe { Self::new_unchecked(master_offset, slave_offset) }
172 }
173
174 /// Creates a new instance of a Chained Pics.
175 ///
176 /// The offset must point to the the chosen 16 entries from the IDT that will be used
177 /// for the software interrupts.
178 ///
179 /// This is a convenience function that maps the PIC1 and PIC2 to a
180 /// contiguous set of interrupts. This function is equivalent to
181 /// `Self::new(primary_offset, primary_offset + 8)`.
182 ///
183 /// # Panics
184 ///
185 /// This function will panic if the provided offset will overlap with cpu exceptions. It
186 /// will always prevent the overlapping between master and slave chips though
187 pub const fn new_contiguous(primary_offset: u8) -> Self {
188 Self::new(primary_offset, primary_offset + 8)
189 }
190
191 /// Returns true if initialized at least once.
192 pub fn is_initialized(&self) -> bool {
193 self.initialized
194 }
195
196 /// Initializes the PICs.
197 ///
198 /// This performs an initialization that is compatible with most x86 PC devices. Some archaic
199 /// devices may use only one PIC. For such possibilities a manual initialization of PIC
200 /// structure must be performed.
201 pub fn initialize(&mut self) {
202 unsafe {
203 self.master.init(PicIRQMapping::Master(Some(ICW3_MASTER::SLAVE2)), false);
204 self.slave.init(PicIRQMapping::Slave(ICW3_SLAVE::MASTER2), false);
205 }
206 if !self.is_initialized() { self.initialized = true }
207 }
208
209
210 /// Changes the operation mode for both master and slave PICs.
211 ///
212 /// This sends the OCW2 command and configures the current operation mode of the PIC logic.
213 /// Refer to [`PicOperationMode`] enum for more details. This function only checks the mode of
214 /// the master PIC, assuming that slave was not changed manually to something else.
215 ///
216 /// # Note
217 ///
218 /// The IRQ mask must be changed after switching from [`PicOperationMode::PolledMode`].
219 pub fn operation_mode_change(&mut self, new_op_mode: PicOperationMode) {
220 if self.master.operation_mode_current() != new_op_mode {
221 self.master.operation_mode_change(new_op_mode);
222 self.slave.operation_mode_change(new_op_mode);
223 }
224 }
225
226 /// Checks if the provided interrupt vector was caused by PIC properly.
227 ///
228 /// When an IRQ occurs, the PIC chip tells the CPU (via. the PIC's INTR line) that there's an interrupt,
229 /// and the CPU acknowledges this and waits for the PIC to send the interrupt vector. This creates a race
230 /// condition: if the IRQ disappears after the PIC has told the CPU there's an interrupt but before the
231 /// PIC has sent the interrupt vector to the CPU, then the CPU will be waiting for the PIC to tell it
232 /// which interrupt vector but the PIC won't have a valid interrupt vector to tell the CPU.
233 ///
234 /// Here we read if the interrupt is written within the ISR register, to be sure that we are
235 /// dealing with real interrupt. If a spurious interrupt was also sent from the slave PIC, the
236 /// master shall clear this flag, because he also thinks that it was a legit IRQ.
237 ///
238 /// # Important
239 ///
240 /// This is also the reason why sometimes an unimplemented handler functions are causing general protection
241 /// faults. PIC will cause an interrupt on the lowest priority IRQ, and the interrupt service
242 /// routine for something like hard disk controller is most likely not implemented in the stage
243 /// of configuring PICs.
244 ///
245 /// # Note (Fully Nested Mode)
246 ///
247 /// Spurious interrupts can only happen when the lowest priority IRQ are called. The fake interrupt number
248 /// is the lowest priority interrupt number for the corresponding PIC chip (IRQ 7 for the master PIC, and
249 /// IRQ 15 for the slave PIC).
250 ///
251 /// **Basically this means that you shall only check for spurious IRQs when it is a parallel
252 /// port interrupt (IRQ7) or secondary ATA channel interrupt (IRQ15)**
253 ///
254 /// # Note (Rotations)
255 ///
256 /// When modes with rotations are used: [`PicOperationMode::AutomaticRotation`],
257 /// [`PicOperationMode::SpecialMask`], the spurious interrupt can still only be found on IRQ7
258 /// for the master PIC or IRQ15 for the slave PIC.
259 ///
260 /// # Note (Polled Mode)
261 ///
262 /// Makes no sense in polled mode. Note also that in polled mode the ISR is always zero, so
263 /// this function will always mark proper IRQs as spurious.
264 ///
265 /// # Unsafe
266 ///
267 /// This function is unsafe only because it shall be used within the interrupt handler function
268 /// at the very start, to make sure that we are not handling a spurious interrupt. It is
269 /// completely forbidden to send an end of interrupt after this function.
270 pub unsafe fn is_spurious(&mut self, vec_id: u8) -> bool {
271 assert!(vec_id >= 32, "Cannot be one of the CPU exceptions.");
272
273 if self.slave.handles_interrupt(vec_id) {
274 if self.slave.is_spurious(vec_id) {
275 self.master.end_of_interrupt();
276 true
277 } else { false }
278 } else if self.master.handles_interrupt(vec_id) {
279 self.master.is_spurious(vec_id)
280 } else {
281 panic!("Provided interrupt is out of scope for both PICs.")
282 }
283 }
284
285 /// Notify a proper PIC chip that the interrupt was successfully handled and shall be cleared
286 /// within the ISR register.
287 ///
288 /// # Important
289 ///
290 /// To prevent spurious interrupts on lowest priority IRQs, use [`ChainedPics::is_spurious`]
291 /// and jump to the end of interrupt handler function if it returns true. If some interrupt was
292 /// caused by a hardware|software error, it should not be handled.
293 ///
294 /// **PIC must not receive a EOI command, when it is a spurious interrupts. It can clear
295 /// other interrupt's flag, which is a bigger trouble.**
296 ///
297 /// Lower priority interrupts vary based on the current mode. The function mentioned above
298 /// handles all logic required for each.
299 ///
300 /// # Unsafe
301 ///
302 /// This command must be used at the end of every interrupt that was issued by any of two PICs.
303 /// Make sure that this is a last command withon the interrupt service routine.
304 pub unsafe fn notify_end_of_interrupt(&mut self, vec_id: u8) {
305 assert!(vec_id >= 32, "Cannot be one of the CPU exceptions.");
306
307 if self.slave.handles_interrupt(vec_id) {
308 self.slave.end_of_interrupt();
309 self.master.end_of_interrupt();
310 } else
311 if self.master.handles_interrupt(vec_id) {
312 self.master.end_of_interrupt();
313 }
314 }
315
316 /// Disable both PICs interrupts.
317 ///
318 /// # Note
319 ///
320 /// This must be used when switching to APIC controller for handling interrupts.
321 pub fn disable(&mut self) {
322 unsafe { self.write_mask(IrqMask::all()) };
323 }
324
325 /// Enables both PICs interrupts.
326 ///
327 /// They are enabled by default after the initialization.
328 ///
329 /// # Warn
330 ///
331 /// This is not the initialization. Please see [`ChainedPics::initialize`]
332 pub fn enable(&mut self) {
333 unsafe { self.write_mask(IrqMask::empty()); }
334 }
335
336 /// Disables the slave PIC fully, i.e IRQ8 ... IRQ15.
337 pub fn disable_slave(&mut self) {
338 unsafe {
339 let mask = self.master.mask_read();
340 self.master.mask_write(
341 mask | OCW1::MASK_IRQ_2
342 );
343 }
344 }
345
346 /// Enables the slave PIC, i.e IRQ8 ... IRQ15.
347 pub fn enable_slave(&mut self) {
348 unsafe {
349 let mask = self.master.mask_read();
350 self.master.mask_write(
351 mask & !OCW1::MASK_IRQ_2
352 );
353 }
354 }
355
356 /// Gets the current IRQ mask.
357 pub fn get_mask(&mut self) -> IrqMask {
358 IrqMask::from_bits_truncate(
359 u16::from_le_bytes([
360 self.master.mask_read().bits(), self.slave.mask_read().bits()
361 ])
362 )
363 }
364
365 /// Masks the IRQ lines of chained PICs.
366 ///
367 /// # Unsafe
368 ///
369 /// Even though masking just disabled some interrupt lines, this function is marked as unsafe
370 /// due to undefined behavior that might happen when the OCW1 command is not right.
371 pub unsafe fn write_mask(&mut self, mask: IrqMask) {
372 let bytes = mask.bits().to_le_bytes();
373 unsafe {
374 self.master.mask_write(OCW1::from_bits_truncate(bytes[0]));
375 self.slave.mask_write(OCW1::from_bits_truncate(bytes[1]));
376 }
377 }
378
379 /// Perform something on the master PIC.
380 pub fn with_master<F>(&mut self, f: F) where
381 F: FnOnce(&mut Pic)
382 {
383 f(&mut self.master)
384 }
385
386 /// Perform something on the slave PIC.
387 pub fn with_slave<F>(&mut self, f: F) where
388 F: FnOnce(&mut Pic) {
389 f(&mut self.slave)
390 }
391
392 /// Creates a new instance of PIC controller.
393 ///
394 /// The master offset and slave offset are two offsets that are pointing to the first
395 /// interrupt vector of each 8259 chip.
396 ///
397 /// # Unsafe
398 ///
399 /// This function will not check if the chosen offsets overlap with each other or do they
400 /// overlap with CPU exceptions.
401 pub const unsafe fn new_unchecked(master_offset: u8, slave_offset: u8) -> Self {
402 Self {
403 initialized: false,
404 master: Pic::new(master_offset, 0x20, 0x21),
405 slave: Pic::new(slave_offset, 0xa0, 0xa1),
406 }
407 }
408}
409
410bitflags::bitflags! {
411 /// IRQ Flags for 16 PIC Interrupts.
412 ///
413 /// These represent the 16 possible IRQ lines that the PIC can handle. Each line corresponds to a specific hardware
414 /// interrupt source.
415 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
416 pub struct IrqMask: u16 {
417 /// **IRQ0** - System timer interrupt.
418 /// Triggered by the system timer (PIT). Essential for task switching and system ticks.
419 const IRQ0_TIMER = 1 << 0;
420
421 /// **IRQ1** - PS/2 Keyboard interrupt.
422 /// Generated when a key is pressed or released on the primary keyboard.
423 const IRQ1_PS2_KEYBOARD = 1 << 1;
424
425 /// **IRQ3** - Serial port 2 (COM2) interrupt.
426 /// Triggered by activity on the second serial port.
427 const IRQ3_SERIAL_PORT2 = 1 << 3;
428
429 /// **IRQ4** - Serial port 1 (COM1) interrupt.
430 /// Triggered by activity on the first serial port.
431 const IRQ4_SERIAL_PORT1 = 1 << 4;
432
433 /// **IRQ5** - Parallel port 2 interrupt (or sound card).
434 /// Often used for parallel port 2, but may be reassigned to other devices like a sound card.
435 const IRQ5_PARALLEL_PORT2 = 1 << 5;
436
437 /// **IRQ6** - Diskette drive (floppy disk controller) interrupt.
438 /// Used for floppy disk read/write operations.
439 const IRQ6_DISKETTE_DRIVE = 1 << 6;
440
441 /// **IRQ7** - Parallel port 1 interrupt.
442 /// Commonly associated with parallel port 1, typically used for printers.
443 const IRQ7_PARALLEL_PORT1 = 1 << 7;
444
445 /// **IRQ8** - Real-Time Clock (RTC) interrupt.
446 /// Generated by the RTC for timekeeping purposes.
447 const IRQ8_RTC = 1 << 8;
448
449 /// **IRQ9** - CGA vertical retrace interrupt (or general use).
450 /// Historically used for CGA video cards. Now typically available for general-purpose use.
451 const IRQ9_CGA_VERTICAL_RETRACE = 1 << 9;
452
453 /// **IRQ10** - Free for general-purpose use (first available line).
454 /// Not assigned to specific hardware by default.
455 const IRQ10_FREE_1 = 1 << 10;
456
457 /// **IRQ11** - Free for general-purpose use (second available line).
458 /// Not assigned to specific hardware by default.
459 const IRQ11_FREE_2 = 1 << 11;
460
461 /// **IRQ12** - PS/2 Mouse interrupt.
462 /// Triggered by activity on the PS/2 mouse.
463 const IRQ12_PS2_MOUSE = 1 << 12;
464
465 /// **IRQ13** - Floating Point Unit (FPU) interrupt.
466 /// Used for floating-point arithmetic errors or related conditions.
467 const IRQ13_FPU = 1 << 13;
468
469 /// **IRQ14** - Primary ATA channel interrupt.
470 /// Handles interrupts from devices on the primary ATA (IDE) bus, such as the main hard drive.
471 const IRQ14_PRIMARY_ATA = 1 << 14;
472
473 /// **IRQ15** - Secondary ATA channel interrupt.
474 /// Handles interrupts from devices on the secondary ATA (IDE) bus, such as additional drives.
475 const IRQ15_SECONDARY_ATA = 1 << 15;
476 }
477}