ostd/arch/x86/kernel/irq/
mod.rs

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
// SPDX-License-Identifier: MPL-2.0

use alloc::{boxed::Box, vec::Vec};
use core::{
    fmt,
    ops::{Deref, DerefMut},
};

use ioapic::IoApic;
use log::info;
use spin::Once;

use super::acpi::get_acpi_tables;
use crate::{io::IoMemAllocatorBuilder, sync::SpinLock, trap::irq::IrqLine, Error, Result};

mod ioapic;
mod pic;

/// An IRQ chip.
///
/// This abstracts the hardware IRQ chips (or IRQ controllers), allowing the bus or device drivers
/// to enable [`IrqLine`]s (via, e.g., [`map_gsi_pin_to`]) regardless of the specifics of the IRQ chip.
///
/// In the x86 architecture, the underlying hardware is typically either 8259 Programmable
/// Interrupt Controller (PIC) or I/O Advanced Programmable Interrupt Controller (I/O APIC).
///
/// [`map_gsi_pin_to`]: Self::map_gsi_pin_to
pub struct IrqChip {
    io_apics: SpinLock<Box<[IoApic]>>,
    overrides: Box<[IsaOverride]>,
}

struct IsaOverride {
    /// ISA IRQ source.
    source: u8,
    /// GSI target.
    target: u32,
}

impl IrqChip {
    /// Maps an IRQ pin specified by a GSI number to an IRQ line.
    ///
    /// ACPI represents all interrupts as "flat" values known as global system interrupts (GSIs).
    /// So GSI numbers are well defined on all systems where the ACPI support is present.
    //
    // TODO: Confirm whether the interrupt numbers in the device tree on non-ACPI systems are the
    // same as the GSI numbers.
    pub fn map_gsi_pin_to(
        &'static self,
        irq_line: IrqLine,
        gsi_index: u32,
    ) -> Result<MappedIrqLine> {
        let mut io_apics = self.io_apics.lock();

        let io_apic = io_apics
            .iter_mut()
            .rev()
            .find(|io_apic| io_apic.interrupt_base() <= gsi_index)
            .unwrap();
        let index_in_io_apic = (gsi_index - io_apic.interrupt_base())
            .try_into()
            .map_err(|_| Error::InvalidArgs)?;
        io_apic.enable(index_in_io_apic, &irq_line)?;

        Ok(MappedIrqLine {
            irq_line,
            gsi_index,
            irq_chip: self,
        })
    }

    fn disable_gsi(&self, gsi_index: u32) {
        let mut io_apics = self.io_apics.lock();

        let io_apic = io_apics
            .iter_mut()
            .rev()
            .find(|io_apic| io_apic.interrupt_base() <= gsi_index)
            .unwrap();
        let index_in_io_apic = (gsi_index - io_apic.interrupt_base()) as u8;
        io_apic.disable(index_in_io_apic).unwrap();
    }

    /// Maps an IRQ pin specified by an ISA interrupt number to an IRQ line.
    ///
    /// Industry Standard Architecture (ISA) is the 16-bit internal bus of IBM PC/AT. For
    /// compatibility reasons, legacy devices such as keyboards connected via the i8042 PS/2
    /// controller still use it.
    ///
    /// This method is x86-specific.
    pub fn map_isa_pin_to(
        &'static self,
        irq_line: IrqLine,
        isa_index: u8,
    ) -> Result<MappedIrqLine> {
        let gsi_index = self
            .overrides
            .iter()
            .find(|isa_override| isa_override.source == isa_index)
            .map(|isa_override| isa_override.target)
            .unwrap_or(isa_index as u32);

        self.map_gsi_pin_to(irq_line, gsi_index)
    }

    /// Counts the number of I/O APICs.
    ///
    /// If I/O APICs are in use, this method counts how many I/O APICs are in use, otherwise, this
    /// method return zero.
    ///
    /// This method exists due to a workaround used in virtio-mmio bus probing. It should be
    /// removed once the workaround is retired. Therefore, only use this method if absolutely
    /// necessary.
    ///
    /// This method is x86-specific.
    pub fn count_io_apics(&self) -> usize {
        self.io_apics.lock().len()
    }
}

/// An [`IrqLine`] mapped to an IRQ pin managed by an [`IrqChip`].
///
/// When the object is dropped, the IRQ line will be unmapped by the IRQ chip.
pub struct MappedIrqLine {
    irq_line: IrqLine,
    gsi_index: u32,
    irq_chip: &'static IrqChip,
}

impl fmt::Debug for MappedIrqLine {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("MappedIrqLine")
            .field("irq_line", &self.irq_line)
            .field("gsi_index", &self.gsi_index)
            .finish_non_exhaustive()
    }
}

impl Deref for MappedIrqLine {
    type Target = IrqLine;

    fn deref(&self) -> &Self::Target {
        &self.irq_line
    }
}

impl DerefMut for MappedIrqLine {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.irq_line
    }
}

impl Drop for MappedIrqLine {
    fn drop(&mut self) {
        self.irq_chip.disable_gsi(self.gsi_index)
    }
}

/// The [`IrqChip`] singleton.
pub static IRQ_CHIP: Once<IrqChip> = Once::new();

pub(in crate::arch) fn init(io_mem_builder: &IoMemAllocatorBuilder) {
    use acpi::madt::{Madt, MadtEntry};

    // If there are no ACPI tables, or the ACPI tables do not provide us with information about
    // the I/O APIC, we may need to find another way to determine the I/O APIC address
    // correctly and reliably (e.g., by parsing the MultiProcessor Specification, which has
    // been deprecated for a long time and may not even exist in modern hardware).
    let acpi_tables = get_acpi_tables().unwrap();
    let madt_table = acpi_tables.find_table::<Madt>().unwrap();

    // "A one indicates that the system also has a PC-AT-compatible dual-8259 setup. The 8259
    // vectors must be disabled (that is, masked) when enabling the ACPI APIC operation"
    const PCAT_COMPAT: u32 = 1;
    if madt_table.get().flags & PCAT_COMPAT != 0 {
        pic::init_and_disable();
    }

    let mut io_apics = Vec::with_capacity(2);
    let mut isa_overrides = Vec::new();

    const BUS_ISA: u8 = 0; // "0 Constant, meaning ISA".

    for madt_entry in madt_table.get().entries() {
        match madt_entry {
            MadtEntry::IoApic(madt_io_apic) => {
                // SAFETY: We trust the ACPI tables (as well as the MADTs in them), from which the
                // base address is obtained, so it is a valid I/O APIC base address.
                let io_apic = unsafe {
                    IoApic::new(
                        madt_io_apic.io_apic_address as usize,
                        madt_io_apic.global_system_interrupt_base,
                        io_mem_builder,
                    )
                };
                io_apics.push(io_apic);
            }
            MadtEntry::InterruptSourceOverride(madt_isa_override)
                if madt_isa_override.bus == BUS_ISA =>
            {
                let isa_override = IsaOverride {
                    source: madt_isa_override.irq,
                    target: madt_isa_override.global_system_interrupt,
                };
                isa_overrides.push(isa_override);
            }
            _ => {}
        }
    }

    if isa_overrides.is_empty() {
        // TODO: QEMU MicroVM does not provide any interrupt source overrides. Therefore, the timer
        // interrupt used by the PIT will not work. Is this a bug in QEMU MicroVM? Why won't this
        // affect operating systems such as Linux?
        isa_overrides.push(IsaOverride {
            source: 0, // Timer ISA IRQ
            target: 2, // Timer GSI
        });
    }

    for isa_override in isa_overrides.iter() {
        info!(
            "[IOAPIC]: Override ISA interrupt {} for GSI {}",
            isa_override.source, isa_override.target
        );
    }

    io_apics.sort_by_key(|io_apic| io_apic.interrupt_base());
    assert!(!io_apics.is_empty(), "No I/O APICs found");
    assert_eq!(
        io_apics[0].interrupt_base(),
        0,
        "No I/O APIC with zero interrupt base found"
    );

    let irq_chip = IrqChip {
        io_apics: SpinLock::new(io_apics.into_boxed_slice()),
        overrides: isa_overrides.into_boxed_slice(),
    };
    IRQ_CHIP.call_once(|| irq_chip);
}