blob: 48af5175b2a2032864fa2c65bd1f78ee236cceb8 [file]
// 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
//
// Ported from zircon/kernel/dev/pdev/interrupt/interrupt.cc
#[cfg(console_enabled)]
pub mod console;
#[cfg(not(console_enabled))]
use debug as _;
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use pin_init as _;
#[cfg(ktest)]
use unittest as _;
use zx_status::Status;
// NOTE: Keep constants, structures, and layout definitions in sync with the C++ header:
// zircon/kernel/dev/pdev/interrupt/include/pdev/interrupt.h
pub use dev_interrupt::{
InterruptHandler, InterruptPolarity, InterruptTriggerMode, InterruptVector, MAX_INTERRUPTS,
MsiBlock,
};
pub struct IntHandlerStruct {
handler: InterruptHandler,
permanent: AtomicBool,
}
unsafe impl Sync for IntHandlerStruct {}
#[ksync::guarded]
pub struct PdevInterruptManager {
#[guarded_by(lock)]
table: [IntHandlerStruct; MAX_INTERRUPTS],
#[mutex]
lock: ksync::KMutex<ksync::RawSpinlock>,
}
impl PdevInterruptManager {
/// Returns a reference to an `IntHandlerStruct` slot without acquiring the lock.
///
/// # Safety
///
/// `vector` must be `< MAX_INTERRUPTS`. The caller must only access lock-free atomic fields
/// (such as `permanent` or immutable `handler` once `permanent` is set to `true`).
#[inline]
pub unsafe fn slot_unchecked(&self, vector: usize) -> &IntHandlerStruct {
let table_ptr = core::ptr::addr_of!(self.table) as *const IntHandlerStruct;
// SAFETY: `self.table` is `repr(transparent)` around `UnsafeCell<[IntHandlerStruct; MAX_INTERRUPTS]>`.
// `vector` is guaranteed to be < MAX_INTERRUPTS.
unsafe { &*table_ptr.add(vector) }
}
}
struct PdevInterruptHolder(core::cell::UnsafeCell<core::mem::MaybeUninit<PdevInterruptManager>>);
// SAFETY: Synchronization is managed by `PdevInterruptManager`'s internal spinlock (`lock`).
unsafe impl Sync for PdevInterruptHolder {}
static PDEV_INTERRUPTS: PdevInterruptHolder =
PdevInterruptHolder(core::cell::UnsafeCell::new(core::mem::MaybeUninit::uninit()));
impl PdevInterruptHolder {
/// Initializes `PDEV_INTERRUPTS` in place during early single-threaded boot.
///
/// # Safety
/// Must only be called once during early single-threaded boot before multiple CPUs or threads are active.
#[inline]
unsafe fn init_in_place(&self) {
use pin_init::InPlaceWrite as _;
// SAFETY: Called once during early boot; `self.0.get()` points to valid static uninitialized memory.
unsafe {
let uninit_mut: &'static mut core::mem::MaybeUninit<PdevInterruptManager> =
&mut *self.0.get();
let initializer = pin_init::pin_init!(PdevInterruptManager {
table: ksync::KCell::new([
const {IntHandlerStruct {
handler: InterruptHandler::DEFAULT,
permanent: AtomicBool::new(false),
}}; MAX_INTERRUPTS]),
lock <- ksync::KSpinlock::init(),
});
let _ = uninit_mut.write_pin_init(initializer);
}
}
}
impl core::ops::Deref for PdevInterruptHolder {
type Target = PdevInterruptManager;
#[inline]
fn deref(&self) -> &Self::Target {
// SAFETY: `PDEV_INTERRUPTS` is initialized in `pdev_register_interrupts` prior to any reference
// or usage, and lives in static storage forever.
unsafe { &*self.0.get().cast::<PdevInterruptManager>() }
}
}
#[repr(C)]
pub struct PdevInterruptOps {
pub mask: extern "C" fn(vector: InterruptVector) -> Status,
pub unmask: extern "C" fn(vector: InterruptVector) -> Status,
pub deactivate: extern "C" fn(vector: InterruptVector) -> Status,
pub configure: extern "C" fn(
vector: InterruptVector,
tm: InterruptTriggerMode,
pol: InterruptPolarity,
) -> Status,
pub get_config: extern "C" fn(
vector: InterruptVector,
tm: *mut InterruptTriggerMode,
pol: *mut InterruptPolarity,
) -> Status,
pub set_affinity: extern "C" fn(vector: InterruptVector, mask: u32) -> Status,
pub is_valid: extern "C" fn(vector: InterruptVector, flags: u32) -> bool,
pub get_base_vector: extern "C" fn() -> InterruptVector,
pub get_max_vector: extern "C" fn() -> InterruptVector,
pub remap: extern "C" fn(vector: InterruptVector) -> InterruptVector,
pub send_ipi: extern "C" fn(target: u32, ipi: u32) -> Status,
pub init_percpu_early: extern "C" fn(),
pub init_percpu: extern "C" fn(),
pub handle_irq: extern "C" fn(frame: *mut core::ffi::c_void),
pub shutdown: extern "C" fn(),
pub shutdown_cpu: extern "C" fn(),
pub suspend_cpu: extern "C" fn() -> Status,
pub resume_cpu: extern "C" fn() -> Status,
pub msi_is_supported: extern "C" fn() -> bool,
pub msi_supports_masking: extern "C" fn() -> bool,
pub msi_mask_unmask: extern "C" fn(block: *const MsiBlock, msi_id: u32, mask: bool),
pub msi_alloc_block: extern "C" fn(
requested_irqs: u32,
can_target_64bit: bool,
is_msix: bool,
out_block: *mut MsiBlock,
) -> Status,
pub msi_free_block: extern "C" fn(block: *mut MsiBlock),
pub msi_register_handler:
extern "C" fn(block: *mut MsiBlock, msi_id: u32, handler: InterruptHandler),
pub get_status: Option<
extern "C" fn(
vector: InterruptVector,
out_pending: *mut bool,
out_enabled: *mut bool,
) -> Status,
>,
}
static INTR_OPS: AtomicPtr<PdevInterruptOps> = AtomicPtr::new(core::ptr::null_mut());
extern "C" fn default_mask(_: InterruptVector) -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_unmask(_: InterruptVector) -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_deactivate(_: InterruptVector) -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_configure(
_: InterruptVector,
_: InterruptTriggerMode,
_: InterruptPolarity,
) -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_get_config(
_: InterruptVector,
_: *mut InterruptTriggerMode,
_: *mut InterruptPolarity,
) -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_set_affinity(_: InterruptVector, _: u32) -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_is_valid(_: InterruptVector, _: u32) -> bool {
false
}
extern "C" fn default_get_base_vector() -> InterruptVector {
InterruptVector(0)
}
extern "C" fn default_get_max_vector() -> InterruptVector {
InterruptVector(0)
}
extern "C" fn default_remap(_: InterruptVector) -> InterruptVector {
InterruptVector(0)
}
extern "C" fn default_send_ipi(_: u32, _: u32) -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_init_percpu_early() {}
extern "C" fn default_init_percpu() {}
extern "C" fn default_handle_irq(_: *mut core::ffi::c_void) {}
extern "C" fn default_shutdown() {}
extern "C" fn default_shutdown_cpu() {}
extern "C" fn default_suspend_cpu() -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_resume_cpu() -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_msi_is_supported() -> bool {
false
}
extern "C" fn default_msi_supports_masking() -> bool {
false
}
extern "C" fn default_msi_mask_unmask(_: *const MsiBlock, _: u32, _: bool) {}
extern "C" fn default_msi_alloc_block(_: u32, _: bool, _: bool, _: *mut MsiBlock) -> Status {
Status::NOT_SUPPORTED
}
extern "C" fn default_msi_free_block(_: *mut MsiBlock) {}
extern "C" fn default_msi_register_handler(_: *mut MsiBlock, _: u32, _: InterruptHandler) {}
// By default most of these are empty stubs and the particular interrupt controller must override
// all of them.
static DEFAULT_OPS: PdevInterruptOps = PdevInterruptOps {
mask: default_mask,
unmask: default_unmask,
deactivate: default_deactivate,
configure: default_configure,
get_config: default_get_config,
set_affinity: default_set_affinity,
is_valid: default_is_valid,
get_base_vector: default_get_base_vector,
get_max_vector: default_get_max_vector,
remap: default_remap,
send_ipi: default_send_ipi,
init_percpu_early: default_init_percpu_early,
init_percpu: default_init_percpu,
handle_irq: default_handle_irq,
shutdown: default_shutdown,
shutdown_cpu: default_shutdown_cpu,
suspend_cpu: default_suspend_cpu,
resume_cpu: default_resume_cpu,
msi_is_supported: default_msi_is_supported,
msi_supports_masking: default_msi_supports_masking,
msi_mask_unmask: default_msi_mask_unmask,
msi_alloc_block: default_msi_alloc_block,
msi_free_block: default_msi_free_block,
msi_register_handler: default_msi_register_handler,
get_status: None,
};
/// Registers the platform interrupt operations.
///
/// # Safety
///
/// `ops` must point to a valid, static `PdevInterruptOps` structure that remains
/// valid for the lifetime of the kernel.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pdev_register_interrupts(ops: *const PdevInterruptOps) {
// SAFETY: Called once during early boot when platform interrupt operations are registered.
unsafe { PDEV_INTERRUPTS.init_in_place() };
INTR_OPS.store(ops as *mut _, Ordering::Release);
}
fn get_ops() -> *const PdevInterruptOps {
let ops = INTR_OPS.load(Ordering::Acquire);
if ops.is_null() { &DEFAULT_OPS as *const PdevInterruptOps } else { ops }
}
/// Invokes the interrupt handler for the vector if it is present and permanent.
///
/// # Safety
///
/// `vector` must be a valid interrupt vector index.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pdev_invoke_int_if_present(vector: InterruptVector) -> bool {
// SAFETY: vector is guaranteed by caller to be a valid interrupt vector index (< MAX_INTERRUPTS).
let slot = unsafe { PDEV_INTERRUPTS.slot_unchecked(vector.0 as usize) };
// Use a relaxed load as permanent handlers are never modified once set, and they are only set in
// startup code, so there is nothing to race with.
if slot.permanent.load(Ordering::Relaxed) {
// Once permanent is set to true we know that handler is immutable and so it is safe
// to read without holding the lock.
slot.handler.invoke();
return true;
}
ksync::lock!(let guard = PDEV_INTERRUPTS.lock_lock());
let slot = &guard.fields().table[vector.0 as usize];
if slot.handler.present() {
slot.handler.invoke();
true
} else {
false
}
}
/// Registers an interrupt handler for the specified vector.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
unsafe fn register_int_handler_common(
vector: InterruptVector,
handler: InterruptHandler,
permanent: bool,
) -> Result<(), Status> {
let ops = get_ops();
// SAFETY: ops is a valid pointer to PdevInterruptOps, registered during startup.
if !unsafe { ((*ops).is_valid)(vector, 0) } {
return Err(Status::INVALID_ARGS);
}
ksync::lock!(let mut guard = PDEV_INTERRUPTS.lock_lock());
let slot = &mut guard.as_mut().fields_mut().table[vector.0 as usize];
if (handler.present() && slot.handler.present()) || slot.permanent.load(Ordering::Relaxed) {
return Err(Status::ALREADY_BOUND);
}
slot.handler = handler;
slot.permanent.store(permanent, Ordering::Relaxed);
Ok(())
}
/// Registers an interrupt handler for the specified vector.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
#[unsafe(no_mangle)]
unsafe extern "C" fn register_int_handler(
vector: InterruptVector,
handler: InterruptHandler,
) -> Status {
unsafe { register_int_handler_common(vector, handler, false) }.into()
}
/// Registers a permanent interrupt handler for the specified vector.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
#[unsafe(no_mangle)]
unsafe extern "C" fn register_permanent_int_handler(
vector: InterruptVector,
handler: InterruptHandler,
) -> Status {
unsafe { register_int_handler_common(vector, handler, true) }.into()
}
/// Checks if an interrupt is registered
///
/// # Safety
///
/// The vector must be within valid range, and HANDLER_TABLE must be initialized.
pub fn is_interrupt_registered(vector: u32) -> bool {
if vector as usize >= MAX_INTERRUPTS {
return false;
}
ksync::lock!(let guard = PDEV_INTERRUPTS.lock_lock());
let slot = &guard.fields().table[vector as usize];
slot.handler.present()
}
/// Queries the status of an interrupt vector.
pub fn query_interrupt_status(vector: u32) -> (Option<bool>, Option<bool>) {
let ops = get_ops();
let mut pending = false;
let mut enabled = false;
unsafe {
if let Some(get_status) = (*ops).get_status {
if get_status(InterruptVector(vector), &mut pending, &mut enabled) == Status::OK {
return (Some(pending), Some(enabled));
}
}
}
(None, None)
}
/// Queries the configuration of an interrupt vector.
pub fn query_interrupt_config(
vector: u32,
) -> (Option<InterruptTriggerMode>, Option<InterruptPolarity>) {
let ops = get_ops();
let mut tm = InterruptTriggerMode::Edge;
let mut pol = InterruptPolarity::High;
unsafe {
if ((*ops).get_config)(InterruptVector(vector), &mut tm, &mut pol) == Status::OK {
return (Some(tm), Some(pol));
}
}
(None, None)
}
/// Masks the specified interrupt vector.
///
/// # Safety
///
/// The global interrupt ops must be registered, and vector must be valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mask_interrupt(vector: InterruptVector) -> Status {
unsafe { ((*get_ops()).mask)(vector) }
}
/// Unmasks the specified interrupt vector.
///
/// # Safety
///
/// The global interrupt ops must be registered, and vector must be valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn unmask_interrupt(vector: InterruptVector) -> Status {
unsafe { ((*get_ops()).unmask)(vector) }
}
/// Deactivates the specified interrupt vector.
///
/// # Safety
///
/// The global interrupt ops must be registered, and vector must be valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn deactivate_interrupt(vector: InterruptVector) -> Status {
unsafe { ((*get_ops()).deactivate)(vector) }
}
/// Configures the specified interrupt vector trigger mode and polarity.
///
/// # Safety
///
/// The global interrupt ops must be registered, and vector must be valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn configure_interrupt(
vector: InterruptVector,
tm: InterruptTriggerMode,
pol: InterruptPolarity,
) -> Status {
unsafe { ((*get_ops()).configure)(vector, tm, pol) }
}
/// Gets the specified interrupt vector configuration.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
/// - `tm` and `pol` must be valid, non-null pointers to enum memory.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn get_interrupt_config(
vector: InterruptVector,
tm: *mut InterruptTriggerMode,
pol: *mut InterruptPolarity,
) -> Status {
unsafe { ((*get_ops()).get_config)(vector, tm, pol) }
}
/// Sets the interrupt affinity mask for the specified vector.
///
/// # Safety
///
/// The global interrupt ops must be registered, and vector must be valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn set_interrupt_affinity(vector: InterruptVector, mask: u32) -> Status {
unsafe { ((*get_ops()).set_affinity)(vector, mask) }
}
/// Gets the base interrupt vector.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn interrupt_get_base_vector() -> InterruptVector {
unsafe { ((*get_ops()).get_base_vector)() }
}
/// Gets the maximum interrupt vector.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn interrupt_get_max_vector() -> InterruptVector {
unsafe { ((*get_ops()).get_max_vector)() }
}
/// Checks if the given interrupt vector is valid.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn is_valid_interrupt(vector: InterruptVector, flags: u32) -> bool {
unsafe { ((*get_ops()).is_valid)(vector, flags) }
}
/// Remaps the specified interrupt vector.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn remap_interrupt(vector: InterruptVector) -> InterruptVector {
unsafe { ((*get_ops()).remap)(vector) }
}
/// Sends an IPI to the target CPU.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn interrupt_send_ipi(target: u32, ipi: u32) -> Status {
unsafe { ((*get_ops()).send_ipi)(target, ipi) }
}
/// Initializes interrupts for the current CPU early in boot.
///
/// # Safety
///
/// The global interrupt ops must be registered.
fn interrupt_init_percpu_early(_level: init::LkInitLevel) {
unsafe { ((*get_ops()).init_percpu_early)() }
}
/// Initializes interrupts for the current CPU.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn interrupt_init_percpu() {
unsafe { ((*get_ops()).init_percpu)() }
}
/// Handles a platform IRQ.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
/// - `frame` must point to a valid interrupt frame.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn platform_irq(frame: *mut core::ffi::c_void) {
unsafe { ((*get_ops()).handle_irq)(frame) }
}
/// Shuts down all interrupts.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn shutdown_interrupts() {
unsafe { ((*get_ops()).shutdown)() }
}
/// Shuts down interrupts for the current CPU.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn shutdown_interrupts_curr_cpu() {
unsafe { ((*get_ops()).shutdown_cpu)() }
}
/// Suspends interrupts for the current CPU.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn suspend_interrupts_curr_cpu() -> Status {
unsafe { ((*get_ops()).suspend_cpu)() }
}
/// Resumes interrupts for the current CPU.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn resume_interrupts_curr_cpu() -> Status {
unsafe { ((*get_ops()).resume_cpu)() }
}
/// Checks if MSI is supported.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn msi_is_supported() -> bool {
unsafe { ((*get_ops()).msi_is_supported)() }
}
/// Checks if MSI supports masking.
///
/// # Safety
///
/// The global interrupt ops must be registered.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn msi_supports_masking() -> bool {
unsafe { ((*get_ops()).msi_supports_masking)() }
}
/// Masks or unmasks the specified MSI interrupt.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
/// - `block` must point to a valid, initialized `MsiBlock`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn msi_mask_unmask(block: *const MsiBlock, msi_id: u32, mask: bool) {
unsafe { ((*get_ops()).msi_mask_unmask)(block, msi_id, mask) }
}
/// Allocates a block of MSIs.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
/// - `out_block` must point to a valid, writable `MsiBlock` memory slot.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn msi_alloc_block(
requested_irqs: u32,
can_target_64bit: bool,
is_msix: bool,
out_block: *mut MsiBlock,
) -> Status {
unsafe { ((*get_ops()).msi_alloc_block)(requested_irqs, can_target_64bit, is_msix, out_block) }
}
/// Frees the specified block of MSIs.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
/// - `block` must point to a valid, previously allocated `MsiBlock` that needs to be freed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn msi_free_block(block: *mut MsiBlock) {
unsafe { ((*get_ops()).msi_free_block)(block) }
}
/// Registers an interrupt handler for the specified MSI.
///
/// # Safety
///
/// - The global interrupt ops must be registered.
/// - `block` must point to a valid, initialized `MsiBlock`.
/// - `handler` must point to a valid interrupt handler function or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn msi_register_handler(
block: *mut MsiBlock,
msi_id: u32,
handler: InterruptHandler,
) {
unsafe { ((*get_ops()).msi_register_handler)(block, msi_id, handler) }
}
unsafe impl Sync for PdevInterruptOps {}
/// PDEV interrupt layer kernel tests.
#[cfg(ktest)]
#[unittest::suite(name = "interrupts")]
mod tests {
#[allow(unused_imports)]
use super::*;
use unittest::{assert_eq, assert_false};
/// Test default ops dispatch table fallback behavior.
#[test]
fn test_pdev_default_ops_fallback() {
assert_eq!(
(DEFAULT_OPS.mask)(InterruptVector(0)).into_raw(),
Status::NOT_SUPPORTED.into_raw()
);
assert_eq!(
(DEFAULT_OPS.unmask)(InterruptVector(0)).into_raw(),
Status::NOT_SUPPORTED.into_raw()
);
}
/// Test unregistered interrupt vector state query.
#[test]
fn test_pdev_unregistered_interrupt_state() {
assert_false!(is_interrupt_registered(999));
}
}
init::lk_init_hook_flags!(
interrupt_init_percpu_early,
interrupt_init_percpu_early,
init::LK_INIT_LEVEL_PLATFORM_EARLY,
init::LkInitFlags::SecondaryCpus
);