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
use crate::cpu;
use hal_core::interrupt::{Handlers, RegistrationError};

#[derive(Debug)]
pub(crate) struct Pic {
    address: u8,
    command: cpu::Port,
    data: cpu::Port,
}

impl Pic {
    const fn new(address: u8, command: u16, data: u16) -> Self {
        Self {
            address,
            command: cpu::Port::at(command),
            data: cpu::Port::at(data),
        }
    }
}

#[derive(Debug)]
pub struct CascadedPic {
    sisters: PicSisters,
}

// two of them

#[derive(Debug)]
struct PicSisters {
    big: Pic,
    little: Pic,
}

impl PicSisters {
    pub(crate) const fn new() -> Self {
        Self {
            // primary and secondary PIC addresses (0 and 8) are Just How IBM Did It.
            // yes, they overlap with x86 interrupt numbers. it's not good.
            // port numbers are also magic IBM PC/AT architecture numbers.
            big: Pic::new(0, 0x20, 0x21),
            little: Pic::new(8, 0xa0, 0xa1),
        }
    }
}

impl CascadedPic {
    pub(crate) const fn new() -> Self {
        Self {
            sisters: PicSisters::new(),
        }
    }

    pub(crate) fn end_interrupt(&mut self, num: u8) {
        const END_INTERRUPT: u8 = 0x20; // from osdev wiki
        if num >= self.sisters.little.address && num < self.sisters.little.address + 8 {
            unsafe {
                self.sisters.little.command.writeb(END_INTERRUPT);
            }
        }

        unsafe {
            self.sisters.big.command.writeb(END_INTERRUPT);
        }
    }
}

impl hal_core::interrupt::Control for CascadedPic {
    type Registers = super::Registers;
    fn register_handlers<H>(&mut self) -> Result<(), hal_core::interrupt::RegistrationError>
    where
        H: Handlers<super::Registers>,
    {
        Err(RegistrationError::other(
            "x86_64 handlers must be registered via the IDT, not to the PIC interrupt component",
        ))
    }

    unsafe fn disable(&mut self) {
        self.sisters.big.data.writeb(0xff);
        self.sisters.little.data.writeb(0xff);
    }

    unsafe fn enable(&mut self) {
        // TODO(ixi): confirm this?? it looks like "disable" is "write a 1 to set the line masked"
        //            so maybe it stands to reason that writing a 0 unmasks an interrupt?
        self.sisters.big.data.writeb(0x00);
        self.sisters.little.data.writeb(0x00);
    }

    fn is_enabled(&self) -> bool {
        unimplemented!("ixi do this one!!!")
    }
}

impl CascadedPic {
    pub(crate) unsafe fn set_irq_address(&mut self, primary_start: u8, secondary_start: u8) {
        // iowait and its uses below are guidance from the osdev wiki for compatibility with "older
        // machines". it is not entirely clear what "older machines" exactly means, or where this
        // is or is not necessary precisely. this code happens to work in qemu without `iowait()`,
        // but is largely untested on real hardware where this may be a concern.
        let iowait = || cpu::Port::at(0x80).writeb(0);

        let primary_mask = self.sisters.big.data.readb();
        let secondary_mask = self.sisters.little.data.readb();

        const EXTENDED_CONFIG: u8 = 0x01; // if present, there are four initialization control words
        const PIC_INIT: u8 = 0x10; // reinitialize the 8259 PIC

        self.sisters.big.command.writeb(PIC_INIT | EXTENDED_CONFIG);
        iowait();
        self.sisters
            .little
            .command
            .writeb(PIC_INIT | EXTENDED_CONFIG);
        iowait();
        self.sisters.big.data.writeb(primary_start);
        iowait();
        self.sisters.little.data.writeb(secondary_start);
        iowait();
        // magic number: secondary pic is at cascade identity 2 (how does 4 say this ???)
        // !UPDATE! 4 says this because this word is a bitmask:
        //
        //   76543210
        // 0b00000100
        //        |
        //        --- bit 2 set means there is another 8259 cascaded at address 2
        //
        // bit 0 would be set if there was only one controller in the system, in `single` mode, and
        // is non-zero in all other modes. the exact read from the intel manual is not immediately
        // clear:
        // ```
        // in sisters mode, a `1` is set for each little sister in the system.
        // ```
        // which means bit 1 indicates a little sister at cascade identity 1, or, as in an IBM
        // PC/AT system, a little sister is present at cascade identity 2, indicated by bit 2 being
        // set for a bitmask of 0b0000_0100. there is a slightly faded diagram that describes ICW3
        // which has not been OCR'd, in the copy of the intel document you might find online.
        self.sisters.big.data.writeb(4);
        iowait();
        // magic number: secondary pic has cascade identity 2 (??)
        // !UPDATE! that's just how IBM PC/AT systems be
        self.sisters.little.data.writeb(2);
        iowait();
        self.sisters.big.data.writeb(1); // 8086/88 (MCS-80/85) mode
        iowait();
        self.sisters.little.data.writeb(1); // 8086/88 (MCS-80/85) mode
        iowait();

        self.sisters.big.data.writeb(primary_mask);
        iowait();
        self.sisters.little.data.writeb(secondary_mask);
        iowait();
        self.sisters.big.address = primary_start;
        self.sisters.little.address = secondary_start;
    }
}