1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
//! Support for the x86 PIC (8259 Programmable Interrupt Controller),
//! which handles basic interrupts.
//! In multicore mode, this isn't used in favor of the APIC interface.
//! This was modified from the toyos pic8259_simple crate.
#![no_std]
extern crate port_io;
use core::fmt;
/// The offset added to the first IRQ: `0x20`.
///
/// This is needed to shift the start of all IRQ vectors
/// to after the end of the CPU exception vectors,
/// which occupy the first 32 IRQ vectors.
pub const IRQ_BASE_OFFSET: u8 = 0x20;
/// The IRQ number reserved for spurious PIC interrupts (as recommended by OS dev wiki).
pub const PIC_SPURIOUS_INTERRUPT_IRQ: u8 = IRQ_BASE_OFFSET + 0x7;
/// Command sent to read the Interrupt Request Register.
const CMD_IRR: u8 = 0x0A;
/// Command sent to read the In-Service Register.
const CMD_ISR: u8 = 0x0B;
/// Command sent to begin PIC initialization.
const CMD_INIT: u8 = 0x11;
/// Command sent to acknowledge an interrupt.
const CMD_END_OF_INTERRUPT: u8 = 0x20;
// The mode in which we want to run our PICs.
const MODE_8086: u8 = 0x01;
const MASTER_CMD: u16 = 0x20;
const MASTER_DATA: u16 = 0x21;
const SLAVE_CMD: u16 = 0xA0;
const SLAVE_DATA: u16 = 0xA1;
/// The set of status registers for both PIC chips.
///
/// Each PIC chip has two interrupt status registers:
/// * `ISR`: the In-Service Register: specifies which interrupts are currently being serviced,
/// meaning IRQs sent to the CPU.
/// * `IRR`: the Interrupt Request Register: specifies which interrupts have been raised
/// but not necessarily serviced yet.
///
/// Based on the interrupt mask, the PIC will send interrupts from the IRR to the CPU,
/// at which point they are marked in the ISR.
///
/// For more, [see this explanation](http://wiki.osdev.org/8259_PIC#ISR_and_IRR).
pub struct IrqStatusRegisters {
pub master_isr: u8,
pub master_irr: u8,
pub slave_isr: u8,
pub slave_irr: u8,
}
impl fmt::Display for IrqStatusRegisters {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Master ISR: {:#b}, Master IRR:{:#b}, Slave ISR: {:#b}, Slave IRR: {:#b}",
self.master_isr, self.master_irr, self.slave_isr, self.slave_irr)
}
}
impl fmt::Debug for IrqStatusRegisters {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
/// 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_io::Port<u8>,
/// The processor I/O port on which we send and receive data.
data: port_io::Port<u8>,
}
impl Pic {
/// Are we in change of handling the specified interrupt?
/// (Each PIC handles 8 interrupts.)
fn handles_interrupt(&self, interupt_id: u8) -> bool {
self.offset <= interupt_id && interupt_id < self.offset + 8
}
/// Notify us that an interrupt has been handled and that we're ready
/// for more.
fn end_of_interrupt(&self) {
unsafe {
self.command.write(CMD_END_OF_INTERRUPT);
}
}
}
/// A pair of chained PIC chips, which represents the standard x86 configuration.
pub struct ChainedPics {
pics: [Pic; 2],
}
impl ChainedPics {
/// Create a new interface for the standard PIC1 and PIC2 controllers,
/// specifying the desired interrupt offsets.
/// Then, it initializes the PICs in a standard chained fashion,
/// which involved mapping the master PIC to 0x20 and the slave to 0x28 (standard rempaping),
/// because even if we don't use them (and disable them for APIC instead),
/// we still need to remap them to avoid a spurious interrupt clashing with an exception.
pub fn init(master_mask: u8, slave_mask: u8) -> ChainedPics {
let mut cpic = ChainedPics {
pics: [
Pic {
offset: IRQ_BASE_OFFSET,
command: port_io::Port::new(MASTER_CMD),
data: port_io::Port::new(MASTER_DATA),
},
Pic {
offset: IRQ_BASE_OFFSET + 8, // 8 IRQ lines per PIC
command: port_io::Port::new(SLAVE_CMD),
data: port_io::Port::new(SLAVE_DATA),
},
]
};
// SAFE: we already checked that we are the only ones to have called this constructor.
unsafe {
cpic.configure(master_mask, slave_mask);
}
cpic
}
/// 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.
unsafe fn configure(&mut self, master_mask: u8, slave_mask: u8) {
// mask all interrupts during init
self.mask_irqs(0xFF, 0xFF);
// pre-emptively acknowledge both PICs in case they have pending unhandled irqs
self.pics[0].command.write(CMD_END_OF_INTERRUPT);
io_wait();
self.pics[1].command.write(CMD_END_OF_INTERRUPT);
io_wait();
// 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);
io_wait();
self.pics[1].command.write(CMD_INIT);
io_wait();
// Byte 1: Set up our base offsets.
self.pics[0].data.write(self.pics[0].offset);
io_wait();
self.pics[1].data.write(self.pics[1].offset);
io_wait();
// Byte 2: Configure chaining between PIC1 and PIC2.
self.pics[0].data.write(4);
io_wait();
self.pics[1].data.write(2);
io_wait();
// Byte 3: Set our mode.
self.pics[0].data.write(MODE_8086);
io_wait();
self.pics[1].data.write(MODE_8086);
io_wait();
self.mask_irqs(master_mask, slave_mask);
// pre-emptively acknowledge both PICs in case they have pending unhandled irqs
// this is generally unnecessary but doesn't hurt if the interrupt hardware is misbehaving
self.pics[0].command.write(CMD_END_OF_INTERRUPT);
io_wait();
self.pics[1].command.write(CMD_END_OF_INTERRUPT);
io_wait();
}
/// Each mask is a bitwise mask for each IRQ line, with the master's IRQ line 2 (0x4)
/// affecting the entire slave IRQ mask. So if the master's IRQ line 2 is masked (disabled),
/// all slave IRQs (0x28 to 0x2F) are masked.
/// If a bit is set to 1, it is masked (disabled). If set to 0, it is unmasked (enabled).
pub fn mask_irqs(&self, master_mask: u8, slave_mask: u8) {
// SAFE: we are guaranteed to have initialized this structure in its constructor.
unsafe {
self.pics[1].data.write(slave_mask);
io_wait();
self.pics[0].data.write(master_mask);
io_wait();
}
}
/// Do we handle this interrupt?
fn handles_interrupt(&self, interrupt_id: u8) -> bool {
self.pics.iter().any(|p| p.handles_interrupt(interrupt_id))
}
/// 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 fn notify_end_of_interrupt(&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();
}
}
/// Reads the ISR and IRR registers of both the master and slave PIC.
pub fn read_isr_irr(&self) -> IrqStatusRegisters {
// SAFE: just reading PIC registers, no harm can be done.
unsafe {
self.pics[0].command.write(CMD_ISR);
self.pics[1].command.write(CMD_ISR);
let master_isr = self.pics[0].command.read();
let slave_isr = self.pics[1].command.read();
self.pics[0].command.write(CMD_IRR);
self.pics[1].command.write(CMD_IRR);
let master_irr = self.pics[0].command.read();
let slave_irr = self.pics[1].command.read();
IrqStatusRegisters {
master_isr,
master_irr,
slave_isr,
slave_irr,
}
}
}
}
#[inline(always)]
fn io_wait() {
// We need to add a short 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.
unsafe { port_io::Port::<u8>::new(0x80).write(0); }
}