1use core::sync::atomic::{AtomicU32, Ordering};
4use sbi::{HartMask, SbiRet};
5use sbi_spec::hsm::hart_state;
6
7#[derive(Clone, Debug)]
9pub enum Case<'a> {
10 NotExist,
12 Begin,
14 HartStartedBeforeTest(usize),
18 NoStoppedHart,
20 BatchBegin(&'a [usize]),
22 HartStarted(usize),
24 HartStartFailed {
26 hartid: usize,
28 ret: SbiRet,
30 },
31 HartSuspendedNonretentive(usize),
33 HartResumed(usize),
35 HartSuspendedRetentive(usize),
37 HartStopped(usize),
39 RemoteRFencePass(usize),
41 RemoteRFenceFailed(usize, SbiRet),
43 BatchPass(&'a [usize]),
45 Pass,
47}
48
49pub fn test(
53 primary_hart_id: usize,
54 mut hart_mask: usize,
55 hart_mask_base: usize,
56 mut f: impl FnMut(Case),
57) {
58 if sbi::probe_extension(sbi::Hsm).is_unavailable() {
60 f(Case::NotExist);
61 return;
62 }
63 f(Case::Begin);
64 let rfnc_available = sbi::probe_extension(sbi::Fence).is_available();
65
66 let mut batch = [0usize; TEST_BATCH_SIZE];
67 let mut batch_count = 0;
68 let mut batch_size = 0;
69 let mut hartid = hart_mask_base;
70 while hart_mask != 0 {
71 if hartid != primary_hart_id {
72 if sbi::hart_get_status(hartid) == STOPPED {
74 batch[batch_size] = hartid;
75 batch_size += 1;
76 if batch_size == TEST_BATCH_SIZE {
78 if test_batch(&batch, rfnc_available, &mut f) {
79 batch_count += 1;
80 batch_size = 0;
81 } else {
82 return;
83 }
84 }
85 }
86 else {
88 f(Case::HartStartedBeforeTest(hartid));
89 }
90 }
91 let distance = hart_mask.trailing_zeros() + 1;
92 hart_mask >>= distance;
93 hartid += distance as usize;
94 }
95 if batch_size > 0 {
97 if test_batch(&batch[..batch_size], rfnc_available, &mut f) {
98 f(Case::Pass);
99 }
100 }
101 else if batch_count > 0 {
103 f(Case::Pass);
104 }
105 else {
107 f(Case::NoStoppedHart)
108 }
109}
110
111const STARTED: SbiRet = SbiRet::success(hart_state::STARTED);
112const STOPPED: SbiRet = SbiRet::success(hart_state::STOPPED);
113const SUSPENDED: SbiRet = SbiRet::success(hart_state::SUSPENDED);
114
115const TEST_BATCH_SIZE: usize = 4;
116static mut STACK: [ItemPerHart; TEST_BATCH_SIZE] = [ItemPerHart::ZERO; TEST_BATCH_SIZE];
117
118#[repr(C, align(512))]
119struct ItemPerHart {
120 stage: AtomicU32,
121 signal: AtomicU32,
122 stack: [u8; 504],
123}
124
125const STAGE_IDLE: u32 = 0;
126const STAGE_STARTED: u32 = 1;
127const STAGE_RESUMED: u32 = 2;
128
129impl ItemPerHart {
130 #[allow(clippy::declare_interior_mutable_const)]
131 const ZERO: Self = Self {
132 stage: AtomicU32::new(STAGE_IDLE),
133 signal: AtomicU32::new(0),
134 stack: [0; 504],
135 };
136
137 #[inline]
138 fn reset(&mut self) -> *const ItemPerHart {
139 self.stage.store(STAGE_IDLE, Ordering::Relaxed);
140 self as _
141 }
142
143 #[inline]
144 fn wait_start(&self) {
145 while self.stage.load(Ordering::Relaxed) != STAGE_STARTED {
146 core::hint::spin_loop();
147 }
148 }
149
150 #[inline]
151 fn wait_resume(&self) {
152 while self.stage.load(Ordering::Relaxed) != STAGE_RESUMED {
153 core::hint::spin_loop();
154 }
155 }
156
157 #[inline]
158 fn send_signal(&self) {
159 self.signal.store(1, Ordering::Release);
160 }
161
162 #[inline]
163 fn wait_signal(&self) {
164 while self
165 .signal
166 .compare_exchange(1, 0, Ordering::Relaxed, Ordering::Relaxed)
167 .is_err()
168 {
169 core::hint::spin_loop();
170 }
171 }
172}
173
174fn test_batch(batch: &[usize], rfnc_available: bool, mut f: impl FnMut(Case)) -> bool {
176 f(Case::BatchBegin(batch));
177 for (i, hartid) in batch.iter().copied().enumerate() {
179 let ptr = unsafe { STACK[i].reset() };
180 let ret = sbi::hart_start(hartid, test_entry as *const () as _, ptr as _);
181 if ret.is_err() {
182 f(Case::HartStartFailed { hartid, ret });
183 return false;
184 }
185 }
186 for (i, hartid) in batch.iter().copied().enumerate() {
188 let item = unsafe { &mut STACK[i] };
189 while sbi::hart_get_status(hartid) != STARTED {
191 core::hint::spin_loop();
192 }
193 f(Case::HartStarted(hartid));
194 if rfnc_available {
195 let rfence_ret = sbi::remote_fence_i(HartMask::from_mask_base(1, hartid));
196 if rfence_ret.is_ok() {
197 f(Case::RemoteRFencePass(hartid));
198 } else {
199 f(Case::RemoteRFenceFailed(hartid, rfence_ret));
200 }
201 }
202 item.wait_start();
204 item.send_signal();
206 while sbi::hart_get_status(hartid) != SUSPENDED {
208 core::hint::spin_loop();
209 }
210 f(Case::HartSuspendedNonretentive(hartid));
211 }
212 let mut mask = 1usize;
214 for hartid in &batch[1..] {
215 mask |= 1 << (hartid - batch[0]);
216 }
217 sbi::send_ipi(HartMask::from_mask_base(mask, batch[0]));
218 for (i, hartid) in batch.iter().copied().enumerate() {
220 let item = unsafe { &mut STACK[i] };
221 while sbi::hart_get_status(hartid) != STARTED {
223 core::hint::spin_loop();
224 }
225 f(Case::HartResumed(hartid));
226 item.wait_resume();
228 item.send_signal();
230 while sbi::hart_get_status(hartid) != SUSPENDED {
232 core::hint::spin_loop();
233 }
234 f(Case::HartSuspendedRetentive(hartid));
235 sbi::send_ipi(HartMask::from_mask_base(1, hartid));
237 while sbi::hart_get_status(hartid) != STOPPED {
239 core::hint::spin_loop();
240 }
241 f(Case::HartStopped(hartid));
242 }
243 f(Case::BatchPass(batch));
244 true
245}
246
247#[unsafe(naked)]
249unsafe extern "C" fn test_entry(hartid: usize, opaque: *mut ItemPerHart) -> ! {
250 core::arch::naked_asm!(
251 "csrw sie, zero", "call {set_stack}", "j {rust_main}", set_stack = sym set_stack,
255 rust_main = sym rust_main,
256 )
257}
258
259#[unsafe(naked)]
260unsafe extern "C" fn set_stack(hart_id: usize, ptr: *const ItemPerHart) {
261 core::arch::naked_asm!("addi sp, a1, 512", "ret");
262}
263
264#[inline(never)]
265extern "C" fn rust_main(hart_id: usize, opaque: *mut ItemPerHart) -> ! {
266 let item = unsafe { &mut *opaque };
267 match item.stage.compare_exchange(
268 STAGE_IDLE,
269 STAGE_STARTED,
270 Ordering::AcqRel,
271 Ordering::Acquire,
272 ) {
273 Ok(_) => {
274 item.wait_signal();
275 let ret =
276 sbi::hart_suspend(sbi::NonRetentive, test_entry as *const () as _, opaque as _);
277 unreachable!("suspend [{hart_id}] but {ret:?}")
278 }
279 Err(STAGE_STARTED) => {
280 item.stage.store(STAGE_RESUMED, Ordering::Release);
281 item.wait_signal();
282 let _ = sbi::hart_suspend(sbi::Retentive, test_entry as *const () as _, opaque as _);
283 let ret = sbi::hart_stop();
284 unreachable!("suspend [{hart_id}] but {ret:?}")
285 }
286 Err(_) => unreachable!(),
287 }
288}