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
//! Configure legacy PIC 8259

use bitfield_struct::bitfield;

use crate::arch::io::Port;

// PIC 1 config
const PIC1_OFFSET: u8 = 0x20;
const PIC1_CMD: Port<u8> = Port::new(0x20);
const PIC1_DATA: Port<u8> = Port::new(0x21);
// PIC 2 config
const PIC2_OFFSET: u8 = 0x28;
const PIC2_CMD: Port<u8> = Port::new(0xa0);
const PIC2_DATA: Port<u8> = Port::new(0xa1);
/// Command sent to begin PIC initialization.
const PIC_CMD_INIT: u8 = 0x11;
/// Command sent to acknowledge an interrupt.
const PIC_CMD_EOI: u8 = 0x20;
/// 8086/8088 or 8085 mode.
const PIC_MODE_8086: u8 = 0x01;
/// Single or multiple (cascade mode)  8259A
const PIC_MODE_AUTO_EOI: u8 = 0x02;

/// Interrupt ports of the PIC
#[bitfield(u16)]
pub struct PicPort {
    pub timer: bool,
    pub keyboard: bool,
    chain: bool, // chain
    pub serial1: bool,
    pub serial2: bool,
    pub parallel23: bool,
    pub floppy: bool,
    pub parallel1: bool,
    pub rtc: bool,
    pub acpi: bool,
    pub p10: bool,
    pub p11: bool,
    pub mouse: bool,
    pub co_processor: bool,
    pub primart_ata: bool,
    pub secondary_ata: bool,
}

/// Initialize the chained 8259 PICs as shown below.
///
/// Where `enable` specifies which interrupts are enabled.
///
/// ```text
///                      ____________                          ____________
/// Real Time Clock --> | 40         |   Timer -------------> | 32         |
/// ACPI -------------> |            |   Keyboard-----------> |            |
/// Available --------> | Secondary  |----------------------> | Primary    |
/// Available --------> | Interrupt  |   Serial Port 2 -----> | Interrupt  |->
/// Mouse ------------> | Controller |   Serial Port 1 -----> | Controller |
/// Co-Processor -----> |            |   Parallel Port 2/3 -> |            |
/// Primary ATA ------> |            |   Floppy disk -------> |            |
/// Secondary ATA ----> |_47_________|   Parallel Port 1----> |_39_________|
/// ```
pub fn init(enable: PicPort) {
    unsafe {
        // ICW1: 8086 mode with ICW4
        PIC1_CMD.write(PIC_CMD_INIT);
        PIC2_CMD.write(PIC_CMD_INIT);

        // ICW2 Master: IRQ # Offset (32)
        PIC1_DATA.write(PIC1_OFFSET);
        // ICW2 Slave: IRQ # Offset (40)
        PIC2_DATA.write(PIC2_OFFSET);

        // ICW3 Master: slaves on IRQs
        PIC1_DATA.write(0x04);
        // ICW3 Slave: connect to IRQ2 of the masters
        PIC2_DATA.write(0x02);

        //ICW4: 8086 mode
        PIC1_DATA.write(PIC_MODE_8086 | PIC_MODE_AUTO_EOI);
        PIC2_DATA.write(PIC_MODE_8086 | PIC_MODE_AUTO_EOI);

        // Set interrupt mask
        let [l, h] = (!enable.with_chain(true).0).to_le_bytes();
        PIC1_DATA.write(l);
        PIC2_DATA.write(h);
    }
}

/// Set the End Of Interrupt bit to allow new interrupts.
pub fn eoi(vector: u8) {
    const PIC1_END: u8 = PIC1_OFFSET + 7;
    const PIC2_END: u8 = PIC2_OFFSET + 7;
    match vector {
        PIC1_OFFSET..=PIC1_END => unsafe { PIC1_CMD.write(PIC_CMD_EOI) },
        PIC2_OFFSET..=PIC2_END => unsafe { PIC2_CMD.write(PIC_CMD_EOI) },
        _ => {}
    }
}

/// Disable the PIC
pub fn disable() {
    unsafe {
        // select Interrupt Mode Control Register (IMCR)
        Port::<u8>::new(0x22).write(0x70);
        // disable PIC Mode
        Port::<u8>::new(0x23).write(0x01);
    }
}