ostd/smp.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! Symmetric Multi-Processing (SMP) support.
4//!
5//! This module provides a way to execute code on other processors via inter-
6//! processor interrupts.
7
8use alloc::{boxed::Box, collections::VecDeque};
9
10use spin::Once;
11
12use crate::{
13 arch::{irq::HwCpuId, trap::TrapFrame},
14 cpu::{CpuSet, PinCurrentCpu},
15 cpu_local, irq,
16 sync::SpinLock,
17 util::id_set::Id,
18};
19
20/// Executes a function on other processors.
21///
22/// The provided function `f` will be executed on all target processors
23/// specified by `targets`. It can also be executed on the current processor.
24/// The function should be short and non-blocking, as it will be executed in
25/// interrupt context with interrupts disabled.
26///
27/// This function does not block until all the target processors acknowledges
28/// the interrupt. So if any of the target processors disables IRQs for too
29/// long that the controller cannot queue them, the function will not be
30/// executed.
31///
32/// The function `f` will be executed asynchronously on the target processors.
33/// However if called on the current processor, it will be synchronous.
34pub fn inter_processor_call(targets: &CpuSet, f: fn()) {
35 let ipi_sender = IPI_SENDER.get().unwrap();
36 ipi_sender.inter_processor_call(targets, f);
37}
38
39/// A sender that carries necessary information to send inter-processor interrupts.
40///
41/// The purpose of exporting this type is to enable the users to check whether
42/// [`IPI_SENDER`] has been initialized.
43pub(crate) struct IpiSender {
44 hw_cpu_ids: Box<[HwCpuId]>,
45}
46
47/// The [`IpiSender`] singleton.
48pub(crate) static IPI_SENDER: Once<IpiSender> = Once::new();
49
50impl IpiSender {
51 /// Executes a function on other processors.
52 ///
53 /// See [`inter_processor_call`] for details. The purpose of exporting this
54 /// method is to enable callers to check whether [`IPI_SENDER`] has been
55 /// initialized.
56 pub(crate) fn inter_processor_call(&self, targets: &CpuSet, f: fn()) {
57 let irq_guard = irq::disable_local();
58 let this_cpu_id = irq_guard.current_cpu();
59
60 let mut call_on_self = false;
61 for cpu_id in targets.iter() {
62 if cpu_id == this_cpu_id {
63 call_on_self = true;
64 continue;
65 }
66 CALL_QUEUES.get_on_cpu(cpu_id).lock().push_back(f);
67 }
68 for cpu_id in targets.iter() {
69 if cpu_id == this_cpu_id {
70 continue;
71 }
72 let hw_cpu_id = self.hw_cpu_ids[cpu_id.as_usize()];
73 crate::arch::irq::send_ipi(hw_cpu_id, &irq_guard as _);
74 }
75 if call_on_self {
76 // Execute the function synchronously.
77 f();
78 }
79 }
80}
81
82cpu_local! {
83 static CALL_QUEUES: SpinLock<VecDeque<fn()>> = SpinLock::new(VecDeque::new());
84}
85
86/// Handles inter-processor calls.
87///
88/// # Safety
89///
90/// This function must be called from an IRQ handler that can be triggered by
91/// inter-processor interrupts.
92pub(crate) unsafe fn do_inter_processor_call(_trapframe: &TrapFrame) {
93 // No races because we are in IRQs.
94 let this_cpu_id = crate::cpu::CpuId::current_racy();
95
96 let mut queue = CALL_QUEUES.get_on_cpu(this_cpu_id).lock();
97 while let Some(f) = queue.pop_front() {
98 log::trace!(
99 "Performing inter-processor call to {:#?} on CPU {:#?}",
100 f,
101 this_cpu_id,
102 );
103 f();
104 }
105}
106
107pub(super) fn init() {
108 IPI_SENDER.call_once(|| {
109 let hw_cpu_ids = crate::boot::smp::construct_hw_cpu_id_mapping();
110 IpiSender { hw_cpu_ids }
111 });
112}