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

1// SPDX-License-Identifier: MPL-2.0
2
3use alloc::{boxed::Box, vec::Vec};
4use core::{
5    fmt,
6    ops::{Deref, DerefMut},
7};
8
9use ioapic::IoApic;
10use log::info;
11use spin::Once;
12
13use crate::{
14    Error, Result, arch::kernel::acpi::get_acpi_tables, io::IoMemAllocatorBuilder, irq::IrqLine,
15    sync::SpinLock,
16};
17
18mod ioapic;
19mod pic;
20
21/// An IRQ chip.
22///
23/// This abstracts the hardware IRQ chips (or IRQ controllers), allowing the bus or device drivers
24/// to enable [`IrqLine`]s (via, e.g., [`map_gsi_pin_to`]) regardless of the specifics of the IRQ chip.
25///
26/// In the x86 architecture, the underlying hardware is typically either 8259 Programmable
27/// Interrupt Controller (PIC) or I/O Advanced Programmable Interrupt Controller (I/O APIC).
28///
29/// [`map_gsi_pin_to`]: Self::map_gsi_pin_to
30pub struct IrqChip {
31    io_apics: SpinLock<Box<[IoApic]>>,
32    overrides: Box<[IsaOverride]>,
33}
34
35struct IsaOverride {
36    /// ISA IRQ source.
37    source: u8,
38    /// GSI target.
39    target: u32,
40}
41
42impl IrqChip {
43    /// Maps an IRQ pin specified by a GSI number to an IRQ line.
44    ///
45    /// ACPI represents all interrupts as "flat" values known as global system interrupts (GSIs).
46    /// So GSI numbers are well defined on all systems where the ACPI support is present.
47    //
48    // TODO: Confirm whether the interrupt numbers in the device tree on non-ACPI systems are the
49    // same as the GSI numbers.
50    pub fn map_gsi_pin_to(
51        &'static self,
52        irq_line: IrqLine,
53        gsi_index: u32,
54    ) -> Result<MappedIrqLine> {
55        let mut io_apics = self.io_apics.lock();
56
57        let io_apic = io_apics
58            .iter_mut()
59            .rev()
60            .find(|io_apic| io_apic.interrupt_base() <= gsi_index)
61            .unwrap();
62        let index_in_io_apic = (gsi_index - io_apic.interrupt_base())
63            .try_into()
64            .map_err(|_| Error::InvalidArgs)?;
65        io_apic.enable(index_in_io_apic, &irq_line)?;
66
67        Ok(MappedIrqLine {
68            irq_line,
69            gsi_index,
70            irq_chip: self,
71        })
72    }
73
74    fn disable_gsi(&self, gsi_index: u32) {
75        let mut io_apics = self.io_apics.lock();
76
77        let io_apic = io_apics
78            .iter_mut()
79            .rev()
80            .find(|io_apic| io_apic.interrupt_base() <= gsi_index)
81            .unwrap();
82        let index_in_io_apic = (gsi_index - io_apic.interrupt_base()) as u8;
83        io_apic.disable(index_in_io_apic).unwrap();
84    }
85
86    /// Maps an IRQ pin specified by an ISA interrupt number to an IRQ line.
87    ///
88    /// Industry Standard Architecture (ISA) is the 16-bit internal bus of IBM PC/AT. For
89    /// compatibility reasons, legacy devices such as keyboards connected via the i8042 PS/2
90    /// controller still use it.
91    ///
92    /// This method is x86-specific.
93    pub fn map_isa_pin_to(
94        &'static self,
95        irq_line: IrqLine,
96        isa_index: u8,
97    ) -> Result<MappedIrqLine> {
98        let gsi_index = self
99            .overrides
100            .iter()
101            .find(|isa_override| isa_override.source == isa_index)
102            .map(|isa_override| isa_override.target)
103            .unwrap_or(isa_index as u32);
104
105        self.map_gsi_pin_to(irq_line, gsi_index)
106    }
107
108    /// Counts the number of I/O APICs.
109    ///
110    /// If I/O APICs are in use, this method counts how many I/O APICs are in use, otherwise, this
111    /// method return zero.
112    ///
113    /// This method exists due to a workaround used in virtio-mmio bus probing. It should be
114    /// removed once the workaround is retired. Therefore, only use this method if absolutely
115    /// necessary.
116    ///
117    /// This method is x86-specific.
118    pub fn count_io_apics(&self) -> usize {
119        self.io_apics.lock().len()
120    }
121}
122
123/// An [`IrqLine`] mapped to an IRQ pin managed by an [`IrqChip`].
124///
125/// When the object is dropped, the IRQ line will be unmapped by the IRQ chip.
126pub struct MappedIrqLine {
127    irq_line: IrqLine,
128    gsi_index: u32,
129    irq_chip: &'static IrqChip,
130}
131
132impl fmt::Debug for MappedIrqLine {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        f.debug_struct("MappedIrqLine")
135            .field("irq_line", &self.irq_line)
136            .field("gsi_index", &self.gsi_index)
137            .finish_non_exhaustive()
138    }
139}
140
141impl Deref for MappedIrqLine {
142    type Target = IrqLine;
143
144    fn deref(&self) -> &Self::Target {
145        &self.irq_line
146    }
147}
148
149impl DerefMut for MappedIrqLine {
150    fn deref_mut(&mut self) -> &mut Self::Target {
151        &mut self.irq_line
152    }
153}
154
155impl Drop for MappedIrqLine {
156    fn drop(&mut self) {
157        self.irq_chip.disable_gsi(self.gsi_index)
158    }
159}
160
161/// The [`IrqChip`] singleton.
162pub static IRQ_CHIP: Once<IrqChip> = Once::new();
163
164pub(in crate::arch) fn init(io_mem_builder: &IoMemAllocatorBuilder) {
165    use acpi::madt::{Madt, MadtEntry};
166
167    // If there are no ACPI tables, or the ACPI tables do not provide us with information about
168    // the I/O APIC, we may need to find another way to determine the I/O APIC address
169    // correctly and reliably (e.g., by parsing the MultiProcessor Specification, which has
170    // been deprecated for a long time and may not even exist in modern hardware).
171    let acpi_tables = get_acpi_tables().unwrap();
172    let madt_table = acpi_tables.find_table::<Madt>().unwrap();
173
174    // "A one indicates that the system also has a PC-AT-compatible dual-8259 setup. The 8259
175    // vectors must be disabled (that is, masked) when enabling the ACPI APIC operation"
176    const PCAT_COMPAT: u32 = 1;
177    if madt_table.get().flags & PCAT_COMPAT != 0 {
178        pic::init_and_disable();
179    }
180
181    let mut io_apics = Vec::with_capacity(2);
182    let mut isa_overrides = Vec::new();
183
184    const BUS_ISA: u8 = 0; // "0 Constant, meaning ISA".
185
186    for madt_entry in madt_table.get().entries() {
187        match madt_entry {
188            MadtEntry::IoApic(madt_io_apic) => {
189                // SAFETY: We trust the ACPI tables (as well as the MADTs in them), from which the
190                // base address is obtained, so it is a valid I/O APIC base address.
191                let io_apic = unsafe {
192                    IoApic::new(
193                        madt_io_apic.io_apic_address as usize,
194                        madt_io_apic.global_system_interrupt_base,
195                        io_mem_builder,
196                    )
197                };
198                io_apics.push(io_apic);
199            }
200            MadtEntry::InterruptSourceOverride(madt_isa_override)
201                if madt_isa_override.bus == BUS_ISA =>
202            {
203                let isa_override = IsaOverride {
204                    source: madt_isa_override.irq,
205                    target: madt_isa_override.global_system_interrupt,
206                };
207                isa_overrides.push(isa_override);
208            }
209            _ => {}
210        }
211    }
212
213    if isa_overrides.is_empty() {
214        // TODO: QEMU MicroVM does not provide any interrupt source overrides. Therefore, the timer
215        // interrupt used by the PIT will not work. Is this a bug in QEMU MicroVM? Why won't this
216        // affect operating systems such as Linux?
217        isa_overrides.push(IsaOverride {
218            source: 0, // Timer ISA IRQ
219            target: 2, // Timer GSI
220        });
221    }
222
223    for isa_override in isa_overrides.iter() {
224        info!(
225            "[IOAPIC]: Override ISA interrupt {} for GSI {}",
226            isa_override.source, isa_override.target
227        );
228    }
229
230    io_apics.sort_by_key(|io_apic| io_apic.interrupt_base());
231    assert!(!io_apics.is_empty(), "No I/O APICs found");
232    assert_eq!(
233        io_apics[0].interrupt_base(),
234        0,
235        "No I/O APIC with zero interrupt base found"
236    );
237
238    let irq_chip = IrqChip {
239        io_apics: SpinLock::new(io_apics.into_boxed_slice()),
240        overrides: isa_overrides.into_boxed_slice(),
241    };
242    IRQ_CHIP.call_once(|| irq_chip);
243}