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
//! 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.
//!
//! 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;
/// 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;
/// 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<u8>,
/// The processor I/O port on which we send and receive data.
data: Port<u8>,
}
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
}
/// 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);
}
/// Reads the interrupt mask of this PIC.
unsafe fn read_mask(&mut self) -> u8 {
self.data.read()
}
/// Writes the interrupt mask of this PIC.
unsafe fn write_mask(&mut self, mask: u8) {
self.data.write(mask)
}
}
/// A pair of chained PIC controllers. This is the standard setup on x86.
pub struct ChainedPics {
pics: [Pic; 2],
}
impl ChainedPics {
/// Create a new interface for the standard PIC1 and PIC2 controllers,
/// 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),
},
],
}
}
/// Create a new `ChainedPics` interface that will map the PIC controllers contiguously starting at the given interrupt offset.
///
/// This is a convenience function that maps the PIC1 and PIC2 controllers 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)
}
/// 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<u8> = 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])
}
/// Reads the interrupt masks of both PICs.
pub unsafe fn read_masks(&mut self) -> [u8; 2] {
[self.pics[0].read_mask(), self.pics[1].read_mask()]
}
/// Writes the interrupt masks of both PICs.
pub unsafe fn write_masks(&mut self, mask1: u8, mask2: u8) {
self.pics[0].write_mask(mask1);
self.pics[1].write_mask(mask2);
}
/// Disables both PICs by masking all interrupts.
pub unsafe fn disable(&mut self) {
self.write_masks(u8::MAX, u8::MAX)
}
/// Do we handle this interrupt?
pub 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 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();
}
}
}