File size: 9,924 Bytes
8df6da4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// http://download.intel.com/design/chipsets/datashts/29056601.pdf

use crate::cpu::{apic, global_pointers::acpi_enabled};
use std::sync::{Mutex, MutexGuard};

const IOAPIC_LOG_VERBOSE: bool = false;

const IOREGSEL: u32 = 0;
const IOWIN: u32 = 0x10;

const IOAPIC_IRQ_COUNT: usize = 24;

const IOAPIC_FIRST_IRQ_REG: u32 = 0x10;
const IOAPIC_LAST_IRQ_REG: u32 = 0x10 + 2 * IOAPIC_IRQ_COUNT as u32;

const IOAPIC_ID: u32 = 0; // must match value in seabios

pub const IOAPIC_CONFIG_TRIGGER_MODE_LEVEL: u32 = 1 << 15;

const IOAPIC_CONFIG_MASKED: u32 = 1 << 16;
const IOAPIC_CONFIG_DELIVS: u32 = 1 << 12;
const IOAPIC_CONFIG_REMOTE_IRR: u32 = 1 << 14;
const IOAPIC_CONFIG_READONLY_MASK: u32 =
    IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000;

const IOAPIC_DELIVERY_FIXED: u8 = 0;
const IOAPIC_DELIVERY_LOWEST_PRIORITY: u8 = 1;
const _IOAPIC_DELIVERY_NMI: u8 = 4;
const _IOAPIC_DELIVERY_INIT: u8 = 5;

const DELIVERY_MODES: [&str; 8] = [
    "Fixed (0)",
    "Lowest Prio (1)",
    "SMI (2)",
    "Reserved (3)",
    "NMI (4)",
    "INIT (5)",
    "Reserved (6)",
    "ExtINT (7)",
];

const DESTINATION_MODES: [&str; 2] = ["physical", "logical"];

// keep in sync with cpu.js
#[allow(dead_code)]
const IOAPIC_STRUCT_SIZE: usize = 4 * 52;

// Note: JavaScript (cpu.get_state_apic) depens on this layout
const _: () = assert!(std::mem::offset_of!(Ioapic, ioredtbl_destination) == 24 * 4);
const _: () = assert!(std::mem::offset_of!(Ioapic, ioregsel) == 48 * 4);
const _: () = assert!(std::mem::offset_of!(Ioapic, irq_value) == 51 * 4);
const _: () = assert!(std::mem::size_of::<Ioapic>() == IOAPIC_STRUCT_SIZE);
#[repr(C)]
struct Ioapic {
    ioredtbl_config: [u32; IOAPIC_IRQ_COUNT],
    ioredtbl_destination: [u32; IOAPIC_IRQ_COUNT],
    ioregsel: u32,
    ioapic_id: u32,
    irr: u32,
    irq_value: u32,
}

static IOAPIC: Mutex<Ioapic> = Mutex::new(Ioapic {
    ioredtbl_config: [IOAPIC_CONFIG_MASKED; IOAPIC_IRQ_COUNT],
    ioredtbl_destination: [0; IOAPIC_IRQ_COUNT],
    ioregsel: 0,
    ioapic_id: IOAPIC_ID,
    irr: 0,
    irq_value: 0,
});

fn get_ioapic() -> MutexGuard<'static, Ioapic> { IOAPIC.try_lock().unwrap() }

#[no_mangle]
pub fn get_ioapic_addr() -> u32 { &raw mut *get_ioapic() as u32 }

pub fn remote_eoi(apic: &mut apic::Apic, vector: u8) {
    remote_eoi_internal(&mut get_ioapic(), apic, vector);
}

fn remote_eoi_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, vector: u8) {
    for i in 0..IOAPIC_IRQ_COUNT as u8 {
        let config = ioapic.ioredtbl_config[i as usize];

        if (config & 0xFF) as u8 == vector && config & IOAPIC_CONFIG_REMOTE_IRR != 0 {
            dbg_log!("Clear remote IRR for irq={:x}", i);
            ioapic.ioredtbl_config[i as usize] &= !IOAPIC_CONFIG_REMOTE_IRR;
            check_irq(ioapic, apic, i);
        }
    }
}

fn check_irq(ioapic: &mut Ioapic, apic: &mut apic::Apic, irq: u8) {
    let mask = 1 << irq;

    if ioapic.irr & mask == 0 {
        return;
    }

    let config = ioapic.ioredtbl_config[irq as usize];

    if config & IOAPIC_CONFIG_MASKED == 0 {
        let delivery_mode = ((config >> 8) & 7) as u8;
        let destination_mode = ((config >> 11) & 1) as u8;
        let vector = (config & 0xFF) as u8;
        let destination = (ioapic.ioredtbl_destination[irq as usize] >> 24) as u8;
        let is_level =
            config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;

        if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == 0 {
            ioapic.irr &= !mask;
        }
        else {
            ioapic.ioredtbl_config[irq as usize] |= IOAPIC_CONFIG_REMOTE_IRR;

            if config & IOAPIC_CONFIG_REMOTE_IRR != 0 {
                dbg_log!("No route: level interrupt and remote IRR still set");
                return;
            }
        }

        if delivery_mode == IOAPIC_DELIVERY_FIXED
            || delivery_mode == IOAPIC_DELIVERY_LOWEST_PRIORITY
        {
            apic::route(
                apic,
                vector,
                delivery_mode,
                is_level,
                destination,
                destination_mode,
            );
        }
        else {
            dbg_assert!(false, "TODO");
        }

        ioapic.ioredtbl_config[irq as usize] &= !IOAPIC_CONFIG_DELIVS;
    }
}

pub fn set_irq(i: u8) { set_irq_internal(&mut get_ioapic(), &mut apic::get_apic(), i) }

fn set_irq_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, i: u8) {
    if i as usize >= IOAPIC_IRQ_COUNT {
        dbg_assert!(false, "Bad irq: {}", i);
        return;
    }

    let mask = 1 << i;

    if ioapic.irq_value & mask == 0 {
        if IOAPIC_LOG_VERBOSE {
            dbg_log!("apic set irq {}", i);
        }

        ioapic.irq_value |= mask;

        let config = ioapic.ioredtbl_config[i as usize];
        if config & (IOAPIC_CONFIG_TRIGGER_MODE_LEVEL | IOAPIC_CONFIG_MASKED)
            == IOAPIC_CONFIG_MASKED
        {
            // edge triggered and masked
            return;
        }

        ioapic.irr |= mask;

        check_irq(ioapic, apic, i);
    }
}

pub fn clear_irq(i: u8) { clear_irq_internal(&mut get_ioapic(), i) }

fn clear_irq_internal(ioapic: &mut Ioapic, i: u8) {
    if i as usize >= IOAPIC_IRQ_COUNT {
        dbg_assert!(false, "Bad irq: {}", i);
        return;
    }

    let mask = 1 << i;

    if ioapic.irq_value & mask == mask {
        ioapic.irq_value &= !mask;

        let config = ioapic.ioredtbl_config[i as usize];
        if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL != 0 {
            ioapic.irr &= !mask;
        }
    }
}

pub fn read32(addr: u32) -> u32 {
    if unsafe { !*acpi_enabled } {
        return 0;
    }
    read32_internal(&mut get_ioapic(), addr)
}

fn read32_internal(ioapic: &mut Ioapic, addr: u32) -> u32 {
    match addr {
        IOREGSEL => ioapic.ioregsel,
        IOWIN => match ioapic.ioregsel {
            0 => {
                dbg_log!("IOAPIC Read id");
                ioapic.ioapic_id << 24
            },
            1 => {
                dbg_log!("IOAPIC Read version");
                0x11 | (IOAPIC_IRQ_COUNT as u32 - 1) << 16
            },
            2 => {
                dbg_log!("IOAPIC Read arbitration id");
                ioapic.ioapic_id << 24
            },
            IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => {
                let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8;
                let index = ioapic.ioregsel & 1;

                if index != 0 {
                    let value = ioapic.ioredtbl_destination[irq as usize];
                    dbg_log!("IOAPIC Read destination irq={:x} -> {:08x}", irq, value);
                    value
                }
                else {
                    let value = ioapic.ioredtbl_config[irq as usize];
                    dbg_log!("IOAPIC Read config irq={:x} -> {:08x}", irq, value);
                    value
                }
            },
            reg => {
                dbg_assert!(false, "IOAPIC register read outside of range {:x}", reg);
                0
            },
        },
        _ => {
            dbg_assert!(false, "Unaligned or oob IOAPIC memory read: {:x}", addr);
            0
        },
    }
}

pub fn write32(addr: u32, value: u32) {
    if unsafe { !*acpi_enabled } {
        return;
    }
    write32_internal(&mut get_ioapic(), &mut apic::get_apic(), addr, value)
}

fn write32_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, addr: u32, value: u32) {
    //dbg_log!("IOAPIC write {:x} <- {:08x}", reg, value);

    match addr {
        IOREGSEL => ioapic.ioregsel = value,
        IOWIN => match ioapic.ioregsel {
            0 => ioapic.ioapic_id = (value >> 24) & 0x0F,
            1 | 2 => {
                dbg_log!("IOAPIC Invalid write: {}", ioapic.ioregsel);
            },
            IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => {
                let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8;
                let index = ioapic.ioregsel & 1;

                if index != 0 {
                    dbg_log!(
                        "Write destination {:08x} irq={:x} dest={:02x}",
                        value,
                        irq,
                        value >> 24
                    );
                    ioapic.ioredtbl_destination[irq as usize] = value & 0xFF000000;
                }
                else {
                    let old_value = ioapic.ioredtbl_config[irq as usize] as u32;
                    ioapic.ioredtbl_config[irq as usize] = (value & !IOAPIC_CONFIG_READONLY_MASK)
                        | (old_value & IOAPIC_CONFIG_READONLY_MASK);

                    let vector = value & 0xFF;
                    let delivery_mode = (value >> 8) & 7;
                    let destination_mode = (value >> 11) & 1;
                    let is_level = (value >> 15) & 1;
                    let disabled = (value >> 16) & 1;

                    dbg_log!(
                            "Write config {:08x} irq={:x} vector={:02x} deliverymode={} destmode={} is_level={} disabled={}",
                            value,
                            irq,
                            vector,
                            DELIVERY_MODES[delivery_mode as usize],
                            DESTINATION_MODES[destination_mode as usize],
                            is_level,
                            disabled
                        );

                    check_irq(ioapic, apic, irq);
                }
            },
            reg => {
                dbg_assert!(
                    false,
                    "IOAPIC register write outside of range {:x} <- {:x}",
                    reg,
                    value
                )
            },
        },
        _ => {
            dbg_assert!(
                false,
                "Unaligned or oob IOAPIC memory write: {:x} <- {:x}",
                addr,
                value
            )
        },
    }
}