From 758c80c32170cd3cc6d62bd2d867a1c902843d0f Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Fri, 23 May 2025 23:18:54 +0800 Subject: [PATCH] Pull code from `arch/*/irq.rs` to `trap/irq.rs` --- ostd/src/arch/riscv/irq.rs | 151 ++++----------------------- ostd/src/arch/riscv/mod.rs | 1 - ostd/src/arch/x86/irq.rs | 185 +++++++------------------------- ostd/src/arch/x86/mod.rs | 1 - ostd/src/trap/handler.rs | 12 +-- ostd/src/trap/irq.rs | 209 +++++++++++++++++++++++++++++-------- 6 files changed, 229 insertions(+), 330 deletions(-) diff --git a/ostd/src/arch/riscv/irq.rs b/ostd/src/arch/riscv/irq.rs index 8c134c29..59149d4d 100644 --- a/ostd/src/arch/riscv/irq.rs +++ b/ostd/src/arch/riscv/irq.rs @@ -2,33 +2,33 @@ //! Interrupts. -use alloc::{boxed::Box, fmt::Debug, sync::Arc, vec::Vec}; +use crate::cpu::PinCurrentCpu; -use id_alloc::IdAlloc; -use spin::Once; +pub(crate) const IRQ_NUM_MIN: u8 = 0; +pub(crate) const IRQ_NUM_MAX: u8 = 255; -use crate::{ - cpu::PinCurrentCpu, - sync::{Mutex, PreemptDisabled, SpinLock, SpinLockGuard}, - trap::TrapFrame, -}; +pub(crate) struct IrqRemapping { + _private: (), +} -/// The global allocator for software defined IRQ lines. -pub(crate) static IRQ_ALLOCATOR: Once> = Once::new(); - -pub(crate) static IRQ_LIST: Once> = Once::new(); - -pub(crate) fn init() { - let mut list: Vec = Vec::new(); - for i in 0..256 { - list.push(IrqLine { - irq_num: i as u8, - callback_list: SpinLock::new(Vec::new()), - }); +impl IrqRemapping { + pub(crate) const fn new() -> Self { + Self { _private: () } + } + + /// Initializes the remapping entry for the specific IRQ number. + /// + /// This will do nothing if the entry is already initialized or interrupt + /// remapping is disabled or not supported by the architecture. + pub(crate) fn init(&self, irq_num: u8) {} + + /// Gets the remapping index of the IRQ line. + /// + /// This method will return `None` if interrupt remapping is disabled or + /// not supported by the architecture. + pub(crate) fn remapping_index(&self) -> Option { + None } - IRQ_LIST.call_once(|| list); - CALLBACK_ID_ALLOCATOR.call_once(|| Mutex::new(IdAlloc::with_capacity(256))); - IRQ_ALLOCATOR.call_once(|| SpinLock::new(IdAlloc::with_capacity(256))); } pub(crate) fn enable_local() { @@ -43,111 +43,6 @@ pub(crate) fn is_local_enabled() -> bool { riscv::register::sstatus::read().sie() } -static CALLBACK_ID_ALLOCATOR: Once> = Once::new(); - -pub struct CallbackElement { - function: Box, - id: usize, -} - -impl CallbackElement { - pub fn call(&self, element: &TrapFrame) { - self.function.call((element,)); - } -} - -impl Debug for CallbackElement { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("CallbackElement") - .field("id", &self.id) - .finish() - } -} - -/// An interrupt request (IRQ) line. -#[derive(Debug)] -pub(crate) struct IrqLine { - pub(crate) irq_num: u8, - pub(crate) callback_list: SpinLock>, -} - -impl IrqLine { - /// Acquire an interrupt request line. - /// - /// # Safety - /// - /// This function is marked unsafe as manipulating interrupt lines is - /// considered a dangerous operation. - #[expect(clippy::redundant_allocation)] - pub unsafe fn acquire(irq_num: u8) -> Arc<&'static Self> { - Arc::new(IRQ_LIST.get().unwrap().get(irq_num as usize).unwrap()) - } - - /// Gets the remapping index of the IRQ line. - /// - /// This method will return `None` if interrupt remapping is disabled or - /// not supported by the architecture. - pub fn remapping_index(&self) -> Option { - None - } - - /// Get the IRQ number. - pub fn num(&self) -> u8 { - self.irq_num - } - - pub fn callback_list( - &self, - ) -> SpinLockGuard, PreemptDisabled> { - self.callback_list.lock() - } - - /// Register a callback that will be invoked when the IRQ is active. - /// - /// A handle to the callback is returned. Dropping the handle - /// automatically unregisters the callback. - /// - /// For each IRQ line, multiple callbacks may be registered. - pub fn on_active(&self, callback: F) -> IrqCallbackHandle - where - F: Fn(&TrapFrame) + Sync + Send + 'static, - { - let allocate_id = CALLBACK_ID_ALLOCATOR.get().unwrap().lock().alloc().unwrap(); - self.callback_list.lock().push(CallbackElement { - function: Box::new(callback), - id: allocate_id, - }); - IrqCallbackHandle { - irq_num: self.irq_num, - id: allocate_id, - } - } -} - -/// The handle to a registered callback for a IRQ line. -/// -/// When the handle is dropped, the callback will be unregistered automatically. -#[must_use] -#[derive(Debug)] -pub struct IrqCallbackHandle { - irq_num: u8, - id: usize, -} - -impl Drop for IrqCallbackHandle { - fn drop(&mut self) { - let mut a = IRQ_LIST - .get() - .unwrap() - .get(self.irq_num as usize) - .unwrap() - .callback_list - .lock(); - a.retain(|item| item.id != self.id); - CALLBACK_ID_ALLOCATOR.get().unwrap().lock().free(self.id); - } -} - // ####### Inter-Processor Interrupts (IPIs) ####### /// Hardware-specific, architecture-dependent CPU ID. diff --git a/ostd/src/arch/riscv/mod.rs b/ostd/src/arch/riscv/mod.rs index 4ebecb86..7d39b4b5 100644 --- a/ostd/src/arch/riscv/mod.rs +++ b/ostd/src/arch/riscv/mod.rs @@ -25,7 +25,6 @@ pub(crate) fn init_cvm_guest() { pub(crate) unsafe fn late_init_on_bsp() { // SAFETY: This function is called in the boot context of the BSP. unsafe { trap::init() }; - irq::init(); // SAFETY: We're on the BSP and we're ready to boot all APs. unsafe { crate::boot::smp::boot_all_aps() }; diff --git a/ostd/src/arch/x86/irq.rs b/ostd/src/arch/x86/irq.rs index 672d405b..d94f28bb 100644 --- a/ostd/src/arch/x86/irq.rs +++ b/ostd/src/arch/x86/irq.rs @@ -2,48 +2,52 @@ //! Interrupts. -#![expect(dead_code)] - -use alloc::{boxed::Box, fmt::Debug, sync::Arc, vec::Vec}; - -use id_alloc::IdAlloc; use spin::Once; use x86_64::registers::rflags::{self, RFlags}; use super::iommu::{alloc_irt_entry, has_interrupt_remapping, IrtEntryHandle}; -use crate::{ - cpu::PinCurrentCpu, - sync::{Mutex, PreemptDisabled, RwLock, RwLockReadGuard, SpinLock}, - trap::TrapFrame, -}; +use crate::cpu::PinCurrentCpu; -/// The global allocator for software defined IRQ lines. -pub(crate) static IRQ_ALLOCATOR: Once> = Once::new(); +// Intel(R) 64 and IA-32 rchitectures Software Developer's Manual, +// Volume 3A, Section 6.2 says "Vector numbers in the range 32 to 255 +// are designated as user-defined interrupts and are not reserved by +// the Intel 64 and IA-32 architecture." +pub(crate) const IRQ_NUM_MIN: u8 = 32; +pub(crate) const IRQ_NUM_MAX: u8 = 255; -pub(crate) static IRQ_LIST: Once> = Once::new(); +pub(crate) struct IrqRemapping { + entry: Once, +} -pub(crate) fn init() { - let mut list: Vec = Vec::new(); - for i in 0..256 { - list.push(IrqLine { - irq_num: i as u8, - callback_list: RwLock::new(Vec::new()), - bind_remapping_entry: Once::new(), +impl IrqRemapping { + pub(crate) const fn new() -> Self { + Self { entry: Once::new() } + } + + /// Initializes the remapping entry for the specific IRQ number. + /// + /// This will do nothing if the entry is already initialized or interrupt + /// remapping is disabled or not supported by the architecture. + pub(crate) fn init(&self, irq_num: u8) { + if !has_interrupt_remapping() { + return; + } + + self.entry.call_once(|| { + // Allocate and enable the IRT entry. + let handle = alloc_irt_entry().unwrap(); + handle.enable(irq_num as u32); + handle }); } - IRQ_LIST.call_once(|| list); - CALLBACK_ID_ALLOCATOR.call_once(|| Mutex::new(IdAlloc::with_capacity(256))); - IRQ_ALLOCATOR.call_once(|| { - // As noted in the Intel 64 and IA-32 rchitectures Software Developer’s Manual, - // Volume 3A, Section 6.2, the first 32 interrupts are reserved for specific - // usages. And the rest from 32 to 255 are available for external user-defined - // interrupts. - let mut id_alloc = IdAlloc::with_capacity(256); - for i in 0..32 { - id_alloc.alloc_specific(i).unwrap(); - } - SpinLock::new(id_alloc) - }); + + /// Gets the remapping index of the IRQ line. + /// + /// This method will return `None` if interrupt remapping is disabled or + /// not supported by the architecture. + pub(crate) fn remapping_index(&self) -> Option { + Some(self.entry.get()?.index()) + } } pub(crate) fn enable_local() { @@ -62,121 +66,6 @@ pub(crate) fn is_local_enabled() -> bool { (rflags::read_raw() & RFlags::INTERRUPT_FLAG.bits()) != 0 } -static CALLBACK_ID_ALLOCATOR: Once> = Once::new(); - -pub struct CallbackElement { - function: Box, - id: usize, -} - -impl CallbackElement { - pub fn call(&self, element: &TrapFrame) { - (self.function)(element); - } -} - -impl Debug for CallbackElement { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("CallbackElement") - .field("id", &self.id) - .finish() - } -} - -/// An interrupt request (IRQ) line. -#[derive(Debug)] -pub(crate) struct IrqLine { - pub(crate) irq_num: u8, - pub(crate) callback_list: RwLock>, - bind_remapping_entry: Once, -} - -impl IrqLine { - /// Acquires an interrupt request line. - /// - /// # Safety - /// - /// This function is marked unsafe as manipulating interrupt lines is - /// considered a dangerous operation. - #[expect(clippy::redundant_allocation)] - pub unsafe fn acquire(irq_num: u8) -> Arc<&'static Self> { - let irq = Arc::new(IRQ_LIST.get().unwrap().get(irq_num as usize).unwrap()); - if has_interrupt_remapping() { - let handle = alloc_irt_entry(); - if let Some(handle) = handle { - // Enable the IRT entry - handle.enable(irq_num as u32); - irq.bind_remapping_entry.call_once(|| handle); - } - } - irq - } - - /// Gets the remapping index of the IRQ line. - /// - /// This method will return `None` if interrupt remapping is disabled or - /// not supported by the architecture. - pub fn remapping_index(&self) -> Option { - Some(self.bind_remapping_entry.get()?.index()) - } - - /// Gets the IRQ number. - pub fn num(&self) -> u8 { - self.irq_num - } - - pub fn callback_list( - &self, - ) -> RwLockReadGuard, PreemptDisabled> { - self.callback_list.read() - } - - /// Registers a callback that will be invoked when the IRQ is active. - /// - /// A handle to the callback is returned. Dropping the handle - /// automatically unregisters the callback. - /// - /// For each IRQ line, multiple callbacks may be registered. - pub fn on_active(&self, callback: F) -> IrqCallbackHandle - where - F: Fn(&TrapFrame) + Sync + Send + 'static, - { - let allocated_id = CALLBACK_ID_ALLOCATOR.get().unwrap().lock().alloc().unwrap(); - self.callback_list.write().push(CallbackElement { - function: Box::new(callback), - id: allocated_id, - }); - IrqCallbackHandle { - irq_num: self.irq_num, - id: allocated_id, - } - } -} - -/// The handle to a registered callback for a IRQ line. -/// -/// When the handle is dropped, the callback will be unregistered automatically. -#[must_use] -#[derive(Debug)] -pub struct IrqCallbackHandle { - irq_num: u8, - id: usize, -} - -impl Drop for IrqCallbackHandle { - fn drop(&mut self) { - let mut a = IRQ_LIST - .get() - .unwrap() - .get(self.irq_num as usize) - .unwrap() - .callback_list - .write(); - a.retain(|item| item.id != self.id); - CALLBACK_ID_ALLOCATOR.get().unwrap().lock().free(self.id); - } -} - // ####### Inter-Processor Interrupts (IPIs) ####### /// Hardware-specific, architecture-dependent CPU ID. diff --git a/ostd/src/arch/x86/mod.rs b/ostd/src/arch/x86/mod.rs index 7eeb7fd5..08bad0dc 100644 --- a/ostd/src/arch/x86/mod.rs +++ b/ostd/src/arch/x86/mod.rs @@ -64,7 +64,6 @@ static CPU_FEATURES: Once = Once::new(); pub(crate) unsafe fn late_init_on_bsp() { // SAFETY: This function is only called once on BSP. unsafe { trap::init() }; - irq::init(); kernel::acpi::init(); diff --git a/ostd/src/trap/handler.rs b/ostd/src/trap/handler.rs index b5b874a5..f9fdd98f 100644 --- a/ostd/src/trap/handler.rs +++ b/ostd/src/trap/handler.rs @@ -2,8 +2,8 @@ use spin::Once; -use super::{disable_local, DisabledLocalIrqGuard}; -use crate::{arch::irq::IRQ_LIST, cpu_local_cell, task::disable_preempt, trap::TrapFrame}; +use super::{disable_local, irq::process_top_half, DisabledLocalIrqGuard}; +use crate::{cpu_local_cell, task::disable_preempt, trap::TrapFrame}; static BOTTOM_HALF_HANDLER: Once DisabledLocalIrqGuard> = Once::new(); @@ -26,14 +26,6 @@ pub fn register_bottom_half_handler(func: fn(DisabledLocalIrqGuard) -> DisabledL BOTTOM_HALF_HANDLER.call_once(|| func); } -fn process_top_half(trap_frame: &TrapFrame, irq_number: usize) { - let irq_line = IRQ_LIST.get().unwrap().get(irq_number).unwrap(); - let callback_functions = irq_line.callback_list(); - for callback_function in callback_functions.iter() { - callback_function.call(trap_frame); - } -} - fn process_bottom_half() { let Some(handler) = BOTTOM_HALF_HANDLER.get() else { return; diff --git a/ostd/src/trap/irq.rs b/ostd/src/trap/irq.rs index ee2290bf..a60fd3bb 100644 --- a/ostd/src/trap/irq.rs +++ b/ostd/src/trap/irq.rs @@ -1,68 +1,72 @@ // SPDX-License-Identifier: MPL-2.0 -use core::fmt::Debug; +use core::{fmt::Debug, ops::Deref}; + +use id_alloc::IdAlloc; +use spin::Once; use crate::{ - arch::irq::{self, IrqCallbackHandle, IRQ_ALLOCATOR}, + arch::irq::{self, IrqRemapping, IRQ_NUM_MAX, IRQ_NUM_MIN}, prelude::*, - sync::GuardTransfer, + sync::{GuardTransfer, RwLock, SpinLock, WriteIrqDisabled}, task::atomic_mode::InAtomicMode, trap::TrapFrame, Error, }; -/// Type alias for the irq callback function. +/// A type alias for the IRQ callback function. pub type IrqCallbackFunction = dyn Fn(&TrapFrame) + Sync + Send + 'static; -/// An Interrupt ReQuest(IRQ) line. User can use [`alloc`] or [`alloc_specific`] to get specific IRQ line. +/// An Interrupt ReQuest (IRQ) line. /// -/// The IRQ number is guaranteed to be external IRQ number and user can register callback functions to this IRQ resource. -/// When this resource is dropped, all the callback in this will be unregistered automatically. +/// Users can use [`alloc`] or [`alloc_specific`] to allocate a (specific) IRQ line. +/// +/// The IRQ number is guaranteed to be an external IRQ number and users can use [`on_active`] to +/// safely register callback functions on this IRQ line. When the IRQ line is dropped, all the +/// registered callbacks will be unregistered automatically. /// /// [`alloc`]: Self::alloc /// [`alloc_specific`]: Self::alloc_specific +/// [`on_active`]: Self::on_active #[derive(Debug)] #[must_use] pub struct IrqLine { - irq_num: u8, - #[expect(clippy::redundant_allocation)] - inner_irq: Arc<&'static irq::IrqLine>, - callbacks: Vec, + inner: Arc, + callbacks: Vec, } impl IrqLine { - /// Allocates a specific IRQ line. - pub fn alloc_specific(irq: u8) -> Result { - IRQ_ALLOCATOR - .get() - .unwrap() + /// Allocates an available IRQ line. + pub fn alloc() -> Result { + get_or_init_allocator() .lock() - .alloc_specific(irq as usize) - .map(|irq_num| Self::new(irq_num as u8)) + .alloc() + .map(|id| Self::new(id as u8)) .ok_or(Error::NotEnoughResources) } - /// Allocates an available IRQ line. - pub fn alloc() -> Result { - let Some(irq_num) = IRQ_ALLOCATOR.get().unwrap().lock().alloc() else { - return Err(Error::NotEnoughResources); - }; - Ok(Self::new(irq_num as u8)) + /// Allocates a specific IRQ line. + pub fn alloc_specific(irq_num: u8) -> Result { + get_or_init_allocator() + .lock() + .alloc_specific((irq_num - IRQ_NUM_MIN) as usize) + .map(|id| Self::new(id as u8)) + .ok_or(Error::NotEnoughResources) } - fn new(irq_num: u8) -> Self { - // SAFETY: The IRQ number is allocated through `RecycleAllocator`, and it is guaranteed that the - // IRQ is not one of the important IRQ like cpu exception IRQ. + fn new(index: u8) -> Self { + let inner = InnerHandle { index }; + inner.remapping.init(index + IRQ_NUM_MIN); + Self { - irq_num, - inner_irq: unsafe { irq::IrqLine::acquire(irq_num) }, + inner: Arc::new(inner), callbacks: Vec::new(), } } /// Gets the IRQ number. pub fn num(&self) -> u8 { - self.irq_num + self.inner.index + IRQ_NUM_MIN } /// Registers a callback that will be invoked when the IRQ is active. @@ -72,7 +76,20 @@ impl IrqLine { where F: Fn(&TrapFrame) + Sync + Send + 'static, { - self.callbacks.push(self.inner_irq.on_active(callback)) + let callback_handle = { + let callback_box = Box::new(callback); + let callback_addr = core::ptr::from_ref(&*callback_box).addr(); + + let mut callbacks = self.inner.callbacks.write(); + callbacks.push(callback_box); + + CallbackHandle { + irq_index: self.inner.index, + callback_addr, + } + }; + + self.callbacks.push(callback_handle); } /// Checks if there are no registered callbacks. @@ -85,32 +102,94 @@ impl IrqLine { /// This method will return `None` if interrupt remapping is disabled or /// not supported by the architecture. pub fn remapping_index(&self) -> Option { - self.inner_irq.remapping_index() + self.inner.remapping.remapping_index() } } impl Clone for IrqLine { fn clone(&self) -> Self { Self { - irq_num: self.irq_num, - inner_irq: self.inner_irq.clone(), + inner: self.inner.clone(), callbacks: Vec::new(), } } } -impl Drop for IrqLine { - fn drop(&mut self) { - if Arc::strong_count(&self.inner_irq) == 1 { - IRQ_ALLOCATOR - .get() - .unwrap() - .lock() - .free(self.irq_num as usize); +struct Inner { + callbacks: RwLock>, WriteIrqDisabled>, + remapping: IrqRemapping, +} + +impl Inner { + const fn new() -> Self { + Self { + callbacks: RwLock::new(Vec::new()), + remapping: IrqRemapping::new(), } } } +const NUMBER_OF_IRQS: usize = (IRQ_NUM_MAX - IRQ_NUM_MIN) as usize + 1; + +static INNERS: [Inner; NUMBER_OF_IRQS] = [const { Inner::new() }; NUMBER_OF_IRQS]; +static ALLOCATOR: Once> = Once::new(); + +fn get_or_init_allocator() -> &'static SpinLock { + ALLOCATOR.call_once(|| SpinLock::new(IdAlloc::with_capacity(NUMBER_OF_IRQS))) +} + +/// A handle for an allocated IRQ line. +/// +/// When the handle is dropped, the IRQ line will be released automatically. +#[derive(Debug)] +struct InnerHandle { + index: u8, +} + +impl Deref for InnerHandle { + type Target = Inner; + + fn deref(&self) -> &Self::Target { + &INNERS[self.index as usize] + } +} + +impl Drop for InnerHandle { + fn drop(&mut self) { + ALLOCATOR.get().unwrap().lock().free(self.index as usize); + } +} + +/// A handle for a registered callback on an IRQ line. +/// +/// When the handle is dropped, the callback will be unregistered automatically. +#[must_use] +#[derive(Debug)] +struct CallbackHandle { + irq_index: u8, + callback_addr: usize, +} + +impl Drop for CallbackHandle { + fn drop(&mut self) { + let mut callbacks = INNERS[self.irq_index as usize].callbacks.write(); + + let pos = callbacks + .iter() + .position(|element| core::ptr::from_ref(&**element).addr() == self.callback_addr); + let _ = callbacks.swap_remove(pos.unwrap()); + } +} + +pub(super) fn process_top_half(trap_frame: &TrapFrame, irq_num: usize) { + let inner = &INNERS[irq_num - (IRQ_NUM_MIN as usize)]; + for callback in &*inner.callbacks.read() { + callback(trap_frame); + } +} + +// ####### IRQ Guards ####### + /// Disables all IRQs on the current CPU (i.e., locally). /// /// This function returns a guard object, which will automatically enable local IRQs again when @@ -175,3 +254,49 @@ impl Drop for DisabledLocalIrqGuard { } } } + +#[cfg(ktest)] +mod test { + use super::*; + + const IRQ_NUM: u8 = 64; + const IRQ_INDEX: usize = (IRQ_NUM - IRQ_NUM_MIN) as usize; + + #[ktest] + fn alloc_and_free_irq() { + let irq_line = IrqLine::alloc_specific(IRQ_NUM).unwrap(); + assert!(IrqLine::alloc_specific(IRQ_NUM).is_err()); + + let irq_line_cloned = irq_line.clone(); + assert!(IrqLine::alloc_specific(IRQ_NUM).is_err()); + + drop(irq_line); + assert!(IrqLine::alloc_specific(IRQ_NUM).is_err()); + + drop(irq_line_cloned); + assert!(IrqLine::alloc_specific(IRQ_NUM).is_ok()); + } + + #[ktest] + fn register_and_unregister_callback() { + let mut irq_line = IrqLine::alloc_specific(IRQ_NUM).unwrap(); + let mut irq_line_cloned = irq_line.clone(); + + assert_eq!(INNERS[IRQ_INDEX].callbacks.read().len(), 0); + + irq_line.on_active(|_| {}); + assert_eq!(INNERS[IRQ_INDEX].callbacks.read().len(), 1); + + irq_line_cloned.on_active(|_| {}); + assert_eq!(INNERS[IRQ_INDEX].callbacks.read().len(), 2); + + irq_line_cloned.on_active(|_| {}); + assert_eq!(INNERS[IRQ_INDEX].callbacks.read().len(), 3); + + drop(irq_line); + assert_eq!(INNERS[IRQ_INDEX].callbacks.read().len(), 2); + + drop(irq_line_cloned); + assert_eq!(INNERS[IRQ_INDEX].callbacks.read().len(), 0); + } +}