1use alloc::{boxed::Box, collections::VecDeque};
13use core::sync::atomic::{AtomicBool, Ordering};
14
15use spin::Once;
16
17use crate::{
18 arch::{irq::HwCpuId, trap::TrapFrame},
19 cpu::{CpuSet, PinCurrentCpu},
20 cpu_local, irq,
21 sync::SpinLock,
22 util::id_set::Id,
23};
24
25pub fn inter_processor_call(targets: &CpuSet, call_fn: fn()) -> PendingIpis {
44 let ipi_sender = IPI_SENDER.get().unwrap();
45 ipi_sender.inter_processor_call(targets, call_fn)
46}
47
48pub struct PendingIpis {
50 cpus: CpuSet,
51}
52
53impl PendingIpis {
54 pub(crate) fn new_empty() -> Self {
55 Self {
56 cpus: CpuSet::new_empty(),
57 }
58 }
59
60 fn add(&mut self, cpu_id: crate::cpu::CpuId) {
61 self.cpus.add(cpu_id);
62 }
63
64 pub(crate) fn extend(&mut self, other: &Self) {
65 for cpu_id in other.cpus.iter() {
66 self.add(cpu_id);
67 }
68 }
69
70 pub fn wait(&self) {
78 assert!(
79 crate::arch::irq::is_local_enabled(),
80 "waiting for remote inter-processor calls with IRQs disabled"
81 );
82
83 for cpu_id in self.cpus.iter() {
84 while HAS_PENDING_IPIS.get_on_cpu(cpu_id).load(Ordering::Acquire) {
90 core::hint::spin_loop();
91 }
92 }
93 }
94}
95
96pub(crate) struct IpiSender {
101 hw_cpu_ids: Box<[HwCpuId]>,
102}
103
104pub(crate) static IPI_SENDER: Once<IpiSender> = Once::new();
106
107impl IpiSender {
108 pub(crate) fn inter_processor_call(&self, targets: &CpuSet, call_fn: fn()) -> PendingIpis {
114 let irq_guard = irq::disable_local();
115 let this_cpu_id = irq_guard.current_cpu();
116
117 let mut call_on_self = false;
118 let mut pending_ipis = PendingIpis::new_empty();
119 for cpu_id in targets.iter() {
120 if cpu_id == this_cpu_id {
121 call_on_self = true;
122 continue;
123 }
124 let mut call_queue = CALL_QUEUES.get_on_cpu(cpu_id).lock();
125 call_queue.push_back(call_fn);
126 HAS_PENDING_IPIS
128 .get_on_cpu(cpu_id)
129 .store(true, Ordering::Release);
130 pending_ipis.add(cpu_id);
131 }
132 for cpu_id in targets.iter() {
133 if cpu_id == this_cpu_id {
134 continue;
135 }
136 let hw_cpu_id = self.hw_cpu_ids[cpu_id.as_usize()];
137 crate::arch::irq::send_ipi(hw_cpu_id, &irq_guard as _)
138 .expect("failed to send inter-processor interrupt");
139 }
140 if call_on_self {
141 call_fn();
143 }
144 pending_ipis
145 }
146}
147
148cpu_local! {
149 static CALL_QUEUES: SpinLock<VecDeque<fn()>> = SpinLock::new(VecDeque::new());
150 static HAS_PENDING_IPIS: AtomicBool = AtomicBool::new(false);
151}
152
153pub(crate) unsafe fn do_inter_processor_call(_trapframe: &TrapFrame) {
160 let this_cpu_id = crate::cpu::CpuId::current_racy();
162
163 let mut queue = CALL_QUEUES.get_on_cpu(this_cpu_id).lock();
164 while let Some(call_fn) = queue.pop_front() {
165 crate::debug!(
166 "Performing inter-processor call to {:#?} on CPU {:#?}",
167 call_fn,
168 this_cpu_id,
169 );
170 call_fn();
171 }
172 HAS_PENDING_IPIS
174 .get_on_cpu(this_cpu_id)
175 .store(false, Ordering::Release);
176}
177
178pub(super) fn init() {
179 IPI_SENDER.call_once(|| {
180 let hw_cpu_ids = crate::boot::smp::construct_hw_cpu_id_mapping();
181 IpiSender { hw_cpu_ids }
182 });
183}
184
185#[cfg(ktest)]
186mod test {
187 use core::sync::atomic::{AtomicUsize, Ordering};
188
189 use crate::{
190 cpu::{self, PinCurrentCpu},
191 prelude::ktest,
192 task,
193 };
194
195 static IPI_CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
196
197 fn count_ipi_call() {
198 IPI_CALL_COUNT.fetch_add(1, Ordering::Relaxed);
199 }
200
201 #[ktest]
202 fn pending_ipis_waits_for_all_cpus() {
203 let before = IPI_CALL_COUNT.load(Ordering::Relaxed);
204
205 super::inter_processor_call(&cpu::CpuSet::new_full(), count_ipi_call).wait();
206
207 assert_eq!(
208 IPI_CALL_COUNT.load(Ordering::Relaxed) - before,
209 cpu::num_cpus()
210 );
211 }
212
213 #[ktest]
214 fn inter_processor_call_runs_on_current_cpu() {
215 let preempt_guard = task::disable_preempt();
216 let before = IPI_CALL_COUNT.load(Ordering::Relaxed);
217
218 super::inter_processor_call(
219 &cpu::CpuSet::from(preempt_guard.current_cpu()),
220 count_ipi_call,
221 )
222 .wait();
223
224 assert_eq!(IPI_CALL_COUNT.load(Ordering::Relaxed) - before, 1);
225 }
226
227 #[ktest]
228 fn pending_ipis_waits_for_remote_cpu() {
229 if cpu::num_cpus() < 2 {
230 return;
231 }
232
233 let preempt_guard = task::disable_preempt();
234 let target_cpu = cpu::all_cpus()
235 .find(|cpu_id| *cpu_id != preempt_guard.current_cpu())
236 .unwrap();
237 let before = IPI_CALL_COUNT.load(Ordering::Relaxed);
238
239 super::inter_processor_call(&cpu::CpuSet::from(target_cpu), count_ipi_call).wait();
240
241 assert_eq!(IPI_CALL_COUNT.load(Ordering::Relaxed) - before, 1);
242 }
243
244 #[ktest]
245 fn pending_ipis_can_be_extended() {
246 if cpu::num_cpus() < 2 {
247 return;
248 }
249
250 let preempt_guard = task::disable_preempt();
251 let target_cpu = cpu::all_cpus()
252 .find(|cpu_id| *cpu_id != preempt_guard.current_cpu())
253 .unwrap();
254 let target_cpus = cpu::CpuSet::from(target_cpu);
255 let before = IPI_CALL_COUNT.load(Ordering::Relaxed);
256
257 let mut pending_ipis = super::PendingIpis::new_empty();
258 pending_ipis.extend(&super::inter_processor_call(&target_cpus, count_ipi_call));
259 pending_ipis.extend(&super::inter_processor_call(&target_cpus, count_ipi_call));
260 pending_ipis.wait();
261
262 assert_eq!(IPI_CALL_COUNT.load(Ordering::Relaxed) - before, 2);
263 }
264}