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);
}