From 425027677b08614d69d6d02f15f6e8b07c97889f Mon Sep 17 00:00:00 2001 From: Zhang Junyang Date: Thu, 15 Aug 2024 19:43:37 +0800 Subject: [PATCH] Add the inter-processor-call facilities --- ostd/src/arch/x86/irq.rs | 24 ++++++++ ostd/src/arch/x86/kernel/apic/mod.rs | 2 - ostd/src/lib.rs | 3 + ostd/src/smp.rs | 87 ++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 ostd/src/smp.rs diff --git a/ostd/src/arch/x86/irq.rs b/ostd/src/arch/x86/irq.rs index f4cb86548..4c7aede15 100644 --- a/ostd/src/arch/x86/irq.rs +++ b/ostd/src/arch/x86/irq.rs @@ -153,3 +153,27 @@ impl Drop for IrqCallbackHandle { CALLBACK_ID_ALLOCATOR.get().unwrap().lock().free(self.id); } } + +/// Sends a general inter-processor interrupt (IPI) to the specified CPU. +/// +/// # Safety +/// +/// The caller must ensure that the CPU ID and the interrupt number corresponds +/// to a safe function to call. +pub(crate) unsafe fn send_ipi(cpu_id: u32, irq_num: u8) { + use crate::arch::kernel::apic::{self, Icr}; + + let icr = Icr::new( + apic::ApicId::from(cpu_id), + apic::DestinationShorthand::NoShorthand, + apic::TriggerMode::Edge, + apic::Level::Assert, + apic::DeliveryStatus::Idle, + apic::DestinationMode::Physical, + apic::DeliveryMode::Fixed, + irq_num, + ); + apic::borrow(|apic| { + apic.send_ipi(icr); + }); +} diff --git a/ostd/src/arch/x86/kernel/apic/mod.rs b/ostd/src/arch/x86/kernel/apic/mod.rs index 855bc83c4..bf6a128d6 100644 --- a/ostd/src/arch/x86/kernel/apic/mod.rs +++ b/ostd/src/arch/x86/kernel/apic/mod.rs @@ -238,7 +238,6 @@ impl From for ApicId { /// in the system excluding the sender. #[repr(u64)] pub enum DestinationShorthand { - #[allow(dead_code)] NoShorthand = 0b00, #[allow(dead_code)] MySelf = 0b01, @@ -278,7 +277,6 @@ pub enum DestinationMode { #[repr(u64)] pub enum DeliveryMode { /// Delivers the interrupt specified in the vector field to the target processor or processors. - #[allow(dead_code)] Fixed = 0b000, /// Same as fixed mode, except that the interrupt is delivered to the processor executing at /// the lowest priority among the set of processors specified in the destination field. The diff --git a/ostd/src/lib.rs b/ostd/src/lib.rs index 79b6d971b..745668ab7 100644 --- a/ostd/src/lib.rs +++ b/ostd/src/lib.rs @@ -39,6 +39,7 @@ pub mod logger; pub mod mm; pub mod panicking; pub mod prelude; +pub mod smp; pub mod sync; pub mod task; pub mod trap; @@ -90,6 +91,8 @@ pub unsafe fn init() { unsafe { trap::softirq::init() }; arch::init_on_bsp(); + smp::init(); + bus::init(); // SAFETY: This function is called only once on the BSP. diff --git a/ostd/src/smp.rs b/ostd/src/smp.rs new file mode 100644 index 000000000..72cf55ff4 --- /dev/null +++ b/ostd/src/smp.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Symmetric Multi-Processing (SMP) support. +//! +//! This module provides a way to execute code on other processors via inter- +//! processor interrupts. + +use alloc::collections::VecDeque; + +use spin::Once; + +use crate::{ + cpu::{CpuSet, PinCurrentCpu}, + cpu_local, + sync::SpinLock, + trap::{self, IrqLine, TrapFrame}, +}; + +/// Execute a function on other processors. +/// +/// The provided function `f` will be executed on all target processors +/// specified by `targets`. It can also be executed on the current processor. +/// The function should be short and non-blocking, as it will be executed in +/// interrupt context with interrupts disabled. +/// +/// This function does not block until all the target processors acknowledges +/// the interrupt. So if any of the target processors disables IRQs for too +/// long that the controller cannot queue them, the function will not be +/// executed. +/// +/// The function `f` will be executed asynchronously on the target processors. +/// However if called on the current processor, it will be synchronous. +pub fn inter_processor_call(targets: &CpuSet, f: fn()) { + let irq_guard = trap::disable_local(); + let this_cpu_id = irq_guard.current_cpu() as usize; + let irq_num = INTER_PROCESSOR_CALL_IRQ.get().unwrap().num(); + + let mut call_on_self = false; + for cpu_id in targets.iter() { + if cpu_id == this_cpu_id { + call_on_self = true; + continue; + } + CALL_QUEUES.get_on_cpu(cpu_id as u32).lock().push_back(f); + } + for cpu_id in targets.iter() { + if cpu_id == this_cpu_id { + continue; + } + // SAFETY: It is safe to send inter processor call IPI to other CPUs. + unsafe { + crate::arch::irq::send_ipi(cpu_id as u32, irq_num); + } + } + if call_on_self { + // Execute the function synchronously. + f(); + } +} + +static INTER_PROCESSOR_CALL_IRQ: Once = Once::new(); + +cpu_local! { + static CALL_QUEUES: SpinLock> = SpinLock::new(VecDeque::new()); +} + +fn do_inter_processor_call(_trapframe: &TrapFrame) { + // TODO: in interrupt context, disabling interrupts is not necessary. + let preempt_guard = trap::disable_local(); + let cur_cpu = preempt_guard.current_cpu(); + + let mut queue = CALL_QUEUES.get_on_cpu(cur_cpu).lock(); + while let Some(f) = queue.pop_front() { + log::trace!( + "Performing inter-processor call to {:#?} on CPU {}", + f, + cur_cpu + ); + f(); + } +} + +pub(super) fn init() { + let mut irq = IrqLine::alloc().unwrap(); + irq.on_active(do_inter_processor_call); + INTER_PROCESSOR_CALL_IRQ.call_once(|| irq); +}