From 248ddcc72a3997c0ebdcd57e55c3372d597f7663 Mon Sep 17 00:00:00 2001 From: Notforest Date: Wed, 20 Nov 2024 23:54:22 +0100 Subject: [PATCH 01/15] Added new imports. --- Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c24e25a..2821c34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pic8259" -version = "0.11.0" -authors = ["Eric Kidd "] +version = "1.0.0" +authors = ["Eric Kidd , notforest "] edition = "2018" description = "Abstractions for the 8259 and 8259A interrupt controllers" @@ -18,6 +18,7 @@ nightly = [] stable = [] [dependencies] +bitflags = "2.3.2" x86_64 = { version = "0.15.0", default-features = false, features = [ "instructions", ] } From d5e16b97b7d346783d2f448493959c29df0b8cbc Mon Sep 17 00:00:00 2001 From: Notforest Date: Wed, 20 Nov 2024 23:55:03 +0100 Subject: [PATCH 02/15] Defined all possible ICW and OCW commands to manage the internal state of PIC controller. --- src/commands.rs | 227 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 src/commands.rs diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..fdf892f --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,227 @@ +//! This module provides the command words for interacting with the Programmable Interrupt Controller (PIC). +//! Proper operation of the PIC requires sending specific command sequences during initialization. However, +//! additional commands can be used during runtime for configuration and management. +//! +//! # Initialization Command Words (ICWs) +//! +//! ICWs are used to configure the PIC during its initialization phase. The process begins with +//! ICW1 and ICW2, which are mandatory. ICW1 also determines whether ICW4 will be required. +//! The initialization sequence is strictly ordered as ICW1 → ICW2 → ICW3 → ICW4. These commands +//! cannot be repeated individually. To modify the PIC's behavior, a complete reinitialization is necessary. +//! +//! # Operational Command Words (OCWs) +//! +//! OCWs are used to manage the PIC after initialization. Unlike ICWs, OCWs are optional, can be issued +//! in any order, and may be repeated multiple times as needed during the PIC's operation. + +const ICW1_CONST: u8 = 1 << 4; +const OCW3_CONST: u8 = 1 << 3; + +bitflags::bitflags! { + /// ICW1 commands are compulsory for initialization + /// + /// It specifies: + /// - single or multiple 8259As in system; + /// - 4 or 8 bit interval between the interrupt vector locations; + /// - the address bits of A7-A5 of the call instruction; + /// - edge triggered or level triggered interrupts; + /// - ICW4 is needed or not + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct ICW1: u8 { + /// If enabled the ICW4 is needed. + /// + /// # x86 + /// + /// Always needed in x86 + const IC4 = 1 | ICW1_CONST; + /// If enabled the single mode will be on. If not, the cascade mode will be on. + /// + /// # x86 + /// + /// Most PCs have two chained pics, so it shall be used only on archaic systems. + const SNGL = 1 << 1 | ICW1_CONST; + /// Call address interval. If enabled, the interval of 4, if disabled, the interval of 8. + /// + /// # x86 + /// + /// Unused in 8086 mode. + const ADI = 1 << 2 | ICW1_CONST; + /// If enabled, the level triggered mode will be used. If disabled, the edge triggered mode. + /// + /// # Note + /// + /// When used, interrupt request must be removed before the EOI comand is issued or the CPU + /// interrupts is enabled to prevent second interrupt from occuring. + const LTIM = 1 << 3 | ICW1_CONST; + /// Interrupt vector addresses. A7, A6, A5 (8085 mode only). + /// + /// # Note + /// + /// This value is a bit shift offset. + const INTERRUPT_VECTOR_ADDRESS = 5; + } + + /// ICW2 commands are compulsory for initialization. + /// + /// It stores the information regarding the interrupt vector address. a write command + /// following the ICW1 with A0 = 1 is interpreted as ICW2. It is used to write the high + /// order byte of the interrupt vector address of all the interrupts. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct ICW2: u8 { + /// The interrupt vector address in 8085 mode. + /// + /// # Note + /// + /// This value is a bit mask. + const INTERRUPT_VECTOR_ADDRESS_8085 = 0b111; + /// The interrupt vector address in 8086/8088 mode. + /// + /// # Note + /// + /// This value is a bit mask + const INTERRUPT_VECTOR_ADDRESS_8086_8088 = 0b11111000; + } + + /// ICW3 for Master PIC configuration. + /// + /// Defines which interrupt request lines (IR0 to IR7) are connected to a slave. + #[allow(non_camel_case_types)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct ICW3_MASTER: u8 { + /// Slave on IR0 + const SLAVE0 = 1; + /// Slave on IR1 + const SLAVE1 = 1 << 1; + /// Slave on IR2. (Used in x86) + const SLAVE2 = 1 << 2; + /// Slave on IR3 + const SLAVE3 = 1 << 3; + /// Slave on IR4 + const SLAVE4 = 1 << 4; + /// Slave on IR5 + const SLAVE5 = 1 << 5; + /// Slave on IR6 + const SLAVE6 = 1 << 6; + /// Slave on IR7 + const SLAVE7 = 1 << 7; + } + + /// ICW3 for Slave PIC configuration. + /// + /// Defines the slave PIC ID for the interrupt lines from the master. + #[allow(non_camel_case_types)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct ICW3_SLAVE: u8 { + /// Connected to master's IR0 + const MASTER0 = 0; + /// Connected to master's IR1 + const MASTER1 = 1; + /// Connected to master's IR2 + const MASTER2 = 2; + /// Connected to master's IR3 + const MASTER3 = 3; + /// Connected to master's IR4 + const MASTER4 = 4; + /// Connected to master's IR5 + const MASTER5 = 5; + /// Connected to master's IR6 + const MASTER6 = 6; + /// Connected to master's IR7 + const MASTER7 = 7; + } + + + /// ICW4 is loaded only if it is set in the ICW1. It specifies: + /// - whether to use special fully nested mode or non special fully nested mode; + /// - whether to use buffered mode or non buffered mode; + /// - whether to use automatic EOI or Normal EOI. + /// - CPU used is 8086/8088 or 8085. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct ICW4: u8 { + /// A 8066/8088 mode is used when set. Otherwise, uses 8050 mode. + const MPM = 1; + /// If set, using auto EOI. If not, use normal EOI. + const AEOI = 1 << 1; + /// Two bits which indicate about the buffered mode: + /// - non buffered mode (0x0 | 0x1); + const NON_BUFFERED_MODE = 0 << 2; + /// - buffered mode slave (0x2); + const BUFFERED_MODE_SLAVE = 2 << 2; + /// - buffered mode master (0x3); + const BUFFERED_MODE_MASTER = 3 << 2; + /// If set, the special fully nested mode is used, else, the not special mode is used. + const SFNM = 1 << 4; + } + + /// OCW1 is used to set and reset the mask bits in IMR. + /// + /// All IRQs, which are masked off won't be issued. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct OCW1: u8 { + const MASK_IRQ_0 = 1; + const MASK_IRQ_1 = 1 << 1; + const MASK_IRQ_2 = 1 << 2; + const MASK_IRQ_3 = 1 << 3; + const MASK_IRQ_4 = 1 << 4; + const MASK_IRQ_5 = 1 << 5; + const MASK_IRQ_6 = 1 << 6; + const MASK_IRQ_7 = 1 << 7; + } + + /// OCW2 is used for selecting the mode of operation of 8259. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct OCW2: u8 { + /// The first three bits are interrupt level on which action need to be performed. + /// + /// # Note + /// + /// This value is a bit shift offset. + const LEVEL = 0; + + /// Sets that the command is not EOI specific (i.e highest priority cleared first). + /// + /// # Note + /// This shall only be used in the fully nested mode. + const NON_SPECIFIC_EOI_COMMAND = 0b001 << 5; + /// Specific EOI command should be used. + const SPECIFIC_EOI_COMMAND = 0b011 << 5; + + /// Automatic rotation on non specific EOI command. + const ROTATE_ON_NON_SPECIFIC_EOI_COMMAND = 0b101 << 5; + /// Automatic rotation set. + const ROTATE_IN_AUTOMATIC_EOI_MODE_SET = 0b100 << 5; + /// Automatic rotation clear. + const ROTATE_IN_AUTOMATIC_EOI_MODE_CLEAR = 0x000 << 5; + /// Rotate on the specific EOI command. This combination requires level to be set. + const ROTATE_ON_SPECIFIC_EOI_COMMAND = 0b111 << 5; + + /// Sets the priority command. Level will be used. + const SET_PRIORITY_COMMAND = 0b110 << 5; + /// Does nothing. + const NO_OPERATION = 0b010 << 5; + } + + /// OCW3 is used to read the status of internal registers, managing the special mask and + /// polling commands. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct OCW3: u8 { + /// Read the Interrupt Request Register (IRR). + /// + /// Holds an IR1 bit vector with all interrupt events which are + /// awaiting to be services. Highest level interrupt is reset when + /// the CPU acknowledges it. + const READ_REG_IRR = 0b10 | OCW3_CONST; + /// Read the Interrupt Service Register (ISR). + /// + /// Tracks IRQ line currently being services. Updated by EOI command. + const READ_REG_ISR = 0b11 | OCW3_CONST; + + /// When set, poll the command. Do not poll otherwise. + const POLL = 1 << 2 | OCW3_CONST; + + /// Resets the special mask. + const RESET_SPECIAL_MASK = 0b10 << 5 | OCW3_CONST; + /// Sets the special mask. + const SET_SPECIAL_MASK = 0b10 << 5 | OCW3_CONST; + } +} From 4459842b72af4622fad10d4a8fefa0da28befaa4 Mon Sep 17 00:00:00 2001 From: Notforest Date: Wed, 20 Nov 2024 23:55:52 +0100 Subject: [PATCH 03/15] The POST CODE delay 'trick' is moved to the external module for better structure. --- src/post.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/post.rs diff --git a/src/post.rs b/src/post.rs new file mode 100644 index 0000000..33a218c --- /dev/null +++ b/src/post.rs @@ -0,0 +1,18 @@ +//! A small module represents the bios port for debug codes. + +use x86_64::instructions::port::PortWriteOnly; + +/// A Debug Board for POST codes. +/// +/// While the system is booting, the BIOS will output a series of debug codes to I/O port 0x80. +/// These are indended for debugging a non-booting system. In most desktop PCs, you can install +/// a POST code debug board, which is basically a small PCI (or ISA) slot board that decodes +/// I/O writes to I/O port 0x80 and displays the value via 7-segment LEDs. +/// +/// This port is used as a small delay generator, which provides a software little sleep(). It +/// could be used like that in places when a small delay is needed, but it must be really tiny. +/// +/// For real debug codes, visit: http://www.bioscentral.com. +pub fn post_debug_delay() { + unsafe { PortWriteOnly::::new(0x80).write(0) } +} // Public for possible external use. From aea835c95bc5364271a378873320ed372e56be39 Mon Sep 17 00:00:00 2001 From: Notforest Date: Wed, 20 Nov 2024 23:56:26 +0100 Subject: [PATCH 04/15] Definitions of PIC's internal IRR and ISR registers. --- src/regs.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/regs.rs diff --git a/src/regs.rs b/src/regs.rs new file mode 100644 index 0000000..f115d92 --- /dev/null +++ b/src/regs.rs @@ -0,0 +1,47 @@ +//! Defines PIC's internal ISR and IRR registers. +//! +//! The interrupts at the IR input lines are handled by two registers in cascade, the Interrupt Request Register (IRR) and the +//! In-Service (ISR). The IRR is used to store all the interrupt levels which are requesting service; and the ISR is used to +//! store all the interrupt levels which are being serviced. +//! +//! OS shall read those registers when deciding on sending the end of interrupt (EOI command). This way spurious IRQs can be +//! prevented and properly handled. + +bitflags::bitflags! { + /// Read the Interrupt Request Register (IRR). + /// + /// Holds an IR1 bit vector with all interrupt events which are + /// awaiting to be services. Highest level interrupt is reset when + /// the CPU acknowledges it. + /// + /// The interrupt request register shows the requested interrupts that have been raised + /// but are not being acknowledged yet. The value will be flushed after the end_of_interrupt method. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct IRR: u8 { + const IRQ0 = 1; + const IRQ1 = 1 << 1; + const IRQ2 = 1 << 2; + const IRQ3 = 1 << 3; + const IRQ4 = 1 << 4; + const IRQ5 = 1 << 5; + const IRQ6 = 1 << 6; + const IRQ7 = 1 << 7; + } + + /// Read the Interrupt Service Register (ISR). + /// + /// Tracks IRQ line currently being services. Updated by EOI command. + /// The interrupt status register inside the PIC chip, shows the info about which interrupts are + /// being serviced at that moment. The value will be flushed after the end_of_interrupt method. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct ISR: u8 { + const IRQ0 = 1; + const IRQ1 = 1 << 1; + const IRQ2 = 1 << 2; + const IRQ3 = 1 << 3; + const IRQ4 = 1 << 4; + const IRQ5 = 1 << 5; + const IRQ6 = 1 << 6; + const IRQ7 = 1 << 7; + } +} From 282678ef0d516f84130fe7e97610604bb0a6d963 Mon Sep 17 00:00:00 2001 From: Notforest Date: Wed, 20 Nov 2024 23:59:04 +0100 Subject: [PATCH 05/15] Added a separate module for a single PIC chip management. Improved the API and overall capability. --- src/chip.rs | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 src/chip.rs diff --git a/src/chip.rs b/src/chip.rs new file mode 100644 index 0000000..2768002 --- /dev/null +++ b/src/chip.rs @@ -0,0 +1,282 @@ +//! Defines a single configurable PIC chip. +//! +//! For most systems it is not necessary to manually configure each individual PIC. Please use +//! [´ChainedPics´] for convenient structure to manipulate on chained set of PICs located on the +//! x86 architecture. + +use x86_64::instructions::port::Port; +use crate::post::post_debug_delay; +use crate::*; + +/// Defines the PIC IRQ mappings (hardwired lines) for the PIC controller. +/// +/// The PIC can be configured either as a master or a slave device. This will change the upcoming +/// ICW3 command during the initialization. +/// +/// The master shall define it's IRQ line on which the slave is connected. The slave shall define +/// it's IRQ line on which it is connected to the master. Only one master can be used in the whole +/// connection. This allows up to 64 IRQ lines, when 8 slaves are +/// +/// # x86 +/// +/// For regular PC systems two PICs are chained together in a MASTER <-> SLAVE pair, allowing to +/// use up to 16 different IRQs. +#[derive(Debug, Clone, Copy)] +pub enum PicIRQMapping { + /// Master PIC chip mapping. + /// + /// Defines all interrupt request bits on which the slave PIC is connected. + Master(Option), + /// Slave PIC chip mapping. + /// + /// Defines one IRQ interrupt request line, which it is connected to. + Slave(ICW3_SLAVE), +} + +/// **PIC Chip** - Programmable Interrupt Controller. +/// +/// In x86 this can be either master or slave chip. Each chip has it's command port and data port. +/// The offset is used to handle different interrupt events. +#[derive(Debug)] +pub struct Pic { + /// The base offset to which our interrupts are mapped. + offset: u8, + /// Current operation mode for this specific PIC. + op_mode: PicOperationMode, + /// Automatic EOI flags. + automatic_interrupts: bool, + /// The processor I/O port on which we send commands. + command: Port, + /// The processor I/O port on which we send and receive data. + data: Port, +} + +impl Pic { + /// Creates a new instance of a PIC chip. + /// + /// According to the datasheet, PIC should be fully reinitialized with all four initialization + /// words again to exit this mode. + /// + /// # Offset + /// + /// Offset must be provided to not collide with x86 exceptions. Changing an offset means + /// reinitializing the PIC again completely from the very start. When several PICs are used, + /// their offsets shall not collide with each others. + /// + /// # Warn + /// + /// This does not include the initialization of the PIC chip. Use the [´Pic::init´] function to + /// perform a proper initialization. + /// + /// # Note + /// + /// This only creates a single PIC chip. Classic x86/x64 PC includes 2 chained PICs within. This function + /// is a public API only due to a possibility of only one PIC chip on some really archaic PC/XT systems. + pub const unsafe fn new(offset: u8, command_port: u16, data_port: u16) -> Self { + Self { + offset, + op_mode: PicOperationMode::FullyNested, // Used after initialization. + automatic_interrupts: false, + command: Port::new(command_port), + data: Port::new(data_port), + } + } + + /// Initialization of the PIC controller. + /// + /// All initialization words shall be provided to the controller in a very strict order: + /// - ICW1 → command port; + /// - ICW2 → data port; + /// - ICW3 → command port, IF (ICW1 bit D1 == 0) ELSE ignored; + /// - ICW4 → data port; + /// + /// For swapping to a different configuration this whole process must be repeated from the very + /// start. After this function OCW commands can be sent to the PIC controller. + /// + /// For using PICs configuration on regular x86 PCs, use [´ChainedPics´] structure, which provides + /// even safer function. + /// + /// # PIC Mapping + /// + /// This desides if the PIC is a master or a slave device. It also defines which lines are + /// connected to which depending on it's value. + /// + /// # Automatic Interrupts + /// + /// With this flag enabled PIC will automatically perform a EOI operation at the trailing edge of the + /// last interrupt acknowledge pulse from the CPU. This setting can only be used with a single + /// master chip. Basically that means that the end of interrupt command is not necessary after + /// a corresponding handler function handles the interrupt, however it does not work well with + /// chained PICs. + pub unsafe fn init(&mut self, pic_map: PicIRQMapping, automatic_interrupts: bool) { + unsafe { + // Saving the values that was before the data change. + let mask = self.data.read(); + // Generating initialization commands based on the chosen operation mode. + let icw1 = + ICW1::IC4 | + match pic_map { + // Using single mode when only one chip is presented. + PicIRQMapping::Master(opt) => if opt.is_none() { + ICW1::SNGL + } else { ICW1::empty() }, + _ => ICW1::empty(), + }; + // Only implementing the x86 compatible version here. + let icw2 = self.offset & ICW2::INTERRUPT_VECTOR_ADDRESS_8086_8088.bits(); + // In most PC systems two PICs are used. One PIC or more than two is also allowed. + let icw3 = pic_map; + // In x86 systems only this bit is used. + let icw4 = + ICW4::MPM | + if automatic_interrupts { ICW4::AEOI } else { ICW4::empty() }; + + // A short delay is required between each write due to the slowness of the controller. + /* ICW1 command. */ + self.command.write(icw1.bits()); + post_debug_delay(); + /* ICW2 command. */ + self.data.write(icw2); + post_debug_delay(); + /* ICW3 command. (If some) */ + match icw3 { + // Master might be alone or in a chained configuration. + PicIRQMapping::Master(opt) => opt.map(|some| { + self.command.write(some.bits()); + post_debug_delay(); + }).unwrap_or(()), + // If slave, at least one more chip should exist. + PicIRQMapping::Slave(some) => { + self.command.write(some.bits()); + post_debug_delay(); + }, + } + /* ICW4 command. */ + self.data.write(icw4.bits()); + post_debug_delay(); + + /* OCW1 command. */ + self.data.write(mask); + } + } + + /// Are we in charge of handling the specified interrupt? + /// (Each PIC handles 8 interrupts.) + pub fn handles_interrupt(&self, interrupt_id: u8) -> bool { + self.offset <= interrupt_id && interrupt_id < self.offset + 8 + } + + /// Reads the value of current operation mode used on this PIC. + pub fn operation_mode_current(&self) -> PicOperationMode { + self.op_mode + } + + /// Changes the current operation mode of this PIC. + /// + /// This sends the OCW2 command and configurest the current operation mode of the PIC logic. + /// Refer to [´PicOperationMode´] enum for more details. + pub fn operation_mode_change(&mut self, new_op_mode: PicOperationMode) { + if self.op_mode != new_op_mode { + unsafe { + self.command.write(match new_op_mode { + PicOperationMode::FullyNested => OCW2::NON_SPECIFIC_EOI_COMMAND.bits(), + PicOperationMode::AutomaticRotation => + if self.automatic_interrupts { + OCW2::ROTATE_IN_AUTOMATIC_EOI_MODE_SET.bits() + } else { + OCW2::ROTATE_ON_NON_SPECIFIC_EOI_COMMAND.bits() + }, + PicOperationMode::SpecialMask => OCW3::SET_SPECIAL_MASK.bits(), + PicOperationMode::PolledMode => OCW3::POLL.bits(), + }); + }; + self.op_mode = new_op_mode; + } + } + + /// Reads the value of the ISR. + /// + /// The interrupt status register inside the PIC chip, shows the info about which interrupts are + /// being serviced at that moment. The value will be flushed after the end_of_interrupt method. + pub fn read_isr(&mut self) -> ISR { + unsafe { + self.command.write( + OCW3::READ_REG_ISR.bits() + ); + ISR::from_bits_truncate(self.command.read()) + } + } + + /// Reads the value of the IRR. + /// + /// The interrupt request register shows the requested interrupts that have been raised + /// but are not being acknowledged yet. The value will be flushed after the end_of_interrupt method. + pub fn read_irr(&mut self) -> IRR { + unsafe { + self.command.write( + OCW3::READ_REG_IRR.bits() + ); + IRR::from_bits_truncate(self.command.read()) + } + } + + /// Read the current PIC mask. + /// + /// The mask defines all IRQ lines, that shall be ignored and not sent to the CPU. + pub fn mask_read(&mut self) -> OCW1 { + unsafe { + OCW1::from_bits_truncate(self.data.read()) + } + } + + /// Masks the requested IRQ lines. + /// + /// Sends the OCW1 command and masks unused IRQ lines. + /// + /// # Unsafe + /// + /// Even though masking just disabled some interrupt lines, this function is masked as unsafe + /// due to undefined behavior that might happen when the OCW1 command is not right. + pub unsafe fn mask_write(&mut self, ocw1: OCW1) { + self.data.write(ocw1.bits()); + } + + /// Sends a proper end of interrupt. + /// + /// # Note + /// + /// Does nothing if PIC is configured with automatic EOI flag. + pub fn end_of_interrupt(&mut self) { + match self.op_mode { + PicOperationMode::FullyNested => unsafe { self.non_specified_eoi() }, + PicOperationMode::SpecialMask => unimplemented!(), + _ => (), + } + } + + /// Performs an unsafe specified end of interrupt. + /// + /// # Unsafe + /// + /// Specified end of interrupt must be written together with an interrupt level to reset. + /// Reseting a wrong level will cause the interrupt handler to enter a loop. + pub unsafe fn specified_eoi(&mut self, level: u8) { + self.command.write( + OCW2::SPECIFIC_EOI_COMMAND.bits() | (level << OCW2::LEVEL.bits()) + ); + } + + /// Performs an unsafe non specified end of interrupt. + /// + /// # Unsafe + /// + /// Non specific EOI resets the highest ISR bit of those that are set. It is safe to use in + /// fully nested mode, which is the default and mostly used mode on PC, however will cause + /// wrong flags being cleared on different operation modes. + pub unsafe fn non_specified_eoi(&mut self) { + self.command.write( + OCW2::NON_SPECIFIC_EOI_COMMAND.bits() + ); + } +} + From 04c8a8ce5af40bd560b0dd566877f0e60d9e9eed Mon Sep 17 00:00:00 2001 From: Notforest Date: Thu, 21 Nov 2024 00:24:13 +0100 Subject: [PATCH 06/15] Refined the 'ChainedPics' structure with new API. Added minimal functionality. --- src/lib.rs | 413 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 263 insertions(+), 150 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bfed53d..31c3aee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,186 +1,299 @@ -//! Support for the 8259 Programmable Interrupt Controller, which handles -//! basic I/O interrupts. In multicore mode, we would apparently need to -//! replace this with an APIC interface. +#![no_std] +//! Support for the 8259 Programmable Interrupt Controller, which handles basic I/O interrupts. +//! In multicore mode, we would apparently need to replace this with an APIC interface. //! -//! The basic idea here is that we have two PIC chips, PIC1 and PIC2, and -//! that PIC2 is slaved to interrupt 2 on PIC 1. You can find the whole -//! story at http://wiki.osdev.org/PIC (as usual). Basically, our -//! immensely sophisticated modern chipset is engaging in early-80s -//! cosplay, and our goal is to do the bare minimum required to get -//! reasonable interrupts. +//! A single PIC handles up to eight vectored priority interrupts for the CPU. By cascading 8259 +//! chips, we can increase interrupts up to 64 interrupt lines, however we only have two chained +//! instances that can handle 16 lines. Can be programmed either in edge triggered, or in level +//! triggered mode. PIC uses CHANNEL0 from the PIT (Programmable Interval Timer), which's frequency +//! can be adjusted based on it's configuration. Individual bits of IRQ register within the PIC can +//! be masked out by the software. //! -//! The most important thing we need to do here is set the base "offset" -//! for each of our two PICs, because by default, PIC1 has an offset of -//! 0x8, which means that the I/O interrupts from PIC1 will overlap -//! processor interrupts for things like "General Protection Fault". Since -//! interrupts 0x00 through 0x1F are reserved by the processor, we move the -//! PIC1 interrupts to 0x20-0x27 and the PIC2 interrupts to 0x28-0x2F. If -//! we wanted to write a DOS emulator, we'd presumably need to choose -//! different base interrupts, because DOS used interrupt 0x21 for system -//! calls. - -#![no_std] - -use x86_64::instructions::port::Port; +//! The basic idea here is that we have two PIC chips, PIC1 and PIC2, and that PIC2 is slaved to +//! interrupt 2 on PIC 1. You can find the whole story at http://wiki.osdev.org/PIC (as usual). -/// Command sent to begin PIC initialization. -const CMD_INIT: u8 = 0x11; +mod commands; +mod chip; +mod regs; +mod post; -/// Command sent to acknowledge an interrupt. -const CMD_END_OF_INTERRUPT: u8 = 0x20; +use commands::*; +use chip::*; -// The mode in which we want to run our PICs. -const MODE_8086: u8 = 0x01; +pub use regs::*; -/// An individual PIC chip. This is not exported, because we always access -/// it through `Pics` below. -struct Pic { - /// The base offset to which our interrupts are mapped. - offset: u8, - - /// The processor I/O port on which we send commands. - command: Port, +/// **Operation Mode for PIC Controller**. +/// +/// PIC supports several operation mode, most of which are most likely to be ignored on x86 +/// architecture, however some of them can be used to obtain some interesting results. See more +/// information for each of them below. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PicOperationMode { + /// Fully Nested Mode (Default Mode) + /// + /// This mode is entered after intiialization unless another mode is programmed. The interrupt + /// requests are ordered in priority from 0 through 7, where 0 is the highest priority. When + /// interrupt is acknowledged the highest priority interrupt will be issued before the rest. + /// + /// On a regular x86 PC with two chained PICs, the IRQ0, which is an output of a PIT timer, + /// will be handled before others IRQ lines. Slave PIC has even smaller priority than first 7 + /// masters IRQ lines, because it is mapped after the master PIC. + /// + /// # Use Case + /// + /// Use when the default priority level suits your needs. For example, if a PS/2 keyboard interrupt + /// (IRQ1) will be always services before the real time clock (IRQ8). + FullyNested, + /// Automatic Rotation Mode (Equal Priority Mode) + /// + /// Rotates the priority by the value specified in the current highest priority interrupt + /// within the ISR register. Basically each time the highest priority interrupt occur, it will + /// then be defined as a lowest priority interrupt. This way giving all interrupt sources an + /// equal service time from the CPU. + /// + /// # Use Case + /// + /// Use if you think that all interrupts deserve to be handled equally. This sometimes might + /// cause troubles with timers, specifically the PIT and RTC. + AutomaticRotation, + /// Special Mask Mode (Manual Priority Mode) + /// + /// Some applications might want to have a different priority mapping for the full software + /// control over the sequence of interrupts. During this mode the mask register is now used to + /// temporarly disable certain interrupt levels (not interrupt lines) as well as manually + /// changing the priority level. + /// + /// # Use Case + /// + /// Critical sections that wish to disable some interrupts from the PIC but not all of them, or + /// some applications with specific timing requirements that require to temporarly inhibit some + /// of interrupt levels to make sure that lower priority interrupts will meet timings accordigly. + SpecialMask, + /// Polled Mode (No interrupts) + /// + /// Do not use interrupts to obtain information from the peripherals but only listen for + /// upcoming changes. After the polled mode is enabled, data bus will provide a binary value of a + /// highest priority issued interrupt. Each read from the data port will be treated as an + /// interrupt acknowledge. + /// + /// # Use Case + /// + /// Probably the most useless one. Since it is very quick to turn this mode on and off, it can + /// be used to handle several interrupts in one handler by reading all values from the data + /// port until it will be equal to zero. + PolledMode, +} - /// The processor I/O port on which we send and receive data. - data: Port, +/// A x86 setup of **Chained PICs**. +/// +/// In most PCs there are one master and one slace PIC configuration, each having 8 inputs +/// servicing 16 interrupts. This structure allows to easily initialize and control the x86 +/// configuration of PICs and configure all 16 interrupts for further handling. +/// +/// Provides a minimal set of functions required to properly handle interrupts based on the +/// currently used mode for each PIC. +pub struct ChainedPics { + initialized: bool, + pub master: Pic, + pub slave: Pic, } -impl Pic { - /// Are we in charge of handling the specified interrupt? - /// (Each PIC handles 8 interrupts.) - fn handles_interrupt(&self, interrupt_id: u8) -> bool { - self.offset <= interrupt_id && interrupt_id < self.offset + 8 +impl ChainedPics { + /// Creates a new instance of Chained Pics. + /// + /// The master offset and slave offset are two offsets that are pointing to the first + /// interrupt vector of each 8259 chip. + /// + /// # Panics + /// + /// This function will panic if the provided offsets will overlap with each other or + /// collide with CPU exceptions. + pub const fn new(master_offset: u8, slave_offset: u8) -> Self { + assert!(master_offset >= 32 || slave_offset >= 32, "Both master and slave offsets must not overlap with CPU exceptions."); + assert!(master_offset.abs_diff(slave_offset) >= 8, "The master and slave offsets are overlapping with each other."); + + unsafe { Self::new_unchecked(master_offset, slave_offset) } } - /// Notify us that an interrupt has been handled and that we're ready - /// for more. - unsafe fn end_of_interrupt(&mut self) { - self.command.write(CMD_END_OF_INTERRUPT); + /// Creates a new instance of a Chained Pics. + /// + /// The offset must point to the the chosen 16 entries from the IDT that will be used + /// for the software interrupts. + /// + /// This is a convenience function that maps the PIC1 and PIC2 to a + /// contiguous set of interrupts. This function is equivalent to + /// `Self::new(primary_offset, primary_offset + 8)`. + /// + /// # Panics + /// + /// This function will panic if the provided offset will overlap with cpu exceptions. It + /// will always prevent the overlapping between master and slave chips, because it makes + /// an offset for them sequentially. + pub const fn new_contiguous(primary_offset: u8) -> Self { + Self::new(primary_offset, primary_offset + 8) } - /// Reads the interrupt mask of this PIC. - unsafe fn read_mask(&mut self) -> u8 { - self.data.read() + /// Returns true if initialized at least once. + pub fn is_initialized(&self) -> bool { + self.initialized } - /// Writes the interrupt mask of this PIC. - unsafe fn write_mask(&mut self, mask: u8) { - self.data.write(mask) + /// Initializes the PICs. + /// + /// This performs an initialization that is compatible with most x86 PC devices. Some archaic + /// devices may use only one PIC. For such possibilities a manual initialization of PIC + /// structures must be performed. + /// + /// # Automatic Interrupts (Both Chips) + /// + /// With this flag enabled PIC will automatically perform a EOI operation at the trailing edge of the + /// last interrupt acknowledge pulse from the CPU. This setting can only be used with a single + /// master chip. Basically that means that the end of interrupt command is not necessary after + /// a corresponding handler function handles the interrupt, however it does not work well with + /// chained PICs. + pub fn initialize(&mut self, automatic_interrupts: bool) { + unsafe { + self.master.init( + PicIRQMapping::Master(Some(ICW3_MASTER::SLAVE2)), + automatic_interrupts, + ); + self.slave.init( + PicIRQMapping::Slave(ICW3_SLAVE::MASTER2), + automatic_interrupts, + ); + } + if !self.is_initialized() { self.initialized = true } } -} -/// A pair of chained PICs. This is the standard setup on x86. -pub struct ChainedPics { - pics: [Pic; 2], -} + /// Disable both PICs interrupts. + /// + /// # Note + /// + /// This must be used when switching to APIC controller for handling interrupts. This is also + /// mandatory even if the chip was never initialized by the OS. + pub fn disable(&mut self) { + unsafe { self.write_mask(IrqMask::all()) }; + } -impl ChainedPics { - /// Create a new interface for the standard PIC1 and PIC2, - /// specifying the desired interrupt offsets. - pub const unsafe fn new(offset1: u8, offset2: u8) -> ChainedPics { - ChainedPics { - pics: [ - Pic { - offset: offset1, - command: Port::new(0x20), - data: Port::new(0x21), - }, - Pic { - offset: offset2, - command: Port::new(0xA0), - data: Port::new(0xA1), - }, - ], - } + /// Gets the current IRQ mask. + pub fn get_mask(&mut self) -> IrqMask { + IrqMask::from_bits_truncate( + u16::from_le_bytes([ + self.master.mask_read().bits(), self.slave.mask_read().bits()] + ) + ) } - /// Create a new `ChainedPics` interface that will map the PICs contiguously starting at the given interrupt offset. + /// Masks the IRQ lines of chained PICs. /// - /// This is a convenience function that maps the PIC1 and PIC2 to a - /// contiguous set of interrupts. This function is equivalent to - /// `Self::new(primary_offset, primary_offset + 8)`. - pub const unsafe fn new_contiguous(primary_offset: u8) -> ChainedPics { - Self::new(primary_offset, primary_offset + 8) + /// # Unsafe + /// + /// Even though masking just disabled some interrupt lines, this function is masked as unsafe + /// due to undefined behavior that might happen when the OCW1 command is not right. + pub unsafe fn write_mask(&mut self, mask: IrqMask) { + let bytes = mask.bits().to_le_bytes(); + unsafe { + self.master.mask_write(OCW1::from_bits_truncate(bytes[0])); + self.slave.mask_write(OCW1::from_bits_truncate(bytes[1])); + } } - /// Initialize both our PICs. We initialize them together, at the same - /// time, because it's traditional to do so, and because I/O operations - /// might not be instantaneous on older processors. - pub unsafe fn initialize(&mut self) { - // We need to add a delay between writes to our PICs, especially on - // older motherboards. But we don't necessarily have any kind of - // timers yet, because most of them require interrupts. Various - // older versions of Linux and other PC operating systems have - // worked around this by writing garbage data to port 0x80, which - // allegedly takes long enough to make everything work on most - // hardware. Here, `wait` is a closure. - let mut wait_port: Port = Port::new(0x80); - let mut wait = || wait_port.write(0); - - // Save our original interrupt masks, because I'm too lazy to - // figure out reasonable values. We'll restore these when we're - // done. - let saved_masks = self.read_masks(); - - // Tell each PIC that we're going to send it a three-byte - // initialization sequence on its data port. - self.pics[0].command.write(CMD_INIT); - wait(); - self.pics[1].command.write(CMD_INIT); - wait(); - - // Byte 1: Set up our base offsets. - self.pics[0].data.write(self.pics[0].offset); - wait(); - self.pics[1].data.write(self.pics[1].offset); - wait(); - - // Byte 2: Configure chaining between PIC1 and PIC2. - self.pics[0].data.write(4); - wait(); - self.pics[1].data.write(2); - wait(); - - // Byte 3: Set our mode. - self.pics[0].data.write(MODE_8086); - wait(); - self.pics[1].data.write(MODE_8086); - wait(); - - // Restore our saved masks. - self.write_masks(saved_masks[0], saved_masks[1]) + /// Creates a new instance of PIC controller. + /// + /// The master offset and slave offset are two offsets that are pointing to the first + /// interrupt vector of each 8259 chip. + /// + /// # Unsafe + /// + /// This function will not check if the chosen offsets overlap with each other or do they + /// overlap with CPU exceptions. + pub const unsafe fn new_unchecked(master_offset: u8, slave_offset: u8) -> Self { + Self { + initialized: false, + master: Pic::new(master_offset, 0x20, 0x21), + slave: Pic::new(slave_offset, 0xa0, 0xa1), + } } /// Reads the interrupt masks of both PICs. + #[deprecated(since = "1.0.0", note = "Use [´get_mask´] to get a convenient 16-bit [´IrqMask´] structure instead.")] pub unsafe fn read_masks(&mut self) -> [u8; 2] { - [self.pics[0].read_mask(), self.pics[1].read_mask()] + [self.master.mask_read().bits(), self.slave.mask_read().bits()] } - + /// Writes the interrupt masks of both PICs. + #[deprecated(since = "1.0.0", note = "Use [´set_mask´] to apply the mask conveniently via [´IrqMask´] structure.")] pub unsafe fn write_masks(&mut self, mask1: u8, mask2: u8) { - self.pics[0].write_mask(mask1); - self.pics[1].write_mask(mask2); + self.master.mask_write(OCW1::from_bits_truncate(mask1)); + self.slave.mask_write(OCW1::from_bits_truncate(mask2)); } +} - /// Disables both PICs by masking all interrupts. - pub unsafe fn disable(&mut self) { - self.write_masks(u8::MAX, u8::MAX) - } +bitflags::bitflags! { + /// IRQ Flags for 16 PIC Interrupts. + /// + /// These represent the 16 possible IRQ lines that the PIC can handle. Each line corresponds to a specific hardware + /// interrupt source. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct IrqMask: u16 { + /// **IRQ0** - System timer interrupt. + /// Triggered by the system timer (PIT). Essential for task switching and system ticks. + const IRQ0_TIMER = 1 << 0; - /// Do we handle this interrupt? - pub fn handles_interrupt(&self, interrupt_id: u8) -> bool { - self.pics.iter().any(|p| p.handles_interrupt(interrupt_id)) - } + /// **IRQ1** - PS/2 Keyboard interrupt. + /// Generated when a key is pressed or released on the primary keyboard. + const IRQ1_PS2_KEYBOARD = 1 << 1; - /// Figure out which (if any) PICs in our chain need to know about this - /// interrupt. This is tricky, because all interrupts from `pics[1]` - /// get chained through `pics[0]`. - pub unsafe fn notify_end_of_interrupt(&mut self, interrupt_id: u8) { - if self.handles_interrupt(interrupt_id) { - if self.pics[1].handles_interrupt(interrupt_id) { - self.pics[1].end_of_interrupt(); - } - self.pics[0].end_of_interrupt(); - } + /// **IRQ3** - Serial port 2 (COM2) interrupt. + /// Triggered by activity on the second serial port. + const IRQ3_SERIAL_PORT2 = 1 << 3; + + /// **IRQ4** - Serial port 1 (COM1) interrupt. + /// Triggered by activity on the first serial port. + const IRQ4_SERIAL_PORT1 = 1 << 4; + + /// **IRQ5** - Parallel port 2 interrupt (or sound card). + /// Often used for parallel port 2, but may be reassigned to other devices like a sound card. + const IRQ5_PARALLEL_PORT2 = 1 << 5; + + /// **IRQ6** - Diskette drive (floppy disk controller) interrupt. + /// Used for floppy disk read/write operations. + const IRQ6_DISKETTE_DRIVE = 1 << 6; + + /// **IRQ7** - Parallel port 1 interrupt. + /// Commonly associated with parallel port 1, typically used for printers. + const IRQ7_PARALLEL_PORT1 = 1 << 7; + + /// **IRQ8** - Real-Time Clock (RTC) interrupt. + /// Generated by the RTC for timekeeping purposes. + const IRQ8_RTC = 1 << 8; + + /// **IRQ9** - CGA vertical retrace interrupt (or general use). + /// Historically used for CGA video cards. Now typically available for general-purpose use. + const IRQ9_CGA_VERTICAL_RETRACE = 1 << 9; + + /// **IRQ10** - Free for general-purpose use (first available line). + /// Not assigned to specific hardware by default. + const IRQ10_FREE_1 = 1 << 10; + + /// **IRQ11** - Free for general-purpose use (second available line). + /// Not assigned to specific hardware by default. + const IRQ11_FREE_2 = 1 << 11; + + /// **IRQ12** - PS/2 Mouse interrupt. + /// Triggered by activity on the PS/2 mouse. + const IRQ12_PS2_MOUSE = 1 << 12; + + /// **IRQ13** - Floating Point Unit (FPU) interrupt. + /// Used for floating-point arithmetic errors or related conditions. + const IRQ13_FPU = 1 << 13; + + /// **IRQ14** - Primary ATA channel interrupt. + /// Handles interrupts from devices on the primary ATA (IDE) bus, such as the main hard drive. + const IRQ14_PRIMARY_ATA = 1 << 14; + + /// **IRQ15** - Secondary ATA channel interrupt. + /// Handles interrupts from devices on the secondary ATA (IDE) bus, such as additional drives. + const IRQ15_SECONDARY_ATA = 1 << 15; } } From 3dc97a743a1fca97fd330926202d8a83f685f5d2 Mon Sep 17 00:00:00 2001 From: Notforest Date: Thu, 21 Nov 2024 19:13:29 +0100 Subject: [PATCH 07/15] Added more API. Now spurious interrupts can be prevented. --- src/chip.rs | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/chip.rs b/src/chip.rs index 2768002..9b0306a 100644 --- a/src/chip.rs +++ b/src/chip.rs @@ -4,6 +4,7 @@ //! [´ChainedPics´] for convenient structure to manipulate on chained set of PICs located on the //! x86 architecture. +use core::ops::Not; use x86_64::instructions::port::Port; use crate::post::post_debug_delay; use crate::*; @@ -40,7 +41,7 @@ pub enum PicIRQMapping { #[derive(Debug)] pub struct Pic { /// The base offset to which our interrupts are mapped. - offset: u8, + pub offset: u8, /// Current operation mode for this specific PIC. op_mode: PicOperationMode, /// Automatic EOI flags. @@ -160,10 +161,11 @@ impl Pic { } } - /// Are we in charge of handling the specified interrupt? - /// (Each PIC handles 8 interrupts.) + /// Checks if the provided IRQ id from the IDT matches this PIC. + /// + /// Each PIC may only handle up to 8 interrupts. pub fn handles_interrupt(&self, interrupt_id: u8) -> bool { - self.offset <= interrupt_id && interrupt_id < self.offset + 8 + (self.offset..self.offset + 1).contains(&interrupt_id) } /// Reads the value of current operation mode used on this PIC. @@ -186,7 +188,7 @@ impl Pic { } else { OCW2::ROTATE_ON_NON_SPECIFIC_EOI_COMMAND.bits() }, - PicOperationMode::SpecialMask => OCW3::SET_SPECIAL_MASK.bits(), + PicOperationMode::SpecialMask(_) => OCW3::SET_SPECIAL_MASK.bits(), PicOperationMode::PolledMode => OCW3::POLL.bits(), }); }; @@ -194,6 +196,27 @@ impl Pic { } } + /// Checks if the provided interrupt vector was caused by PIC properly. + /// + /// When an IRQ occurs, the PIC chip tells the CPU (via. the PIC's INTR line) that there's an interrupt, + /// and the CPU acknowledges this and waits for the PIC to send the interrupt vector. This creates a race + /// condition: if the IRQ disappears after the PIC has told the CPU there's an interrupt but before the + /// PIC has sent the interrupt vector to the CPU, then the CPU will be waiting for the PIC to tell it + /// which interrupt vector but the PIC won't have a valid interrupt vector to tell the CPU. + /// + /// Basically if the ISR bit for this flag is not set, but the interrupt service routine was + /// executed, that means it is spurious and interrupt must end right away. + /// + /// # Unsafe + /// + /// This function is only unsafe as it shall be only used within the interrupt handler function + /// at the very start, to make sure that we are not handling a spurious interrupt. It is + /// completely forbidden to send an end of interrupt after this function. + pub unsafe fn is_spurious(&mut self, id: u8) -> bool { + let irq = ISR::from_bits_truncate(id); + self.read_isr().not().contains(irq) + } + /// Reads the value of the ISR. /// /// The interrupt status register inside the PIC chip, shows the info about which interrupts are @@ -233,9 +256,14 @@ impl Pic { /// /// Sends the OCW1 command and masks unused IRQ lines. /// + /// # Huge Warn + /// + /// On special mask mode, this inhibits the priority level, not masks the interrupts + /// completely. See more info in [´PicOperationMode::SpecialMask´] + /// /// # Unsafe /// - /// Even though masking just disabled some interrupt lines, this function is masked as unsafe + /// Even though masking just disabled some interrupt lines, this function is marked as unsafe /// due to undefined behavior that might happen when the OCW1 command is not right. pub unsafe fn mask_write(&mut self, ocw1: OCW1) { self.data.write(ocw1.bits()); @@ -243,13 +271,18 @@ impl Pic { /// Sends a proper end of interrupt. /// + /// # Special Mask + /// + /// Before calling this function a special mask can be used with the enum to inhibit the + /// interrupt priority. See more info in [´PicOperationMode::SpecialMask´] + /// /// # Note /// /// Does nothing if PIC is configured with automatic EOI flag. pub fn end_of_interrupt(&mut self) { match self.op_mode { PicOperationMode::FullyNested => unsafe { self.non_specified_eoi() }, - PicOperationMode::SpecialMask => unimplemented!(), + PicOperationMode::SpecialMask(_) => todo!("Special mask mode"), // TODO!! _ => (), } } @@ -279,4 +312,3 @@ impl Pic { ); } } - From 639729bdd81a4cc06011e2eaa3ee809f0f2073e2 Mon Sep 17 00:00:00 2001 From: Notforest Date: Thu, 21 Nov 2024 19:14:13 +0100 Subject: [PATCH 08/15] Added better EOI handling. Added spurious interrupts handling. Improved the API. --- src/lib.rs | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 31c3aee..d525e93 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,10 +17,12 @@ mod chip; mod regs; mod post; +use bitflags::Flags; use commands::*; use chip::*; pub use regs::*; +use x86_64::structures::idt::InterruptStackFrame; /// **Operation Mode for PIC Controller**. /// @@ -68,7 +70,7 @@ pub enum PicOperationMode { /// Critical sections that wish to disable some interrupts from the PIC but not all of them, or /// some applications with specific timing requirements that require to temporarly inhibit some /// of interrupt levels to make sure that lower priority interrupts will meet timings accordigly. - SpecialMask, + SpecialMask(IrqMask), /// Polled Mode (No interrupts) /// /// Do not use interrupts to obtain information from the peripherals but only listen for @@ -165,6 +167,85 @@ impl ChainedPics { if !self.is_initialized() { self.initialized = true } } + /// Checks if the provided interrupt vector was caused by PIC properly. + /// + /// When an IRQ occurs, the PIC chip tells the CPU (via. the PIC's INTR line) that there's an interrupt, + /// and the CPU acknowledges this and waits for the PIC to send the interrupt vector. This creates a race + /// condition: if the IRQ disappears after the PIC has told the CPU there's an interrupt but before the + /// PIC has sent the interrupt vector to the CPU, then the CPU will be waiting for the PIC to tell it + /// which interrupt vector but the PIC won't have a valid interrupt vector to tell the CPU. + /// + /// Here we read if the interrupt is written within the ISR register, to be sure that we are + /// dealing with real interrupt. If a spurious interrupt was also sent from the slave PIC, the + /// master shall clear this flag, because he also thinks that it was a legit IRQ. + /// + /// # Important + /// + /// This is also the reason why sometimes an unimplemented handler functions are causing general protection + /// faults. PIC will cause an interrupt on the lowest priority IRQ, and the interrupt service + /// routine for something like hard disk controller is most likely not implemented in the stage + /// of configuring PICs. + /// + /// # Note (Fully Nested Mode) + /// + /// Spurious interrupts can only happen when the lowest priority IRQ are called. The fake interrupt number + /// is the lowest priority interrupt number for the corresponding PIC chip (IRQ 7 for the master PIC, and + /// IRQ 15 for the slave PIC). + /// + /// **Basically this means that you shall only check for spurious IRQs when it is a parallel + /// port interrupt (IRQ7) or secondary ATA channel interrupt (IRQ15)** + /// + /// # Note (Rotations) + /// + /// When modes with rotations are used: [´PicOperationMode::AutomaticRotation´], + /// [´PicOperationMode::SpecialMask´], the lowest priority priority IRQ is changed in time. For + /// such configurations, calling this function on every interrupt caused by PIC is probably + /// fine. + /// + /// # Unsafe + /// + /// This function is only unsafe as it shall be only used within the interrupt handler function + /// at the very start, to make sure that we are not handling a spurious interrupt. It is + /// completely forbidden to send an end of interrupt after this function. + pub unsafe fn is_spurious(&mut self, vec_id: u8) -> bool { + if self.slave.is_spurious(vec_id) { self.master.end_of_interrupt(); true } + else if self.master.is_spurious(vec_id) { true } + else { false } + } + + /// Notify a proper PIC chip that the interrupt was succesfully handled and shall be cleared + /// within the ISR register. + /// + /// # Important + /// + /// To prevent spurious interrupts on lowest priority IRQs, use [´ChainedPics::is_spurious´] + /// and jump to the end of interrupt handler function if it returns true. If some interrupt was + /// caused by a hardware|software mistake, it should not be handled. + /// + /// **PIC must not receive a EOI command, when it is a spurious interrupts. It will prevent + /// other interrupts from being handled, which is a bigger trouble.** + /// + /// Lower priority interrupts vary based on the current mode. The function mentioned above + /// handles all logic required for each. + /// + /// # Unsafe + /// + /// This command must be used at the end of every interrupt that was issued by any of two PICs. + /// Make sure that this is a last command withon the interrupt service routine. + /// + /// # Note + /// + /// Does nothing on chips with automatic interrupts flag enabled. See more in [´Pic´] + pub unsafe fn notify_end_of_interrupt(&mut self, vec_id: u8) { + if self.slave.handles_interrupt(vec_id) { + self.slave.end_of_interrupt(); + self.master.end_of_interrupt(); + } else + if self.master.handles_interrupt(vec_id) { + self.master.end_of_interrupt(); + } + } + /// Disable both PICs interrupts. /// /// # Note @@ -179,8 +260,8 @@ impl ChainedPics { pub fn get_mask(&mut self) -> IrqMask { IrqMask::from_bits_truncate( u16::from_le_bytes([ - self.master.mask_read().bits(), self.slave.mask_read().bits()] - ) + self.master.mask_read().bits(), self.slave.mask_read().bits() + ]) ) } @@ -188,7 +269,7 @@ impl ChainedPics { /// /// # Unsafe /// - /// Even though masking just disabled some interrupt lines, this function is masked as unsafe + /// Even though masking just disabled some interrupt lines, this function is marked as unsafe /// due to undefined behavior that might happen when the OCW1 command is not right. pub unsafe fn write_mask(&mut self, mask: IrqMask) { let bytes = mask.bits().to_le_bytes(); From 2773e4af0d2b3d88c2c76f0b36f10dc4be67289c Mon Sep 17 00:00:00 2001 From: Notforest Date: Sat, 23 Nov 2024 14:46:26 +0100 Subject: [PATCH 09/15] Fixed handles_interrupt function logic. Added proper handling for PolledMode. Added poll command to perform interrupt polling. --- src/chip.rs | 71 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/src/chip.rs b/src/chip.rs index 9b0306a..f6fd5ff 100644 --- a/src/chip.rs +++ b/src/chip.rs @@ -4,9 +4,9 @@ //! [´ChainedPics´] for convenient structure to manipulate on chained set of PICs located on the //! x86 architecture. -use core::ops::Not; use x86_64::instructions::port::Port; use crate::post::post_debug_delay; +use crate::regs::*; use crate::*; /// Defines the PIC IRQ mappings (hardwired lines) for the PIC controller. @@ -109,6 +109,9 @@ impl Pic { /// master chip. Basically that means that the end of interrupt command is not necessary after /// a corresponding handler function handles the interrupt, however it does not work well with /// chained PICs. + /// + /// Note that from a system standpoint, this mode should be used only when a nested multilevel interrupt + /// structure is not required within a single 8259A. pub unsafe fn init(&mut self, pic_map: PicIRQMapping, automatic_interrupts: bool) { unsafe { // Saving the values that was before the data change. @@ -165,7 +168,7 @@ impl Pic { /// /// Each PIC may only handle up to 8 interrupts. pub fn handles_interrupt(&self, interrupt_id: u8) -> bool { - (self.offset..self.offset + 1).contains(&interrupt_id) + (self.offset..self.offset + 8).contains(&interrupt_id) } /// Reads the value of current operation mode used on this PIC. @@ -180,17 +183,22 @@ impl Pic { pub fn operation_mode_change(&mut self, new_op_mode: PicOperationMode) { if self.op_mode != new_op_mode { unsafe { - self.command.write(match new_op_mode { - PicOperationMode::FullyNested => OCW2::NON_SPECIFIC_EOI_COMMAND.bits(), + match new_op_mode { + PicOperationMode::FullyNested => { + self.command.write(OCW2::ROTATE_IN_AUTOMATIC_EOI_MODE_CLEAR.bits()); + }, PicOperationMode::AutomaticRotation => if self.automatic_interrupts { - OCW2::ROTATE_IN_AUTOMATIC_EOI_MODE_SET.bits() + self.command.write(OCW2::ROTATE_IN_AUTOMATIC_EOI_MODE_SET.bits()); } else { - OCW2::ROTATE_ON_NON_SPECIFIC_EOI_COMMAND.bits() + self.command.write(OCW2::ROTATE_ON_NON_SPECIFIC_EOI_COMMAND.bits()); }, - PicOperationMode::SpecialMask(_) => OCW3::SET_SPECIAL_MASK.bits(), - PicOperationMode::PolledMode => OCW3::POLL.bits(), - }); + PicOperationMode::SpecialMask => self.command.write(OCW3::SET_SPECIAL_MASK.bits()), + PicOperationMode::PolledMode => { + self.mask_write(OCW1::all()); + self.command.write(OCW3::POLL.bits()); + }, + }; }; self.op_mode = new_op_mode; } @@ -213,8 +221,10 @@ impl Pic { /// at the very start, to make sure that we are not handling a spurious interrupt. It is /// completely forbidden to send an end of interrupt after this function. pub unsafe fn is_spurious(&mut self, id: u8) -> bool { - let irq = ISR::from_bits_truncate(id); - self.read_isr().not().contains(irq) + assert!(id >= 32 && self.offset + 8 > id, "The provided interrupt vector is outside of scope of this PIC chip."); + + let irq = ISR::from_bits_truncate(1 << id.saturating_sub(self.offset)); + !self.read_isr().contains(irq) } /// Reads the value of the ISR. @@ -224,7 +234,10 @@ impl Pic { pub fn read_isr(&mut self) -> ISR { unsafe { self.command.write( - OCW3::READ_REG_ISR.bits() + (OCW3::READ_REG_ISR | + if self.op_mode == PicOperationMode::PolledMode { + OCW3::POLL + } else { OCW3::empty() }).bits() ); ISR::from_bits_truncate(self.command.read()) } @@ -237,7 +250,10 @@ impl Pic { pub fn read_irr(&mut self) -> IRR { unsafe { self.command.write( - OCW3::READ_REG_IRR.bits() + (OCW3::READ_REG_IRR | + if self.op_mode == PicOperationMode::PolledMode { + OCW3::POLL + } else { OCW3::empty() }).bits() ); IRR::from_bits_truncate(self.command.read()) } @@ -252,6 +268,26 @@ impl Pic { } } + /// Poll the interrupt with highest priority. + /// + /// The value returned is a binary code of the highest priority level requesting service. Will + /// return None if the current mode is not [´PicOperationMode::PolledMode´]. + /// + /// # Note + /// + /// The interrupt is immediately acknowledged after the first read. + pub fn poll(&mut self) -> Option { + match self.op_mode { + PicOperationMode::PolledMode => unsafe { + let irq = self.command.read(); + // Acknowledge the IRQ right away. + self.specified_eoi(irq); + Some(irq) + }, + _ => None + } + } + /// Masks the requested IRQ lines. /// /// Sends the OCW1 command and masks unused IRQ lines. @@ -278,12 +314,13 @@ impl Pic { /// /// # Note /// - /// Does nothing if PIC is configured with automatic EOI flag. + /// Does nothing if PIC is configured with automatic EOI flag or in poll mode. pub fn end_of_interrupt(&mut self) { match self.op_mode { - PicOperationMode::FullyNested => unsafe { self.non_specified_eoi() }, - PicOperationMode::SpecialMask(_) => todo!("Special mask mode"), // TODO!! - _ => (), + PicOperationMode::SpecialMask => todo!("Special mask mode"), // TODO!! + PicOperationMode::AutomaticRotation => unsafe { self.non_specified_eoi() }, + PicOperationMode::PolledMode => (), + _ => unsafe { self.non_specified_eoi() }, } } From 47cc056b655244d19861cddf13f4ef535f95599f Mon Sep 17 00:00:00 2001 From: Notforest Date: Sat, 23 Nov 2024 14:47:52 +0100 Subject: [PATCH 10/15] Removed the automatic interrupts booleans, because it is impossible to use it in chained configuration. Added proper asserts. Added function to swap modes at runtime. Added example at the top of this module. --- src/lib.rs | 96 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d525e93..68b4faf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,18 +11,54 @@ //! //! The basic idea here is that we have two PIC chips, PIC1 and PIC2, and that PIC2 is slaved to //! interrupt 2 on PIC 1. You can find the whole story at http://wiki.osdev.org/PIC (as usual). +//! +//! # Typical Application. +//! +//! ``` +//! // Somewhere alongside other statics... +//! use pic8259::ChainedPics; +//! use spin::Mutex; +//! pub static PROGRAMMABLE_INTERRUPT_CONTROLLER: Mutex> = Mutex::new(None); +//! ``` +//! # Fully nested +//! ``` +//! ... +//! // Somewhere in the OS initialization... +//! +//! /* Memory init code, IDT and stuff... */ +//! +//! let mut pic = ChainedPics::new_contiguous(32); // i.e IRQ0 => interrupt vector 32. +//! pic.initialize(); +//! +//! PROGRAMMABLE_INTERRUPT_CONTROLLER.lock().replace(pic); +//! /* Here we can enable interrupts freely :) */ +//! +//! ... +//! // Somewhere in interrupt handling module. +//! +//! #[no_mangle] +//! unsafe extern "x86-interrupt" fn timer_interrupt_handler(mut stack_frame: InterruptStackFrame) { +//! +//! /* +//! * Some interrupt handling logic and stuff. +//! * */ +//! +//! // Sending the EOI. +//! PROGRAMMABLE_INTERRUPT_CONTROLLER.lock().as_mut().map(|pic| { +//! pic.notify_end_of_interrupt(32) +//! }); +//! } +//! ``` mod commands; mod chip; mod regs; mod post; -use bitflags::Flags; use commands::*; use chip::*; pub use regs::*; -use x86_64::structures::idt::InterruptStackFrame; /// **Operation Mode for PIC Controller**. /// @@ -70,7 +106,7 @@ pub enum PicOperationMode { /// Critical sections that wish to disable some interrupts from the PIC but not all of them, or /// some applications with specific timing requirements that require to temporarly inhibit some /// of interrupt levels to make sure that lower priority interrupts will meet timings accordigly. - SpecialMask(IrqMask), + SpecialMask, /// Polled Mode (No interrupts) /// /// Do not use interrupts to obtain information from the peripherals but only listen for @@ -111,7 +147,7 @@ impl ChainedPics { /// This function will panic if the provided offsets will overlap with each other or /// collide with CPU exceptions. pub const fn new(master_offset: u8, slave_offset: u8) -> Self { - assert!(master_offset >= 32 || slave_offset >= 32, "Both master and slave offsets must not overlap with CPU exceptions."); + assert!(master_offset >= 32 && slave_offset >= 32, "Both master and slave offsets must not overlap with CPU exceptions."); assert!(master_offset.abs_diff(slave_offset) >= 8, "The master and slave offsets are overlapping with each other."); unsafe { Self::new_unchecked(master_offset, slave_offset) } @@ -145,28 +181,27 @@ impl ChainedPics { /// This performs an initialization that is compatible with most x86 PC devices. Some archaic /// devices may use only one PIC. For such possibilities a manual initialization of PIC /// structures must be performed. - /// - /// # Automatic Interrupts (Both Chips) - /// - /// With this flag enabled PIC will automatically perform a EOI operation at the trailing edge of the - /// last interrupt acknowledge pulse from the CPU. This setting can only be used with a single - /// master chip. Basically that means that the end of interrupt command is not necessary after - /// a corresponding handler function handles the interrupt, however it does not work well with - /// chained PICs. - pub fn initialize(&mut self, automatic_interrupts: bool) { + pub fn initialize(&mut self) { unsafe { - self.master.init( - PicIRQMapping::Master(Some(ICW3_MASTER::SLAVE2)), - automatic_interrupts, - ); - self.slave.init( - PicIRQMapping::Slave(ICW3_SLAVE::MASTER2), - automatic_interrupts, - ); + self.master.init(PicIRQMapping::Master(Some(ICW3_MASTER::SLAVE2)), false); + self.slave.init(PicIRQMapping::Slave(ICW3_SLAVE::MASTER2), false); } if !self.is_initialized() { self.initialized = true } } + + /// Changes the operation mode for both master and slave PICs. + /// + /// This sends the OCW2 command and configurest the current operation mode of the PIC logic. + /// Refer to [´PicOperationMode´] enum for more details. This function only checks the mode of + /// the master PIC, assuming that slave was not changed manually to something else. + pub fn operation_mode_change(&mut self, new_op_mode: PicOperationMode) { + if self.master.operation_mode_current() != new_op_mode { + self.master.operation_mode_change(new_op_mode); + self.slave.operation_mode_change(new_op_mode); + } + } + /// Checks if the provided interrupt vector was caused by PIC properly. /// /// When an IRQ occurs, the PIC chip tells the CPU (via. the PIC's INTR line) that there's an interrupt, @@ -208,9 +243,16 @@ impl ChainedPics { /// at the very start, to make sure that we are not handling a spurious interrupt. It is /// completely forbidden to send an end of interrupt after this function. pub unsafe fn is_spurious(&mut self, vec_id: u8) -> bool { - if self.slave.is_spurious(vec_id) { self.master.end_of_interrupt(); true } - else if self.master.is_spurious(vec_id) { true } - else { false } + assert!(vec_id >= 32, "Cannot be one of the CPU exceptions."); + + if self.slave.handles_interrupt(vec_id) { + if self.slave.is_spurious(vec_id) { + self.master.end_of_interrupt(); + true + } else { false } + } else if self.master.handles_interrupt(vec_id) { + self.master.is_spurious(vec_id) + } else { false } } /// Notify a proper PIC chip that the interrupt was succesfully handled and shall be cleared @@ -232,11 +274,9 @@ impl ChainedPics { /// /// This command must be used at the end of every interrupt that was issued by any of two PICs. /// Make sure that this is a last command withon the interrupt service routine. - /// - /// # Note - /// - /// Does nothing on chips with automatic interrupts flag enabled. See more in [´Pic´] pub unsafe fn notify_end_of_interrupt(&mut self, vec_id: u8) { + assert!(vec_id >= 32, "Cannot be one of the CPU exceptions."); + if self.slave.handles_interrupt(vec_id) { self.slave.end_of_interrupt(); self.master.end_of_interrupt(); From 98ff14cf73d45d4f3b94cc126e3b3ffc80d69682 Mon Sep 17 00:00:00 2001 From: Notforest Date: Sun, 24 Nov 2024 00:42:57 +0100 Subject: [PATCH 11/15] Fully fixed the polling mode routine. --- src/chip.rs | 45 ++++++++++++++++++++------------------------- src/lib.rs | 7 +++---- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/chip.rs b/src/chip.rs index f6fd5ff..4f1d196 100644 --- a/src/chip.rs +++ b/src/chip.rs @@ -4,6 +4,7 @@ //! [´ChainedPics´] for convenient structure to manipulate on chained set of PICs located on the //! x86 architecture. +use bitflags::Flags; use x86_64::instructions::port::Port; use crate::post::post_debug_delay; use crate::regs::*; @@ -219,7 +220,7 @@ impl Pic { /// /// This function is only unsafe as it shall be only used within the interrupt handler function /// at the very start, to make sure that we are not handling a spurious interrupt. It is - /// completely forbidden to send an end of interrupt after this function. + /// completely forbidden to send an EOI, if this function evaluates to true! pub unsafe fn is_spurious(&mut self, id: u8) -> bool { assert!(id >= 32 && self.offset + 8 > id, "The provided interrupt vector is outside of scope of this PIC chip."); @@ -233,13 +234,12 @@ impl Pic { /// being serviced at that moment. The value will be flushed after the end_of_interrupt method. pub fn read_isr(&mut self) -> ISR { unsafe { - self.command.write( - (OCW3::READ_REG_ISR | - if self.op_mode == PicOperationMode::PolledMode { - OCW3::POLL - } else { OCW3::empty() }).bits() - ); - ISR::from_bits_truncate(self.command.read()) + self.command.write(OCW3::READ_REG_ISR.bits()); + let isr = ISR::from_bits_truncate(self.command.read()); + if self.op_mode == PicOperationMode::PolledMode { + self.command.write(OCW3::POLL.bits()) + } + isr } } @@ -249,13 +249,12 @@ impl Pic { /// but are not being acknowledged yet. The value will be flushed after the end_of_interrupt method. pub fn read_irr(&mut self) -> IRR { unsafe { - self.command.write( - (OCW3::READ_REG_IRR | - if self.op_mode == PicOperationMode::PolledMode { - OCW3::POLL - } else { OCW3::empty() }).bits() - ); - IRR::from_bits_truncate(self.command.read()) + self.command.write(OCW3::READ_REG_IRR.bits()); + let irr = IRR::from_bits_truncate(self.command.read()); + if self.op_mode == PicOperationMode::PolledMode { + self.command.write(OCW3::POLL.bits()) + } + irr } } @@ -275,15 +274,11 @@ impl Pic { /// /// # Note /// - /// The interrupt is immediately acknowledged after the first read. + /// The interrupt is immediately acknowledged after the first read. According to the datasheet: + /// When poll command is issued, the 8259 treats the next RD pulse as an interrupt acknowledge. pub fn poll(&mut self) -> Option { match self.op_mode { - PicOperationMode::PolledMode => unsafe { - let irq = self.command.read(); - // Acknowledge the IRQ right away. - self.specified_eoi(irq); - Some(irq) - }, + PicOperationMode::PolledMode => unsafe { Some(self.command.read()) }, _ => None } } @@ -317,10 +312,10 @@ impl Pic { /// Does nothing if PIC is configured with automatic EOI flag or in poll mode. pub fn end_of_interrupt(&mut self) { match self.op_mode { - PicOperationMode::SpecialMask => todo!("Special mask mode"), // TODO!! + PicOperationMode::FullyNested => unsafe { self.non_specified_eoi() }, PicOperationMode::AutomaticRotation => unsafe { self.non_specified_eoi() }, - PicOperationMode::PolledMode => (), - _ => unsafe { self.non_specified_eoi() }, + PicOperationMode::SpecialMask => todo!("Special mask mode"), // TODO!! + PicOperationMode::PolledMode => (), // Interrupt is acknowledged once the command port is read. } } diff --git a/src/lib.rs b/src/lib.rs index 68b4faf..0af6bff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -233,13 +233,12 @@ impl ChainedPics { /// # Note (Rotations) /// /// When modes with rotations are used: [´PicOperationMode::AutomaticRotation´], - /// [´PicOperationMode::SpecialMask´], the lowest priority priority IRQ is changed in time. For - /// such configurations, calling this function on every interrupt caused by PIC is probably - /// fine. + /// [´PicOperationMode::SpecialMask´], the spurious interrupt can still only be found on IRQ7 + /// for the master PIC or IRQ15 for the slave PIC. /// /// # Unsafe /// - /// This function is only unsafe as it shall be only used within the interrupt handler function + /// This function is unsafe only because it shall be used within the interrupt handler function /// at the very start, to make sure that we are not handling a spurious interrupt. It is /// completely forbidden to send an end of interrupt after this function. pub unsafe fn is_spurious(&mut self, vec_id: u8) -> bool { From 938a06dadb0a50262572c6477756aac456b2b519 Mon Sep 17 00:00:00 2001 From: Notforest Date: Sun, 24 Nov 2024 11:46:27 +0100 Subject: [PATCH 12/15] Added more high-level API functions. Expanded the example. --- src/lib.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0af6bff..a780d86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,28 @@ //! pic.notify_end_of_interrupt(32) //! }); //! } +//! ... +//! +//! #[no_mangle] +//! unsafe extern "x86-interrupt" fn parallel_port1_handler(mut stack_frame: InterruptStackFrame) { +//! let mut pic = PROGRAMMABLE_INTERRUPT_CONTROLLER.lock(); +//! +//! // Checking if the interrupt was legit. (please look [´ChainedPics::is_spurious´]) +//! if pic.as_mut().map(|pic| !pic.is_spurious(39)).unwrap_or(false) { +//! +//! /* +//! * Some interrupt handling logic and stuff. +//! * */ +//! println!("The interrupt was legit!"); +//! +//! // Sending the EOI. +//! pic.as_mut().map(|pic| { +//! pic.notify_end_of_interrupt(39) +//! }); +//! } else { +//! println!("A spurious interrupt! Do not handle that!"); +//! } +//! } //! ``` mod commands; @@ -98,8 +120,7 @@ pub enum PicOperationMode { /// /// Some applications might want to have a different priority mapping for the full software /// control over the sequence of interrupts. During this mode the mask register is now used to - /// temporarly disable certain interrupt levels (not interrupt lines) as well as manually - /// changing the priority level. + /// temporarly disable certain interrupt levels as well as manually changing the priority level. /// /// # Use Case /// @@ -251,7 +272,9 @@ impl ChainedPics { } else { false } } else if self.master.handles_interrupt(vec_id) { self.master.is_spurious(vec_id) - } else { false } + } else { + panic!("Provided interrupt is out of scope for both PICs.") + } } /// Notify a proper PIC chip that the interrupt was succesfully handled and shall be cleared @@ -295,6 +318,37 @@ impl ChainedPics { unsafe { self.write_mask(IrqMask::all()) }; } + /// Enables both PICs interrupts. + /// + /// They are enabled by default after the initialization. + /// + /// # Warn + /// + /// This is not the initialization. Please see [´ChainedPics::initialize´] + pub fn enable(&mut self) { + unsafe { self.write_mask(IrqMask::empty()); } + } + + /// Disables the slave PIC fully, i.e IRQ8 ... IRQ15. + pub fn disable_slave(&mut self) { + unsafe { + let mask = self.master.mask_read(); + self.master.mask_write( + mask | OCW1::MASK_IRQ_2 + ); + } + } + + /// Enables the slave PIC, i.e IRQ8 ... IRQ15. + pub fn enable_slave(&mut self) { + unsafe { + let mask = self.master.mask_read(); + self.master.mask_write( + mask & !OCW1::MASK_IRQ_2 + ); + } + } + /// Gets the current IRQ mask. pub fn get_mask(&mut self) -> IrqMask { IrqMask::from_bits_truncate( @@ -318,6 +372,19 @@ impl ChainedPics { } } + /// Perform something on the master PIC. + pub fn with_master(&mut self, f: F) where + F: FnOnce(&mut Pic) + { + f(&mut self.master) + } + + /// Perform something on the slave PIC. + pub fn with_slave(&mut self, f: F) where + F: FnOnce(&mut Pic) { + f(&mut self.slave) + } + /// Creates a new instance of PIC controller. /// /// The master offset and slave offset are two offsets that are pointing to the first From d2b0cbb20a9fd4261308d0cf0189d994658eb539 Mon Sep 17 00:00:00 2001 From: Notforest Date: Sun, 24 Nov 2024 11:48:30 +0100 Subject: [PATCH 13/15] Added better handling for all operation modes. Added proper switching between modes. Added all EOI routine for all modes. Saving poll flag from being cleared when reading the IRR or ISR. Added the priority change function. Added the special mask mode handling. --- src/chip.rs | 159 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 127 insertions(+), 32 deletions(-) diff --git a/src/chip.rs b/src/chip.rs index 4f1d196..98dc1f4 100644 --- a/src/chip.rs +++ b/src/chip.rs @@ -4,7 +4,6 @@ //! [´ChainedPics´] for convenient structure to manipulate on chained set of PICs located on the //! x86 architecture. -use bitflags::Flags; use x86_64::instructions::port::Port; use crate::post::post_debug_delay; use crate::regs::*; @@ -181,28 +180,63 @@ impl Pic { /// /// This sends the OCW2 command and configurest the current operation mode of the PIC logic. /// Refer to [´PicOperationMode´] enum for more details. + /// + /// # Warn. + /// + /// When switching from polled mode a mask must be restored to the previously used one. pub fn operation_mode_change(&mut self, new_op_mode: PicOperationMode) { - if self.op_mode != new_op_mode { - unsafe { - match new_op_mode { - PicOperationMode::FullyNested => { - self.command.write(OCW2::ROTATE_IN_AUTOMATIC_EOI_MODE_CLEAR.bits()); - }, - PicOperationMode::AutomaticRotation => - if self.automatic_interrupts { - self.command.write(OCW2::ROTATE_IN_AUTOMATIC_EOI_MODE_SET.bits()); - } else { - self.command.write(OCW2::ROTATE_ON_NON_SPECIFIC_EOI_COMMAND.bits()); - }, - PicOperationMode::SpecialMask => self.command.write(OCW3::SET_SPECIAL_MASK.bits()), - PicOperationMode::PolledMode => { - self.mask_write(OCW1::all()); - self.command.write(OCW3::POLL.bits()); - }, + use PicOperationMode::*; + + unsafe { + /* Default behaviour when switching the mode. */ + let fully_nested_arm = |s: &mut Self| // Restoring the disturbed fully nested structure. + s.set_lowest_priority(7); + let automatic_rotation_arm = |s: &mut Self| + if s.automatic_interrupts { + s.command.write(OCW2::ROTATE_IN_AUTOMATIC_EOI_MODE_SET.bits()); }; + let special_mask_arm = |s: &mut Self| s.command.write(OCW3::SET_SPECIAL_MASK.bits()); + let polled_mode_arm = |s: &mut Self| { + s.mask_write(OCW1::all()); + s.command.write(OCW3::POLL.bits()); }; - self.op_mode = new_op_mode; + + match self.op_mode { + FullyNested => match new_op_mode { + FullyNested => return, + AutomaticRotation => automatic_rotation_arm(self), + SpecialMask => special_mask_arm(self), + PolledMode => polled_mode_arm(self), + }, + AutomaticRotation => { + if self.automatic_interrupts { + self.command.write(OCW2::ROTATE_IN_AUTOMATIC_EOI_MODE_CLEAR.bits()); + } + match new_op_mode { + FullyNested => fully_nested_arm(self), + AutomaticRotation => return, + SpecialMask => special_mask_arm(self), + PolledMode => polled_mode_arm(self), + } + }, + SpecialMask => { + self.command.write(OCW3::RESET_SPECIAL_MASK.bits()); + match new_op_mode { + FullyNested => fully_nested_arm(self), + AutomaticRotation => automatic_rotation_arm(self), + SpecialMask => return, + PolledMode => polled_mode_arm(self) + } + }, + PolledMode => match new_op_mode { + FullyNested => fully_nested_arm(self), + AutomaticRotation => automatic_rotation_arm(self), + SpecialMask => special_mask_arm(self), + PolledMode => return, + }, + } } + self.op_mode = new_op_mode; } /// Checks if the provided interrupt vector was caused by PIC properly. @@ -232,14 +266,17 @@ impl Pic { /// /// The interrupt status register inside the PIC chip, shows the info about which interrupts are /// being serviced at that moment. The value will be flushed after the end_of_interrupt method. + /// + /// # Note + /// + /// Always 0x0 in polled mode. pub fn read_isr(&mut self) -> ISR { unsafe { - self.command.write(OCW3::READ_REG_ISR.bits()); - let isr = ISR::from_bits_truncate(self.command.read()); - if self.op_mode == PicOperationMode::PolledMode { - self.command.write(OCW3::POLL.bits()) + // ISR is guaranteed to be empty during in polled mode. + if self.op_mode == PicOperationMode::PolledMode { ISR::empty() } else { + self.command.write(OCW3::READ_REG_ISR.bits()); + ISR::from_bits_truncate(self.command.read()) } - isr } } @@ -251,6 +288,7 @@ impl Pic { unsafe { self.command.write(OCW3::READ_REG_IRR.bits()); let irr = IRR::from_bits_truncate(self.command.read()); + // Polled mode will override the register read. The same goes vice-versa. if self.op_mode == PicOperationMode::PolledMode { self.command.write(OCW3::POLL.bits()) } @@ -274,7 +312,7 @@ impl Pic { /// /// # Note /// - /// The interrupt is immediately acknowledged after the first read. According to the datasheet: + /// The interrupt is immediately acknowledged after the first read, according to the datasheet: /// When poll command is issued, the 8259 treats the next RD pulse as an interrupt acknowledge. pub fn poll(&mut self) -> Option { match self.op_mode { @@ -304,23 +342,78 @@ impl Pic { /// /// # Special Mask /// - /// Before calling this function a special mask can be used with the enum to inhibit the - /// interrupt priority. See more info in [´PicOperationMode::SpecialMask´] + /// Before calling this function in a special mask mode [´PicOperationMode::SpecialMask´], a + /// mask can be applied to the data port of the PIC to inhibit some interrupts. Priority can + /// also be changed. /// /// # Note /// /// Does nothing if PIC is configured with automatic EOI flag or in poll mode. pub fn end_of_interrupt(&mut self) { - match self.op_mode { - PicOperationMode::FullyNested => unsafe { self.non_specified_eoi() }, - PicOperationMode::AutomaticRotation => unsafe { self.non_specified_eoi() }, - PicOperationMode::SpecialMask => todo!("Special mask mode"), // TODO!! - PicOperationMode::PolledMode => (), // Interrupt is acknowledged once the command port is read. + if !self.automatic_interrupts { + match self.op_mode { + PicOperationMode::AutomaticRotation => unsafe { + self.command.write(OCW2::ROTATE_ON_NON_SPECIFIC_EOI_COMMAND.bits()); + }, + PicOperationMode::PolledMode => (), // Interrupt is acknowledged once the command port is read. + _ => unsafe { self.non_specified_eoi() }, + } + } + } + + /// Sends a proper specific end of interrupt. + /// + /// # Special Mask + /// + /// Before calling this function in a special mask mode [´PicOperationMode::SpecialMask´], a + /// mask can be applied to the data port of the PIC to inhibit some interrupts. Priority can + /// also be changed. + /// + /// # Unsafe + /// + /// A proper irq must be used, or new interrupts won't appear. + /// + /// # Note + /// + /// Does nothing if PIC is configured with automatic EOI flag or in poll mode. + pub unsafe fn end_of_interrupt_specific(&mut self, irq: u8) { + assert!(irq < 8, "Level is written in binary format (0 .. 7)."); + + if !self.automatic_interrupts { + match self.op_mode { + PicOperationMode::AutomaticRotation => unsafe { + self.command.write(OCW2::ROTATE_ON_SPECIFIC_EOI_COMMAND.bits() | irq << OCW2::LEVEL.bits()); + }, + PicOperationMode::PolledMode => (), // Interrupt is acknowledged once the command port is read. + _ => unsafe { self.specified_eoi(irq) }, + } } } + /// Manually change the lowest priority of this PIC. + /// + /// The lowest priority can be fixed on some IRQ and thus fixing other priorities, where lower + /// IRQs grow priority, i.e if the IRQ5 is lowest priority, then IRQ6 is the highest priority + /// and IRQ4 is the second lowest priority (it is circular). By default in fully nested mode, + /// the IRQ0 is the highest and IRQ7 is the lowest. + /// + /// The value is expected in binary format. + /// + /// # Note + /// + /// Note that PIC will generate a spurious interrupt on IRQ7 regardless of the priority level. + pub unsafe fn set_lowest_priority(&mut self, level: u8) { + assert!(level < 8, "Level is written in binary format (0 .. 7)."); + + self.command.write( + OCW2::SET_PRIORITY_COMMAND.bits() | (level << OCW2::LEVEL.bits()) + ); + } + /// Performs an unsafe specified end of interrupt. /// + /// The value is expected in binary format. + /// /// # Unsafe /// /// Specified end of interrupt must be written together with an interrupt level to reset. @@ -333,6 +426,8 @@ impl Pic { /// Performs an unsafe non specified end of interrupt. /// + /// The value is expected in binary format. + /// /// # Unsafe /// /// Non specific EOI resets the highest ISR bit of those that are set. It is safe to use in From 097759aa02b0b601b01aa7a07e13a1925a657a54 Mon Sep 17 00:00:00 2001 From: Notforest Date: Sun, 24 Nov 2024 12:02:29 +0100 Subject: [PATCH 14/15] Preserving the special mask when reading ISR or IRR --- src/chip.rs | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/chip.rs b/src/chip.rs index 98dc1f4..360584d 100644 --- a/src/chip.rs +++ b/src/chip.rs @@ -272,11 +272,21 @@ impl Pic { /// Always 0x0 in polled mode. pub fn read_isr(&mut self) -> ISR { unsafe { - // ISR is guaranteed to be empty during in polled mode. - if self.op_mode == PicOperationMode::PolledMode { ISR::empty() } else { - self.command.write(OCW3::READ_REG_ISR.bits()); - ISR::from_bits_truncate(self.command.read()) + match self.op_mode { + // ISR is guaranteed to be empty during in polled mode. + PicOperationMode::PolledMode => return ISR::empty(), + PicOperationMode::SpecialMask => { + self.command.write( + OCW3::READ_REG_ISR.bits() | OCW3::SET_SPECIAL_MASK.bits() + ); + }, + _ => { + self.command.write( + OCW3::READ_REG_ISR.bits() + ); + } } + ISR::from_bits_truncate(self.command.read()) } } @@ -286,13 +296,23 @@ impl Pic { /// but are not being acknowledged yet. The value will be flushed after the end_of_interrupt method. pub fn read_irr(&mut self) -> IRR { unsafe { - self.command.write(OCW3::READ_REG_IRR.bits()); - let irr = IRR::from_bits_truncate(self.command.read()); - // Polled mode will override the register read. The same goes vice-versa. + match self.op_mode { + PicOperationMode::SpecialMask => { + self.command.write( + OCW3::READ_REG_IRR.bits() | OCW3::SET_SPECIAL_MASK.bits() + ); + }, + _ => { + self.command.write( + OCW3::READ_REG_IRR.bits() + ); + + } + } if self.op_mode == PicOperationMode::PolledMode { self.command.write(OCW3::POLL.bits()) } - irr + IRR::from_bits_truncate(self.command.read()) } } From 3ef854378da7edb8e4da28d26439b1cd1e318b9e Mon Sep 17 00:00:00 2001 From: Notforest Date: Sun, 24 Nov 2024 12:38:08 +0100 Subject: [PATCH 15/15] Improved comments. --- src/chip.rs | 16 ++++++++-------- src/commands.rs | 3 --- src/lib.rs | 33 ++++++++++++++++++++------------- src/regs.rs | 9 +++++---- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/chip.rs b/src/chip.rs index 360584d..43d0fb6 100644 --- a/src/chip.rs +++ b/src/chip.rs @@ -99,7 +99,7 @@ impl Pic { /// /// # PIC Mapping /// - /// This desides if the PIC is a master or a slave device. It also defines which lines are + /// This decides if the PIC is a master or a slave device. It also defines which lines are /// connected to which depending on it's value. /// /// # Automatic Interrupts @@ -114,8 +114,8 @@ impl Pic { /// structure is not required within a single 8259A. pub unsafe fn init(&mut self, pic_map: PicIRQMapping, automatic_interrupts: bool) { unsafe { - // Saving the values that was before the data change. - let mask = self.data.read(); + // Saving the previous irq masking. + let mask = self.mask_read(); // Generating initialization commands based on the chosen operation mode. let icw1 = ICW1::IC4 | @@ -159,8 +159,8 @@ impl Pic { self.data.write(icw4.bits()); post_debug_delay(); - /* OCW1 command. */ - self.data.write(mask); + // Restoring the mask. + self.mask_write(mask); } } @@ -178,7 +178,7 @@ impl Pic { /// Changes the current operation mode of this PIC. /// - /// This sends the OCW2 command and configurest the current operation mode of the PIC logic. + /// This sends the OCW2 command and configures the current operation mode of the PIC logic. /// Refer to [´PicOperationMode´] enum for more details. /// /// # Warn. @@ -236,6 +236,7 @@ impl Pic { }, } } + post_debug_delay(); self.op_mode = new_op_mode; } @@ -306,7 +307,6 @@ impl Pic { self.command.write( OCW3::READ_REG_IRR.bits() ); - } } if self.op_mode == PicOperationMode::PolledMode { @@ -391,7 +391,7 @@ impl Pic { /// /// # Unsafe /// - /// A proper irq must be used, or new interrupts won't appear. + /// A proper IRQ must be used, or new interrupts won't appear. /// /// # Note /// diff --git a/src/commands.rs b/src/commands.rs index fdf892f..597fc9f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -179,9 +179,6 @@ bitflags::bitflags! { const LEVEL = 0; /// Sets that the command is not EOI specific (i.e highest priority cleared first). - /// - /// # Note - /// This shall only be used in the fully nested mode. const NON_SPECIFIC_EOI_COMMAND = 0b001 << 5; /// Specific EOI command should be used. const SPECIFIC_EOI_COMMAND = 0b011 << 5; diff --git a/src/lib.rs b/src/lib.rs index a780d86..c32c896 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,9 @@ //! A single PIC handles up to eight vectored priority interrupts for the CPU. By cascading 8259 //! chips, we can increase interrupts up to 64 interrupt lines, however we only have two chained //! instances that can handle 16 lines. Can be programmed either in edge triggered, or in level -//! triggered mode. PIC uses CHANNEL0 from the PIT (Programmable Interval Timer), which's frequency -//! can be adjusted based on it's configuration. Individual bits of IRQ register within the PIC can -//! be masked out by the software. +//! triggered mode. PIC uses CHANNEL0 from the PIT (Programmable Interval Timer), the frequency of +//! which can be adjusted based on it's configuration. Individual bits of IRQ register within the +//! PIC can be masked out by the software. //! //! The basic idea here is that we have two PIC chips, PIC1 and PIC2, and that PIC2 is slaved to //! interrupt 2 on PIC 1. You can find the whole story at http://wiki.osdev.org/PIC (as usual). @@ -91,7 +91,7 @@ pub use regs::*; pub enum PicOperationMode { /// Fully Nested Mode (Default Mode) /// - /// This mode is entered after intiialization unless another mode is programmed. The interrupt + /// This mode is entered after initialization unless another mode is programmed. The interrupt /// requests are ordered in priority from 0 through 7, where 0 is the highest priority. When /// interrupt is acknowledged the highest priority interrupt will be issued before the rest. /// @@ -186,8 +186,7 @@ impl ChainedPics { /// # Panics /// /// This function will panic if the provided offset will overlap with cpu exceptions. It - /// will always prevent the overlapping between master and slave chips, because it makes - /// an offset for them sequentially. + /// will always prevent the overlapping between master and slave chips though pub const fn new_contiguous(primary_offset: u8) -> Self { Self::new(primary_offset, primary_offset + 8) } @@ -201,7 +200,7 @@ impl ChainedPics { /// /// This performs an initialization that is compatible with most x86 PC devices. Some archaic /// devices may use only one PIC. For such possibilities a manual initialization of PIC - /// structures must be performed. + /// structure must be performed. pub fn initialize(&mut self) { unsafe { self.master.init(PicIRQMapping::Master(Some(ICW3_MASTER::SLAVE2)), false); @@ -213,9 +212,13 @@ impl ChainedPics { /// Changes the operation mode for both master and slave PICs. /// - /// This sends the OCW2 command and configurest the current operation mode of the PIC logic. + /// This sends the OCW2 command and configures the current operation mode of the PIC logic. /// Refer to [´PicOperationMode´] enum for more details. This function only checks the mode of /// the master PIC, assuming that slave was not changed manually to something else. + /// + /// # Note + /// + /// The IRQ mask must be changed after switching from [´PicOperationMode::PolledMode´]. pub fn operation_mode_change(&mut self, new_op_mode: PicOperationMode) { if self.master.operation_mode_current() != new_op_mode { self.master.operation_mode_change(new_op_mode); @@ -257,6 +260,11 @@ impl ChainedPics { /// [´PicOperationMode::SpecialMask´], the spurious interrupt can still only be found on IRQ7 /// for the master PIC or IRQ15 for the slave PIC. /// + /// # Note (Polled Mode) + /// + /// Makes no sense in polled mode. Note also that in polled mode the ISR is always zero, so + /// this function will always mark proper IRQs as spurious. + /// /// # Unsafe /// /// This function is unsafe only because it shall be used within the interrupt handler function @@ -284,10 +292,10 @@ impl ChainedPics { /// /// To prevent spurious interrupts on lowest priority IRQs, use [´ChainedPics::is_spurious´] /// and jump to the end of interrupt handler function if it returns true. If some interrupt was - /// caused by a hardware|software mistake, it should not be handled. + /// caused by a hardware|software error, it should not be handled. /// - /// **PIC must not receive a EOI command, when it is a spurious interrupts. It will prevent - /// other interrupts from being handled, which is a bigger trouble.** + /// **PIC must not receive a EOI command, when it is a spurious interrupts. It can clear + /// other interrupt's flag, which is a bigger trouble.** /// /// Lower priority interrupts vary based on the current mode. The function mentioned above /// handles all logic required for each. @@ -312,8 +320,7 @@ impl ChainedPics { /// /// # Note /// - /// This must be used when switching to APIC controller for handling interrupts. This is also - /// mandatory even if the chip was never initialized by the OS. + /// This must be used when switching to APIC controller for handling interrupts. pub fn disable(&mut self) { unsafe { self.write_mask(IrqMask::all()) }; } diff --git a/src/regs.rs b/src/regs.rs index f115d92..4c9b5e5 100644 --- a/src/regs.rs +++ b/src/regs.rs @@ -15,7 +15,8 @@ bitflags::bitflags! { /// the CPU acknowledges it. /// /// The interrupt request register shows the requested interrupts that have been raised - /// but are not being acknowledged yet. The value will be flushed after the end_of_interrupt method. + /// but are not being acknowledged yet. The highest priority value will be flushed after + /// CPU enters the interrupt handler. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct IRR: u8 { const IRQ0 = 1; @@ -30,9 +31,9 @@ bitflags::bitflags! { /// Read the Interrupt Service Register (ISR). /// - /// Tracks IRQ line currently being services. Updated by EOI command. - /// The interrupt status register inside the PIC chip, shows the info about which interrupts are - /// being serviced at that moment. The value will be flushed after the end_of_interrupt method. + /// Tracks IRQ line currently being services. Updated by EOI command. The interrupt status register + /// inside the PIC chip, shows the info about which interrupts are being serviced at that moment. + /// The highest priority value will be flushed after the end_of_interrupt method. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ISR: u8 { const IRQ0 = 1;