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}