1use core::{pin::Pin, ptr::NonNull, sync::atomic::Ordering};
4
5use crate::{ContextSwitchError, CpuAreaRef, CpuLocalError, CpuPin, ExecutionContextHeader};
6
7#[cfg(all(not(feature = "host-test"), target_arch = "aarch64"))]
8mod aarch64;
9#[cfg(feature = "host-test")]
10mod host;
11#[cfg(all(not(feature = "host-test"), target_arch = "loongarch64"))]
12mod loongarch64;
13#[cfg(all(
14 not(feature = "host-test"),
15 any(target_arch = "riscv32", target_arch = "riscv64")
16))]
17mod riscv;
18#[cfg(all(not(feature = "host-test"), target_arch = "x86_64"))]
19mod x86_64;
20
21#[cfg(all(not(feature = "host-test"), target_arch = "aarch64"))]
22use aarch64 as imp;
23#[cfg(feature = "host-test")]
24use host as imp;
25#[cfg(all(not(feature = "host-test"), target_arch = "loongarch64"))]
26use loongarch64 as imp;
27#[cfg(all(
28 not(feature = "host-test"),
29 any(target_arch = "riscv32", target_arch = "riscv64")
30))]
31use riscv as imp;
32#[cfg(all(not(feature = "host-test"), target_arch = "x86_64"))]
33use x86_64 as imp;
34
35#[cfg(all(
36 not(feature = "host-test"),
37 not(any(
38 target_arch = "x86_64",
39 target_arch = "aarch64",
40 target_arch = "riscv32",
41 target_arch = "riscv64",
42 target_arch = "loongarch64"
43 ))
44))]
45compile_error!("cpu-local supports x86_64, AArch64, RISC-V, and LoongArch64 only");
46
47#[derive(Clone, Copy, Debug)]
48pub(super) struct ArchitectureCurrentModel {
49 pub(super) linux_current: CurrentContextSource,
50 pub(super) unikernel_tls: CurrentContextSource,
51}
52
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54pub(super) enum CurrentContextSource {
55 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
56 ArchitectureRegister,
57 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
58 RuntimeAnchor,
59}
60
61impl ArchitectureCurrentModel {
62 const fn current_context_source(self, tls_enabled: bool) -> CurrentContextSource {
63 if tls_enabled {
64 self.unikernel_tls
65 } else {
66 self.linux_current
67 }
68 }
69}
70
71#[doc(hidden)]
78pub unsafe fn install_cpu_area(area: CpuAreaRef) -> Result<(), CpuLocalError> {
79 imp::validate_environment()?;
80 let boot_context = area.prefix().boot_context().header();
81 let boot_pointer = boot_context as *const ExecutionContextHeader as usize;
82 unsafe { imp::install_cpu_base(area.base(), boot_pointer) };
84 if unsafe { imp::read_cpu_base()? } != area.base() {
85 fatal_register_invariant();
86 }
87 Ok(())
88}
89
90pub(crate) fn current_area() -> Result<CpuAreaRef, CpuLocalError> {
91 let area_base = unsafe { imp::read_cpu_base()? };
92 if area_base == 0 {
93 return Err(CpuLocalError::AreaNotInstalled);
94 }
95 unsafe { CpuAreaRef::from_initialized_base(area_base) }
98}
99
100#[inline(always)]
107pub(crate) unsafe fn current_cpu_area_base() -> Result<usize, CpuLocalError> {
108 let area_base = unsafe { imp::read_cpu_base()? };
109 if area_base == 0 {
110 return Err(CpuLocalError::AreaNotInstalled);
111 }
112 if !area_base.is_multiple_of(core::mem::align_of::<crate::CpuAreaPrefix>()) {
113 return Err(CpuLocalError::InvalidAreaBase { base: area_base });
114 }
115 Ok(area_base)
116}
117
118pub(crate) unsafe fn commit_current_context(_area: CpuAreaRef, _value: usize) {
125 match imp::CURRENT_MODEL.current_context_source(cfg!(feature = "tls")) {
126 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
127 CurrentContextSource::RuntimeAnchor => _area
128 .runtime_anchor()
129 .current_context_slot()
130 .store(_value, Ordering::Release),
131 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
132 CurrentContextSource::ArchitectureRegister => {
133 core::sync::atomic::compiler_fence(Ordering::Release);
134 #[cfg(feature = "host-test")]
135 unsafe {
136 imp::write_current_context(_value)
137 };
138 }
139 }
140}
141
142pub fn current_context(pin: &CpuPin<'_>) -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
144 let area = pin.area();
145 let raw = match imp::CURRENT_MODEL.current_context_source(cfg!(feature = "tls")) {
146 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
147 CurrentContextSource::ArchitectureRegister => unsafe {
148 imp::read_current_context(area.base())
149 },
150 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
151 CurrentContextSource::RuntimeAnchor => area.runtime_anchor().current_context_raw(),
152 };
153 let pointer = validated_context_pointer(raw)?;
154 let context_area = unsafe { pointer.as_ref() }
157 .cpu_area()
158 .ok_or(CpuLocalError::CurrentContextMismatch)?;
159 if context_area != area {
160 return Err(CpuLocalError::CurrentContextMismatch);
161 }
162 Ok(pointer)
163}
164
165#[doc(hidden)]
172pub unsafe fn current_context_unpinned() -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
173 match imp::CURRENT_MODEL.current_context_source(cfg!(feature = "tls")) {
174 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
175 CurrentContextSource::ArchitectureRegister => {
176 let register = unsafe { imp::read_current_context(0) };
180 validated_context_pointer(register)
181 }
182 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
183 CurrentContextSource::RuntimeAnchor => loop {
184 let area = current_area()?;
188 let register = unsafe { imp::read_current_context(area.base()) };
189 if unsafe { imp::read_cpu_base()? } != area.base() {
190 continue;
191 }
192 return validated_context_pointer(register);
193 },
194 }
195}
196
197#[doc(hidden)]
203pub fn is_permanent_boot_context(
204 context: NonNull<ExecutionContextHeader>,
205) -> Result<bool, CpuLocalError> {
206 let area = current_area()?;
207 Ok(context == NonNull::from(area.prefix().boot_context().header()))
208}
209
210fn validated_context_pointer(raw: usize) -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
211 if raw == 0 || !raw.is_multiple_of(core::mem::align_of::<ExecutionContextHeader>()) {
212 return Err(CpuLocalError::CurrentContextMismatch);
213 }
214 NonNull::new(raw as *mut ExecutionContextHeader).ok_or(CpuLocalError::CurrentContextMismatch)
215}
216
217#[cfg(feature = "host-test")]
218pub(crate) mod host_test {
219 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
221 pub struct RegisterReadCounts {
222 pub cpu_base: usize,
224 pub current_context: usize,
226 pub initialized_area_validations: usize,
228 }
229
230 pub fn reset_register_read_counts() {
232 super::imp::reset_register_read_counts();
233 }
234
235 pub fn register_read_counts() -> RegisterReadCounts {
237 super::imp::register_read_counts()
238 }
239
240 pub(crate) fn record_initialized_area_validation() {
241 super::imp::record_initialized_area_validation();
242 }
243}
244
245#[cfg(all(test, feature = "host-test"))]
246mod tests {
247 use core::mem::MaybeUninit;
248
249 use super::*;
250 use crate::{CpuAreaPrefix, CpuIndex};
251
252 fn modeled_area(cpu_index: usize) -> CpuAreaRef {
253 let storage = Box::leak(Box::new(MaybeUninit::<CpuAreaPrefix>::uninit()));
254 let base = storage.as_mut_ptr() as usize;
255 storage.write(
256 CpuAreaPrefix::initialize(CpuIndex::try_from(cpu_index).unwrap(), base).unwrap(),
257 );
258 unsafe { CpuAreaRef::from_initialized_base(base) }.unwrap()
260 }
261
262 #[test]
263 fn independent_current_register_ignores_kernel_tls_feature() {
264 let independent = ArchitectureCurrentModel {
265 linux_current: CurrentContextSource::ArchitectureRegister,
266 unikernel_tls: CurrentContextSource::ArchitectureRegister,
267 };
268 assert_eq!(
269 independent.current_context_source(false),
270 CurrentContextSource::ArchitectureRegister,
271 );
272 assert_eq!(
273 independent.current_context_source(true),
274 CurrentContextSource::ArchitectureRegister,
275 );
276 }
277
278 #[test]
279 fn aliased_current_register_follows_kernel_tls_feature() {
280 let aliased = ArchitectureCurrentModel {
281 linux_current: CurrentContextSource::ArchitectureRegister,
282 unikernel_tls: CurrentContextSource::RuntimeAnchor,
283 };
284 assert_eq!(
285 aliased.current_context_source(false),
286 CurrentContextSource::ArchitectureRegister,
287 );
288 assert_eq!(
289 aliased.current_context_source(true),
290 CurrentContextSource::RuntimeAnchor,
291 );
292 }
293
294 #[test]
295 fn current_context_unpinned_survives_migration_during_bootstrap_read() {
296 let first = modeled_area(0);
297 let second = modeled_area(1);
298 let first_boot = first.prefix().boot_context().header();
299
300 unsafe { imp::install_cpu_base(first.base(), first_boot as *const _ as usize) };
302 imp::migrate_on_next_current_read(second.base());
303
304 assert_eq!(
305 unsafe { current_context_unpinned() },
307 if cfg!(feature = "tls") {
308 Ok(NonNull::from(second.prefix().boot_context().header()))
309 } else {
310 Ok(NonNull::from(first_boot))
311 },
312 );
313 }
314
315 #[test]
316 fn current_context_unpinned_rejects_an_uninstalled_host_area() {
317 let rejected = std::thread::spawn(move || {
318 unsafe { current_context_unpinned() }.is_err()
321 })
322 .join()
323 .expect("host current-context probe panicked");
324
325 assert!(rejected);
326 }
327
328 #[test]
329 fn permanent_boot_context_is_classified_by_area_identity() {
330 let area = modeled_area(0);
331 let boot = area.prefix().boot_context().header();
332 let runtime_context = Box::pin(ExecutionContextHeader::new());
333
334 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
336
337 assert_eq!(is_permanent_boot_context(NonNull::from(boot)), Ok(true));
338 assert_eq!(
339 is_permanent_boot_context(runtime_context.as_ref().as_non_null()),
340 Ok(false)
341 );
342 }
343
344 #[test]
345 #[cfg(not(feature = "tls"))]
346 fn architecture_current_is_authoritative_when_anchor_is_stale() {
347 let area = modeled_area(0);
348 let boot = area.prefix().boot_context().header();
349 let next = Box::pin(ExecutionContextHeader::new());
350
351 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
354 unsafe {
355 crate::with_cpu_pin(|pin| {
356 let next_epoch = next.as_ref().bind_cpu(area).unwrap();
357 imp::set_architecture_current(next.as_ref().as_non_null().as_ptr() as usize);
358
359 assert_eq!(current_context(pin), Ok(next.as_ref().as_non_null()));
360
361 imp::set_architecture_current(0);
362 next.as_ref().unbind_cpu(next_epoch).unwrap();
363 })
364 }
365 .unwrap();
366 }
367}
368
369#[doc(hidden)]
376pub unsafe fn install_bootstrap_context(
377 pin: &CpuPin<'_>,
378 header: Pin<&ExecutionContextHeader>,
379) -> Result<(), ContextSwitchError> {
380 let epoch = unsafe { header.bind_cpu(pin.area()) }?;
381 let pointer = header.as_non_null().as_ptr() as usize;
382 match imp::CURRENT_MODEL.current_context_source(cfg!(feature = "tls")) {
383 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
384 CurrentContextSource::RuntimeAnchor => unsafe {
385 commit_current_context(pin.area(), pointer)
386 },
387 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
388 CurrentContextSource::ArchitectureRegister => unsafe {
389 imp::write_current_context(pointer)
390 },
391 }
392 if current_context(pin) != Ok(header.as_non_null()) {
393 let _ = epoch;
396 fatal_register_invariant();
397 }
398 Ok(())
399}
400
401#[cfg(feature = "tls")]
403pub fn kernel_tls(_pin: &CpuPin<'_>) -> usize {
404 unsafe { imp::read_kernel_tls() }
405}
406
407#[cfg(feature = "tls")]
414#[doc(hidden)]
415pub unsafe fn install_kernel_tls(_pin: &CpuPin<'_>, value: usize) {
416 unsafe { imp::write_kernel_tls(value) };
417}
418
419#[cold]
420#[inline(never)]
421pub(crate) fn fatal_register_invariant() -> ! {
422 panic!("CPU-local register commit did not retain the validated state")
423}
424
425#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
426#[inline(always)]
427pub(crate) unsafe fn enter_x86_preemption() {
428 unsafe { imp::enter_preemption() };
429}