blob: 7ddcf7effc222d909730daee5399bcbb4de4dd53 [file] [edit]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
use super::interrupt_manager::InterruptManager;
use super::pic::{pic_disable, pic_map};
use crate::arch_rs::x86::apic::{
ApicInterruptDeliveryMode, ApicInterruptDstMode, GsiRange, IoApicDescriptor, IoApicIsaOverride,
};
use crate::arch_rs::x86::interrupts::{X86_INT_PLATFORM_BASE, X86_INT_PLATFORM_MAX};
use crate::arch_rs::x86::ioapic::{
apic_io_configure_irq, apic_io_configure_irq_vector, apic_io_configure_isa_irq,
apic_io_fetch_irq_config_safe, apic_io_fetch_irq_vector, apic_io_get_gsi_range,
apic_io_init_safe, apic_io_is_valid_irq, apic_io_isa_to_global, apic_io_mask_irq,
};
use crate::dev_interrupt::{InterruptHandler, InterruptPolarity, InterruptTriggerMode, MsiBlock};
use crate::object::ResourceDispatcher;
use core::pin::Pin;
use lazy_init::LazyInit;
use zx_status::Status;
use zx_types::{ZX_RSRC_KIND_IRQ, zx_status_t};
struct RealIoApic;
impl super::interrupt_manager::IoApic for RealIoApic {
fn is_valid_interrupt(vector: u32, flags: u32) -> bool {
is_valid_interrupt(vector, flags)
}
fn fetch_irq_vector(vector: u32) -> u8 {
apic_io_fetch_irq_vector(vector)
}
fn configure_irq_vector(global_irq: u32, x86_vector: u8) {
apic_io_configure_irq_vector(global_irq, x86_vector);
}
fn configure_irq(
global_irq: u32,
trig_mode: InterruptTriggerMode,
polarity: InterruptPolarity,
del_mode: ApicInterruptDeliveryMode,
mask: bool,
dst_mode: ApicInterruptDstMode,
dst: u8,
vector: u8,
) {
apic_io_configure_irq(
global_irq, trig_mode, polarity, del_mode, mask, dst_mode, dst, vector,
);
}
fn mask_irq(global_irq: u32, mask: bool) {
apic_io_mask_irq(global_irq, mask);
}
fn fetch_irq_config(
global_irq: u32,
) -> Result<(InterruptTriggerMode, InterruptPolarity), Status> {
apic_io_fetch_irq_config_safe(global_irq)
}
}
static INTERRUPT_MANAGER: LazyInit<InterruptManager<RealIoApic>> = LazyInit::uninit();
// Values from ioapic to cache for calls to interrupt_get_base_vector / interrupt_get_max_vector
static GSI_RANGE: LazyInit<GsiRange> = LazyInit::uninit();
unsafe extern "C" {
fn apic_vm_init();
fn apic_local_init();
fn apic_issue_eoi();
fn cpp_arch_ints_disabled() -> bool;
fn apic_bsp_id() -> u8;
}
// Convert an ACPI entry into the format required by the platform's APIC code.
fn parse_isa_override(
record: &acpi_lite::structures::AcpiMadtIntSourceOverrideEntry,
) -> IoApicIsaOverride {
// 0 means ISA, ISOs are only ever for ISA IRQs.
if record.bus != 0 {
panic!("Invalid bus for IO APIC interrupt override.");
}
// "Conforms" below means conforms to the bus spec: edge triggered and active high.
let flags = record.flags;
let polarity = match flags & acpi_lite::structures::ACPI_MADT_FLAG_POLARITY_MASK {
acpi_lite::structures::ACPI_MADT_FLAG_POLARITY_CONFORMS
| acpi_lite::structures::ACPI_MADT_FLAG_POLARITY_HIGH => InterruptPolarity::High,
acpi_lite::structures::ACPI_MADT_FLAG_POLARITY_LOW => InterruptPolarity::Low,
p => panic!("Unknown IRQ polarity in override: {}", p),
};
let trigger_mode = match flags & acpi_lite::structures::ACPI_MADT_FLAG_TRIGGER_MASK {
acpi_lite::structures::ACPI_MADT_FLAG_TRIGGER_CONFORMS
| acpi_lite::structures::ACPI_MADT_FLAG_TRIGGER_EDGE => InterruptTriggerMode::Edge,
acpi_lite::structures::ACPI_MADT_FLAG_TRIGGER_LEVEL => InterruptTriggerMode::Level,
t => panic!("Unknown IRQ trigger in override: {}", t),
};
IoApicIsaOverride {
isa_irq: record.source,
remapped: true,
tm: trigger_mode,
pol: polarity,
global_irq: record.global_sys_interrupt,
}
}
/// Initializes the APIC platform and registers the interrupt manager.
fn platform_init_apic(_level: init::LkInitLevel) {
pic_map(0x20, 0x28);
pic_disable();
let parser = super::acpi::global_acpi_lite_parser();
// Enumerate the IO APICs
let mut descriptors = fbl::Vector::<IoApicDescriptor>::new();
let status = acpi_lite::enumerate_io_apics(parser, |descriptor| {
descriptors
.push_back(IoApicDescriptor {
apic_id: descriptor.io_apic_id,
global_irq_base: descriptor.global_system_interrupt_base,
paddr: descriptor.io_apic_address as usize,
})
.map_err(|_| Status::NO_MEMORY)
});
if status.is_err() {
panic!("Could not get IO APIC details: {:?}", status);
}
// Enumerate interrupt source overrides.
let mut overrides = fbl::Vector::<IoApicIsaOverride>::new();
let status = acpi_lite::enumerate_io_apic_isa_overrides(parser, |isa_override| {
overrides.push_back(parse_isa_override(isa_override)).map_err(|_| Status::NO_MEMORY)
});
if status.is_err() {
panic!("Could not get interrupt source overrides: {:?}", status);
}
// SAFETY: These functions initialize the virtual memory mappings and local APIC registers
// on the bootstrap processor. This must be done before configuring any interrupt routing.
unsafe {
apic_vm_init();
apic_local_init();
}
// Call ioapic init
apic_io_init_safe(&descriptors, &overrides);
let range = apic_io_get_gsi_range();
// SAFETY: Called once during boot, serialized with respect to any other access.
unsafe {
GSI_RANGE.init(range);
}
// SAFETY: `cpp_arch_ints_disabled` returns if interrupts are disabled for the current cpu,
// which is safe to call at any time.
assert!(unsafe { cpp_arch_ints_disabled() });
// Initialize the delivery modes/targets for the ISA interrupts
// SAFETY: `apic_bsp_id` is safe to call to retrieve the bootstrap processor APIC ID
// once the APIC vm is initialized.
let bsp_apic_id = unsafe { apic_bsp_id() };
for irq in 0..8 {
if irq != 2 {
// Explicitly skip mapping the PIC2 interrupt, since it is actually
// just used internally on the PICs for daisy chaining. QEMU remaps
// ISA IRQ 0 to global IRQ 2, but does not remap ISA IRQ 2 off of
// global IRQ 2, so skipping this mapping also prevents a collision
// with the PIT IRQ.
apic_io_configure_isa_irq(
irq,
ApicInterruptDeliveryMode::Fixed,
true, // mask
ApicInterruptDstMode::Physical,
bsp_apic_id,
0,
);
}
apic_io_configure_isa_irq(
irq + 8,
ApicInterruptDeliveryMode::Fixed,
true, // mask
ApicInterruptDstMode::Physical,
bsp_apic_id,
0,
);
}
// Initialize the global INTERRUPT_MANAGER
// SAFETY: Called once during boot, serialized with respect to any other access.
let _ = unsafe {
Pin::static_ref(&INTERRUPT_MANAGER).init_pin(InterruptManager::<RealIoApic>::new())
};
let status = (*INTERRUPT_MANAGER).init();
if status.is_err() {
panic!("InterruptManager init failed: {:?}", status);
}
let status = ResourceDispatcher::initialize_allocator(
ZX_RSRC_KIND_IRQ,
interrupt_get_base_vector() as u64,
interrupt_get_max_vector() as usize,
);
assert!(status.is_ok());
}
/// Handles a platform interrupt from an interrupt vector.
///
/// # Safety
/// This function is called directly from the assembly interrupt entry point.
/// `frame` must be a valid pointer to the interrupt frame containing the CPU registers,
/// and interrupts must be disabled on the current CPU when this is called.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn platform_irq(frame: *const crate::arch_rs::x86::Iframe) {
crate::kernel::stats::inc_interrupts();
// SAFETY: `frame` is a valid pointer to an interrupt register state frame.
let x86_vector = unsafe { (*frame).vector };
debug_assert!(
x86_vector >= X86_INT_PLATFORM_BASE.0 as u64 && x86_vector <= X86_INT_PLATFORM_MAX.0 as u64
);
INTERRUPT_MANAGER.invoke_x86_vector(x86_vector as u8);
// SAFETY: Issuing an EOI is required to acknowledge the interrupt at the APIC level
// and is safe to call inside the interrupt handler.
unsafe {
apic_issue_eoi();
}
}
/// Registers the handler for the specified vector.
#[unsafe(no_mangle)]
pub extern "C" fn register_int_handler(
vector: u32,
handler: InterruptHandler,
) -> Result<(), Status> {
INTERRUPT_MANAGER.register_interrupt_handler(vector, handler, false)
}
/// Registers a permanent handler for the specified vector.
#[unsafe(no_mangle)]
pub extern "C" fn register_permanent_int_handler(
vector: u32,
handler: InterruptHandler,
) -> Result<(), Status> {
INTERRUPT_MANAGER.register_interrupt_handler(vector, handler, true)
}
/// Registers the MSI handler.
#[unsafe(no_mangle)]
pub extern "C" fn msi_register_handler(block: &MsiBlock, msi_id: u32, handler: InterruptHandler) {
INTERRUPT_MANAGER.msi_register_handler(block, msi_id, handler);
}
/// Masks the specified interrupt vector.
#[unsafe(no_mangle)]
pub extern "C" fn mask_interrupt(vector: u32) -> zx_status_t {
Status::result_into_raw(INTERRUPT_MANAGER.mask_interrupt(vector))
}
/// Unmasks the specified interrupt vector.
#[unsafe(no_mangle)]
pub extern "C" fn unmask_interrupt(vector: u32) -> zx_status_t {
Status::result_into_raw(INTERRUPT_MANAGER.unmask_interrupt(vector))
}
/// Configures the trigger mode and polarity of the specified interrupt vector.
#[unsafe(no_mangle)]
pub extern "C" fn configure_interrupt(
vector: u32,
tm: InterruptTriggerMode,
pol: InterruptPolarity,
) -> zx_status_t {
Status::result_into_raw(INTERRUPT_MANAGER.configure_interrupt(vector, tm, pol))
}
/// Retrieves the configuration of the specified interrupt vector.
#[unsafe(no_mangle)]
pub extern "C" fn get_interrupt_config(
vector: u32,
tm: *mut InterruptTriggerMode,
pol: *mut InterruptPolarity,
) -> zx_status_t {
match INTERRUPT_MANAGER.get_interrupt_config(vector) {
Ok((t, p)) => {
if !tm.is_null() {
// SAFETY: `tm` is checked to be non-null and is a valid pointer provided by the caller
// to write the interrupt trigger mode.
unsafe {
*tm = t;
}
}
if !pol.is_null() {
// SAFETY: `pol` is checked to be non-null and is a valid pointer provided by the caller
// to write the interrupt polarity.
unsafe {
*pol = p;
}
}
zx_status::sys::ZX_OK
}
Err(e) => e.into_raw(),
}
}
/// On x64 these methods return the base and max Global System Interrupts as
/// defined by ACPI and described by MADT tables.
/// ACPI Spec 6.1, section 5.2.12 & section 5.2.13.
#[unsafe(no_mangle)]
pub extern "C" fn interrupt_get_base_vector() -> u32 {
GSI_RANGE.start
}
/// Returns the maximum global IRQ vector.
#[unsafe(no_mangle)]
pub extern "C" fn interrupt_get_max_vector() -> u32 {
GSI_RANGE.end
}
/// Returns true if the vector is a valid interrupt vector.
#[unsafe(no_mangle)]
pub extern "C" fn is_valid_interrupt(vector: u32, _flags: u32) -> bool {
apic_io_is_valid_irq(vector)
}
/// Remaps the ISA vector to a global system interrupt vector.
#[unsafe(no_mangle)]
pub extern "C" fn remap_interrupt(vector: u32) -> u32 {
if vector >= crate::arch_rs::x86::apic::NUM_ISA_IRQS as u32 {
return vector;
}
apic_io_isa_to_global(vector as u8)
}
/// Disables legacy interrupts globally.
#[unsafe(no_mangle)]
pub extern "C" fn shutdown_interrupts() {
pic_disable();
}
/// Suspends interrupts on the current CPU. Returns Status::NOT_SUPPORTED on PC.
#[unsafe(no_mangle)]
pub extern "C" fn suspend_interrupts_curr_cpu() -> zx_status_t {
Status::NOT_SUPPORTED.into_raw()
}
/// Resumes interrupts on the current CPU. Returns Status::NOT_SUPPORTED on PC.
#[unsafe(no_mangle)]
pub extern "C" fn resume_interrupts_curr_cpu() -> zx_status_t {
Status::NOT_SUPPORTED.into_raw()
}
/// Returns true if MSI is supported.
#[unsafe(no_mangle)]
pub extern "C" fn msi_is_supported() -> bool {
true
}
/// Returns true if MSI masking is supported (always false on PC).
#[unsafe(no_mangle)]
pub extern "C" fn msi_supports_masking() -> bool {
false
}
/// Masks or unmasks MSI (always panics on PC since masking is unsupported).
#[unsafe(no_mangle)]
pub extern "C" fn msi_mask_unmask(_block: *const MsiBlock, _msi_id: u32, _mask: bool) {
panic!("MSI masking not supported on x64");
}
/// Allocates an MSI block.
#[unsafe(no_mangle)]
pub extern "C" fn msi_alloc_block(
requested_irqs: u32,
can_target_64bit: bool,
is_msix: bool,
out_block: *mut MsiBlock,
) -> zx_status_t {
let out_block = match unsafe { out_block.as_mut() }.ok_or(Status::INVALID_ARGS) {
Ok(block) => block,
Err(e) => {
return e.into_raw();
}
};
if out_block.allocated {
return Status::INVALID_ARGS.into_raw();
}
match INTERRUPT_MANAGER.msi_alloc_block(requested_irqs, can_target_64bit, is_msix) {
Ok(block) => {
*out_block = block;
zx_status::sys::ZX_OK
}
Err(e) => e.into_raw(),
}
}
/// Frees an allocated MSI block.
#[unsafe(no_mangle)]
pub extern "C" fn msi_free_block(block: &mut MsiBlock) {
INTERRUPT_MANAGER.msi_free_block(block);
}
/// Shutdown interrupts for the calling CPU.
///
/// Should be called before powering off the calling CPU.
#[unsafe(no_mangle)]
pub extern "C" fn shutdown_interrupts_curr_cpu() {
if crate::arch_rs::x86::feature::x86_hypervisor_has_pv_eoi() {
let mut msr_access = crate::arch_rs::x86::platform_access::RealMsrAccess {};
crate::arch_rs::x86::pv::PvEoi::get().disable(&mut msr_access);
}
// TODO(maniscalco): Walk interrupt redirection entries and make sure nothing targets this CPU.
}
init::lk_init_hook!(apic, platform_init_apic, init::LkInitLevel(init::LK_INIT_LEVEL_VM.0 + 2));