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