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 irq_guard = irq::disable_local();
36    let this_cpu_id = irq_guard.current_cpu();
37
38    let ipi_data = IPI_GLOBAL_DATA.get().unwrap();
39
40    let mut call_on_self = false;
41    for cpu_id in targets.iter() {
42        if cpu_id == this_cpu_id {
43            call_on_self = true;
44            continue;
45        }
46        CALL_QUEUES.get_on_cpu(cpu_id).lock().push_back(f);
47    }
48    for cpu_id in targets.iter() {
49        if cpu_id == this_cpu_id {
50            continue;
51        }
52        let hw_cpu_id = ipi_data.hw_cpu_ids[cpu_id.as_usize()];
53        crate::arch::irq::send_ipi(hw_cpu_id, &irq_guard as _);
54    }
55    if call_on_self {
56        // Execute the function synchronously.
57        f();
58    }
59}
60
61struct IpiGlobalData {
62    hw_cpu_ids: Box<[HwCpuId]>,
63}
64
65static IPI_GLOBAL_DATA: Once<IpiGlobalData> = Once::new();
66
67cpu_local! {
68    static CALL_QUEUES: SpinLock<VecDeque<fn()>> = SpinLock::new(VecDeque::new());
69}
70
71/// Handles inter-processor calls.
72///
73/// # Safety
74///
75/// This function must be called from an IRQ handler that can be triggered by
76/// inter-processor interrupts.
77pub(crate) unsafe fn do_inter_processor_call(_trapframe: &TrapFrame) {
78    // No races because we are in IRQs.
79    let this_cpu_id = crate::cpu::CpuId::current_racy();
80
81    let mut queue = CALL_QUEUES.get_on_cpu(this_cpu_id).lock();
82    while let Some(f) = queue.pop_front() {
83        log::trace!(
84            "Performing inter-processor call to {:#?} on CPU {:#?}",
85            f,
86            this_cpu_id,
87        );
88        f();
89    }
90}
91
92pub(super) fn init() {
93    IPI_GLOBAL_DATA.call_once(|| {
94        let hw_cpu_ids = crate::boot::smp::construct_hw_cpu_id_mapping();
95
96        IpiGlobalData { hw_cpu_ids }
97    });
98}