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
//! Driver for the 8259 Programmable Interrupt Controller.
//!
//! Only handles the usual case of two PICs in a cascading setup, where the
//! SLAVE is setup to cascade to the line 2 of the MASTER.

use crate::i386::pio::Pio;
use crate::io::Io;
use crate::sync::{Once, SpinLockIRQ};

bitflags! {
    /// The first control word sent to the PIC.
    struct ICW1: u8 {
        /// If this bit is set, ICW4 has to be read. If ICW4 is not needed, set
        /// ICW4 to 0
        const ICW4      = 0x01;
        /// Single. Means that this is the only 8259A in the system. If SINGLE
        // is 1, no ICW3 will be issued.
        const SINGLE    = 0x02;
        /// Call Address Interval. Used only in 8085, not 8086. 1=ISR's are 4
        /// bytes apart (0200, 0204, etc) 0=ISR's are 8 byte apart (0200, 0208,
        /// etc)
        const INTERVAL4 = 0x04;
        /// If LEVEL = 1, then the 8259A will operate in the level interrupt
        /// mode. Edge detect logic on the interrupt inputs will be disabled.
        const LEVEL     = 0x08;
        /// Should always be set to 1.
        const INIT      = 0x10;
    }
}

/// ICW4: 8086/88 (MCS-80/85) mode.
const ICW4_8086: u8     = 0x01;       /* 8086/88 (MCS-80/85) mode */
//const icw4_auto         = 0x02;       /* Auto (normal) EOI */
//const icw4_buf_slave    = 0x08;       /* Buffered mode/slave */
//const icw4_buf_master   = 0x0C;       /* Buffered mode/master */
//const icw4_sfnm         = 0x10;       /* Special fully nested (not) */

/// The PIC manager.
static PIC: Once<Pic> = Once::new();

/// Acquires a reference to the PIC, initializing it if it wasn't already setup.
pub fn get() -> &'static Pic {
    PIC.call_once(|| unsafe {
        Pic::new()
    })
}

/// Initializes the PIC if it has not yet been initialized. Otherwise, does nothing.
pub fn init() {
    PIC.call_once(|| unsafe {
        Pic::new()
    });
}

/// A single PIC8259 device.
#[derive(Debug)]
struct InternalPic {
    /// The PIC's COMMAND IO port.
    port_cmd: Pio<u8>,
    /// The PIC's DATA IO port.
    port_data: Pio<u8>
}

/// A master/slave PIC setup, as commonly found on IBM PCs.
#[derive(Debug)]
pub struct Pic {
    /// The master PIC.
    master: SpinLockIRQ<InternalPic>,
    /// The slave PIC, cascaded on line 2 of `.master`
    slave: SpinLockIRQ<InternalPic>,
}

impl Pic {
    /// Creates a new PIC, and initializes it.
    ///
    /// Interrupts will be mapped to IRQ [32..48]
    ///
    /// # Safety
    ///
    /// This should only be called once! If called more than once, then both Pics instances
    /// will share the same underlying Pios, but different mutexes protecting them!
    unsafe fn new() -> Pic {
        Pic {
            master: SpinLockIRQ::new(InternalPic::new(0x20, true, 32)),
            slave: SpinLockIRQ::new(InternalPic::new(0xA0, false, 32 + 8)),
        }
    }

    /// Mask the given IRQ number. Will redirect the call to the right Pic device.
    pub fn mask(&self, irq: u8) {
        if irq < 8 {
            self.master.lock().mask(irq);
        } else {
            self.slave.lock().mask(irq - 8);
        }
    }

    /// Unmask the given IRQ number. Will redirect the call to the right Pic device.
    pub fn unmask(&self, irq: u8) {
        if irq < 8 {
            self.master.lock().unmask(irq);
        } else {
            self.slave.lock().unmask(irq - 8);
        }
    }

    /// Reads the PIC interrupt mask. Used for debug purposes.
    ///
    /// LSB is irq 0, MSB is irq 15.
    pub fn get_mask(&self) -> u16 {
        u16::from(self.master.lock().get_mask()) | (u16::from(self.slave.lock().get_mask()) << 8)
    }

    /// Acknowledges an IRQ, allowing the PIC to send a new IRQ on the next
    /// cycle.
    pub fn acknowledge(&self, irq: u8) {
        self.master.lock().acknowledge();
        if irq >= 8 {
            self.slave.lock().acknowledge();
        }
    }
}

impl InternalPic {
    /// Setup the 8259 pic. Redirect the IRQ to the chosen interrupt vector.
    ///
    /// # Safety
    ///
    /// The port should map to a proper PIC device. Sending invalid data to a
    /// random device can lead to memory unsafety. Furthermore, care should be
    /// taken not to share the underlying Pio.
    unsafe fn new(port_base: u16, is_master: bool, vector_offset: u8) -> InternalPic {
        let mut pic = InternalPic {
            port_cmd: Pio::new(port_base),
            port_data: Pio::new(port_base + 1)
        };

        // save masks
        let mask_backup = pic.port_data.read();

        // starts the initialization sequence (in cascade mode)
        pic.port_cmd.write((ICW1::INIT | ICW1::ICW4).bits());

        // ICW2: Master PIC vector offset
        pic.port_data.write(vector_offset);

        // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
        pic.port_data.write(if is_master { 4 } else { 2 });

        // ICW4: ??
        pic.port_data.write(ICW4_8086);

        // restore saved masks.
        pic.port_data.write(mask_backup);

        pic
    }

    /// Acknowledges an IRQ, allowing the PIC to send a new IRQ on the next
    /// cycle.
    pub fn acknowledge(&mut self) {
        unsafe {
            self.port_cmd.write(0x20);
        }
    }

    /// Mask the given IRQ
    pub fn mask(&mut self, irq: u8) {
        self.port_data.writef(1 << irq, true);
    }

    /// Unmask the given IRQ
    pub fn unmask(&mut self, irq: u8) {
        self.port_data.writef(1 << irq, false);
    }

    /// Read the IRQ mask. Used mostly for debug purposes.
    pub fn get_mask(&self) -> u8 {
        self.port_data.read()
    }
}