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", ] } diff --git a/src/chip.rs b/src/chip.rs new file mode 100644 index 0000000..43d0fb6 --- /dev/null +++ b/src/chip.rs @@ -0,0 +1,461 @@ +//! 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::regs::*; +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. + pub 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 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 + /// + /// 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. + /// + /// 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 previous irq masking. + let mask = self.mask_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(); + + // Restoring the mask. + self.mask_write(mask); + } + } + + /// 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..self.offset + 8).contains(&interrupt_id) + } + + /// 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 configures 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) { + 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()); + }; + + 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, + }, + } + } + post_debug_delay(); + self.op_mode = 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, + /// 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 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."); + + let irq = ISR::from_bits_truncate(1 << id.saturating_sub(self.offset)); + !self.read_isr().contains(irq) + } + + /// 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. + /// + /// # Note + /// + /// Always 0x0 in polled mode. + pub fn read_isr(&mut self) -> ISR { + unsafe { + 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()) + } + } + + /// 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 { + 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::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()) + } + } + + /// 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, 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 { Some(self.command.read()) }, + _ => None + } + } + + /// Masks the requested IRQ lines. + /// + /// 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 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()); + } + + /// Sends a proper 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. + /// + /// # Note + /// + /// Does nothing if PIC is configured with automatic EOI flag or in poll mode. + pub fn end_of_interrupt(&mut self) { + 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. + /// 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. + /// + /// 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 + /// 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() + ); + } +} diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..597fc9f --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,224 @@ +//! 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). + 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; + } +} diff --git a/src/lib.rs b/src/lib.rs index bfed53d..c32c896 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,186 +1,493 @@ -//! 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. +//! +//! 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), 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). +//! +//! # 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 :) */ //! -//! 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. +//! ... +//! // Somewhere in interrupt handling module. +//! +//! #[no_mangle] +//! unsafe extern "x86-interrupt" fn timer_interrupt_handler(mut stack_frame: InterruptStackFrame) { //! -//! 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. +//! /* +//! * Some interrupt handling logic and stuff. +//! * */ +//! +//! // Sending the EOI. +//! PROGRAMMABLE_INTERRUPT_CONTROLLER.lock().as_mut().map(|pic| { +//! 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!"); +//! } +//! } +//! ``` -#![no_std] +mod commands; +mod chip; +mod regs; +mod post; -use x86_64::instructions::port::Port; +use commands::*; +use chip::*; -/// Command sent to begin PIC initialization. -const CMD_INIT: u8 = 0x11; +pub use regs::*; -/// Command sent to acknowledge an interrupt. -const CMD_END_OF_INTERRUPT: u8 = 0x20; +/// **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 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. + /// + /// 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 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 mode in which we want to run our PICs. -const MODE_8086: u8 = 0x01; +/// 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, +} -/// 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, +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."); - /// The processor I/O port on which we send commands. - command: Port, + unsafe { Self::new_unchecked(master_offset, slave_offset) } + } - /// The processor I/O port on which we send and receive data. - data: Port, -} + /// 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 though + pub const fn new_contiguous(primary_offset: u8) -> Self { + Self::new(primary_offset, primary_offset + 8) + } + + /// Returns true if initialized at least once. + pub fn is_initialized(&self) -> bool { + self.initialized + } + + /// 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 + /// structure must be performed. + pub fn initialize(&mut self) { + unsafe { + 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 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); + 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, + /// 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 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 + /// 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 { + assert!(vec_id >= 32, "Cannot be one of the CPU exceptions."); -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 + 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 { + panic!("Provided interrupt is out of scope for both PICs.") + } } - /// 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); + /// 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 error, it should not be handled. + /// + /// **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. + /// + /// # 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. + 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(); + } else + if self.master.handles_interrupt(vec_id) { + self.master.end_of_interrupt(); + } } - /// Reads the interrupt mask of this PIC. - unsafe fn read_mask(&mut self) -> u8 { - self.data.read() + /// Disable both PICs interrupts. + /// + /// # Note + /// + /// This must be used when switching to APIC controller for handling interrupts. + pub fn disable(&mut self) { + unsafe { self.write_mask(IrqMask::all()) }; } - /// Writes the interrupt mask of this PIC. - unsafe fn write_mask(&mut self, mask: u8) { - self.data.write(mask) + /// 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()); } } -} -/// A pair of chained PICs. This is the standard setup on x86. -pub struct ChainedPics { - pics: [Pic; 2], -} + /// 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 + ); + } + } -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), - }, - ], + /// 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 + ); } } - /// Create a new `ChainedPics` interface that will map the PICs contiguously starting at the given interrupt offset. + /// 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() + ]) + ) + } + + /// 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 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(); + unsafe { + self.master.mask_write(OCW1::from_bits_truncate(bytes[0])); + self.slave.mask_write(OCW1::from_bits_truncate(bytes[1])); + } + } + + /// Perform something on the master PIC. + pub fn with_master(&mut self, f: F) where + F: FnOnce(&mut Pic) + { + f(&mut self.master) } - /// 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]) + /// 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 + /// 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; } } 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. diff --git a/src/regs.rs b/src/regs.rs new file mode 100644 index 0000000..4c9b5e5 --- /dev/null +++ b/src/regs.rs @@ -0,0 +1,48 @@ +//! 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 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; + 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 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; + 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; + } +}