Skip to main content

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