1use core::{pin::Pin, ptr::NonNull, sync::atomic::Ordering};
4
5#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
6use crate::preempt::PreemptionState;
7use crate::{
8 ContextSwitchError, CpuAreaRef, CpuIndex, CpuLocalError, CpuPin, ExecutionContextHeader,
9 preempt::PreemptionSnapshot,
10};
11
12#[cfg(all(not(feature = "host-test"), target_arch = "aarch64"))]
13mod aarch64;
14#[cfg(feature = "host-test")]
15mod host;
16#[cfg(all(not(feature = "host-test"), target_arch = "loongarch64"))]
17mod loongarch64;
18#[cfg(all(
19 not(feature = "host-test"),
20 any(target_arch = "riscv32", target_arch = "riscv64")
21))]
22mod riscv;
23#[cfg(all(not(feature = "host-test"), target_arch = "x86_64"))]
24mod x86_64;
25
26#[cfg(all(not(feature = "host-test"), target_arch = "aarch64"))]
27use aarch64 as imp;
28#[cfg(feature = "host-test")]
29use host as imp;
30#[cfg(all(not(feature = "host-test"), target_arch = "loongarch64"))]
31use loongarch64 as imp;
32#[cfg(all(
33 not(feature = "host-test"),
34 any(target_arch = "riscv32", target_arch = "riscv64")
35))]
36use riscv as imp;
37#[cfg(all(not(feature = "host-test"), target_arch = "x86_64"))]
38use x86_64 as imp;
39
40#[cfg(all(
41 not(feature = "host-test"),
42 not(any(
43 target_arch = "x86_64",
44 target_arch = "aarch64",
45 target_arch = "riscv32",
46 target_arch = "riscv64",
47 target_arch = "loongarch64"
48 ))
49))]
50compile_error!("cpu-local supports x86_64, AArch64, RISC-V, and LoongArch64 only");
51
52#[derive(Clone, Copy, Debug)]
53pub(super) struct ArchitectureCurrentModel {
54 pub(super) linux_current: CurrentContextSource,
55 pub(super) unikernel_tls: CurrentContextSource,
56}
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub(super) enum CurrentContextSource {
60 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
61 ArchitectureRegister,
62 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
63 RuntimeAnchor,
64}
65
66impl ArchitectureCurrentModel {
67 const fn current_context_source(self, tls_enabled: bool) -> CurrentContextSource {
68 if tls_enabled {
69 self.unikernel_tls
70 } else {
71 self.linux_current
72 }
73 }
74}
75
76pub(super) trait ArchitectureRegisterBackend {
82 #[inline(always)]
83 fn current_cpu_index() -> Result<CpuIndex, CpuLocalError> {
84 default_current_cpu_index()
85 }
86
87 #[inline(always)]
88 fn current_preemption_snapshot() -> Result<PreemptionSnapshot, CpuLocalError> {
89 default_current_preemption_snapshot()
90 }
91}
92
93fn default_current_cpu_index() -> Result<CpuIndex, CpuLocalError> {
94 Ok(current_area()?.cpu_index())
95}
96
97#[doc(hidden)]
104pub unsafe fn install_cpu_area(area: CpuAreaRef) -> Result<(), CpuLocalError> {
105 imp::validate_environment()?;
106 let boot_context = area.prefix().boot_context().header();
107 let boot_pointer = boot_context as *const ExecutionContextHeader as usize;
108 unsafe { imp::install_cpu_base(area.base(), boot_pointer) };
110 if unsafe { imp::read_cpu_base()? } != area.base() {
111 fatal_register_invariant();
112 }
113 Ok(())
114}
115
116pub(crate) fn current_area() -> Result<CpuAreaRef, CpuLocalError> {
117 let area_base = unsafe { imp::read_cpu_base()? };
118 if area_base == 0 {
119 return Err(CpuLocalError::AreaNotInstalled);
120 }
121 Ok(unsafe { CpuAreaRef::from_installed_base(area_base) })
124}
125
126#[inline(always)]
133pub unsafe fn current_cpu_index() -> Result<CpuIndex, CpuLocalError> {
134 imp::Backend::current_cpu_index()
135}
136
137#[inline(always)]
144pub(crate) unsafe fn current_cpu_area_base() -> Result<usize, CpuLocalError> {
145 let area_base = unsafe { imp::read_cpu_base()? };
146 if area_base == 0 {
147 return Err(CpuLocalError::AreaNotInstalled);
148 }
149 if !area_base.is_multiple_of(core::mem::align_of::<crate::CpuAreaPrefix>()) {
150 return Err(CpuLocalError::InvalidAreaBase { base: area_base });
151 }
152 Ok(area_base)
153}
154
155#[inline(always)]
156pub(crate) fn current_preemption_snapshot() -> Result<PreemptionSnapshot, CpuLocalError> {
157 imp::Backend::current_preemption_snapshot()
158}
159
160#[inline(always)]
161fn default_current_preemption_snapshot() -> Result<PreemptionSnapshot, CpuLocalError> {
162 let current = unsafe { current_context_unpinned()? };
163 Ok(unsafe { current.as_ref() }.preemption_state().snapshot())
168}
169
170pub(crate) unsafe fn commit_current_context(_area: CpuAreaRef, _value: usize) {
177 match imp::CURRENT_MODEL.current_context_source(cfg!(feature = "tls")) {
178 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
179 CurrentContextSource::RuntimeAnchor => _area
180 .runtime_anchor()
181 .current_context_slot()
182 .store(_value, Ordering::Release),
183 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
184 CurrentContextSource::ArchitectureRegister => {
185 core::sync::atomic::compiler_fence(Ordering::Release);
186 #[cfg(feature = "host-test")]
187 unsafe {
188 imp::write_current_context(_value)
189 };
190 }
191 }
192}
193
194pub fn current_context(pin: &CpuPin<'_>) -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
200 let area = pin.area();
201 let raw = match imp::CURRENT_MODEL.current_context_source(cfg!(feature = "tls")) {
202 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
203 CurrentContextSource::ArchitectureRegister => unsafe {
204 imp::read_current_context(area.base())
205 },
206 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
207 CurrentContextSource::RuntimeAnchor => area.runtime_anchor().current_context_raw(),
208 };
209 validated_context_pointer(raw)
210}
211
212#[doc(hidden)]
219pub unsafe fn current_context_unpinned() -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
220 match imp::CURRENT_MODEL.current_context_source(cfg!(feature = "tls")) {
221 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
222 CurrentContextSource::ArchitectureRegister => {
223 let register = unsafe { imp::read_current_context(0) };
227 validated_context_pointer(register)
228 }
229 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
230 CurrentContextSource::RuntimeAnchor => {
231 #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
232 {
233 validated_context_pointer(unsafe { imp::read_current_context(0) })
238 }
239 #[cfg(not(all(target_arch = "x86_64", not(feature = "host-test"))))]
240 loop {
241 let area = current_area()?;
244 let register = unsafe { imp::read_current_context(area.base()) };
245 if unsafe { imp::read_cpu_base()? } != area.base() {
246 continue;
247 }
248 return validated_context_pointer(register);
249 }
250 }
251 }
252}
253
254#[doc(hidden)]
260pub fn is_permanent_boot_context(
261 context: NonNull<ExecutionContextHeader>,
262) -> Result<bool, CpuLocalError> {
263 let area = current_area()?;
264 Ok(context == NonNull::from(area.prefix().boot_context().header()))
265}
266
267fn validated_context_pointer(raw: usize) -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
268 if raw == 0 || !raw.is_multiple_of(core::mem::align_of::<ExecutionContextHeader>()) {
269 return Err(CpuLocalError::CurrentContextMismatch);
270 }
271 NonNull::new(raw as *mut ExecutionContextHeader).ok_or(CpuLocalError::CurrentContextMismatch)
272}
273
274#[cfg(feature = "host-test")]
275pub(crate) mod host_test {
276 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
278 pub struct RegisterReadCounts {
279 pub cpu_base: usize,
281 pub current_context: usize,
283 pub binding_observations: usize,
285 pub initialized_area_validations: usize,
287 }
288
289 pub fn reset_register_read_counts() {
291 super::imp::reset_register_read_counts();
292 }
293
294 pub fn register_read_counts() -> RegisterReadCounts {
296 super::imp::register_read_counts()
297 }
298
299 pub(crate) fn record_initialized_area_validation() {
300 super::imp::record_initialized_area_validation();
301 }
302
303 pub(crate) fn record_binding_observation() {
304 super::imp::record_binding_observation();
305 }
306}
307
308#[cfg(all(test, feature = "host-test"))]
309mod tests {
310 use core::mem::MaybeUninit;
311
312 use super::*;
313 use crate::{CpuAreaPrefix, CpuIndex};
314
315 fn modeled_area(cpu_index: usize) -> CpuAreaRef {
316 let storage = Box::leak(Box::new(MaybeUninit::<CpuAreaPrefix>::uninit()));
317 let base = storage.as_mut_ptr() as usize;
318 storage.write(
319 CpuAreaPrefix::initialize(CpuIndex::try_from(cpu_index).unwrap(), base).unwrap(),
320 );
321 unsafe { CpuAreaRef::from_initialized_base(base) }.unwrap()
323 }
324
325 #[test]
326 fn independent_current_register_ignores_kernel_tls_feature() {
327 let independent = ArchitectureCurrentModel {
328 linux_current: CurrentContextSource::ArchitectureRegister,
329 unikernel_tls: CurrentContextSource::ArchitectureRegister,
330 };
331 assert_eq!(
332 independent.current_context_source(false),
333 CurrentContextSource::ArchitectureRegister,
334 );
335 assert_eq!(
336 independent.current_context_source(true),
337 CurrentContextSource::ArchitectureRegister,
338 );
339 }
340
341 #[test]
342 fn aliased_current_register_follows_kernel_tls_feature() {
343 let aliased = ArchitectureCurrentModel {
344 linux_current: CurrentContextSource::ArchitectureRegister,
345 unikernel_tls: CurrentContextSource::RuntimeAnchor,
346 };
347 assert_eq!(
348 aliased.current_context_source(false),
349 CurrentContextSource::ArchitectureRegister,
350 );
351 assert_eq!(
352 aliased.current_context_source(true),
353 CurrentContextSource::RuntimeAnchor,
354 );
355 }
356
357 #[test]
358 fn current_context_unpinned_survives_migration_during_bootstrap_read() {
359 let first = modeled_area(0);
360 let second = modeled_area(1);
361 let first_boot = first.prefix().boot_context().header();
362
363 unsafe { imp::install_cpu_base(first.base(), first_boot as *const _ as usize) };
365 imp::migrate_on_next_current_read(second.base());
366
367 assert_eq!(
368 unsafe { current_context_unpinned() },
370 if cfg!(feature = "tls") {
371 Ok(NonNull::from(second.prefix().boot_context().header()))
372 } else {
373 Ok(NonNull::from(first_boot))
374 },
375 );
376 }
377
378 #[test]
379 fn current_context_unpinned_rejects_an_uninstalled_host_area() {
380 let rejected = std::thread::spawn(move || {
381 unsafe { current_context_unpinned() }.is_err()
384 })
385 .join()
386 .expect("host current-context probe panicked");
387
388 assert!(rejected);
389 }
390
391 #[test]
392 fn permanent_boot_context_is_classified_by_area_identity() {
393 let area = modeled_area(0);
394 let boot = area.prefix().boot_context().header();
395 let runtime_context = Box::pin(ExecutionContextHeader::new());
396
397 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
399
400 assert!(boot.is_permanent_boot_context());
401 assert!(!runtime_context.is_permanent_boot_context());
402 assert_eq!(is_permanent_boot_context(NonNull::from(boot)), Ok(true));
403 assert_eq!(
404 is_permanent_boot_context(runtime_context.as_ref().as_non_null()),
405 Ok(false)
406 );
407 }
408
409 #[test]
410 fn installed_current_area_reuses_install_time_identity_validation() {
411 let area = modeled_area(0);
412 let boot = area.prefix().boot_context().header();
413
414 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
416 host_test::reset_register_read_counts();
417
418 assert_eq!(current_area(), Ok(area));
419 assert_eq!(
420 host_test::register_read_counts(),
421 host_test::RegisterReadCounts {
422 cpu_base: 1,
423 current_context: 0,
424 binding_observations: 0,
425 initialized_area_validations: 0,
426 },
427 "a live installed base must not repeat shutdown-lifetime identity validation",
428 );
429 }
430
431 #[test]
432 fn pin_construction_trusts_published_area_and_context_identity() {
433 let area = modeled_area(0);
434 let boot = area.prefix().boot_context().header();
435
436 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
438 host_test::reset_register_read_counts();
439
440 unsafe { crate::with_cpu_pin(|_| ()) }.unwrap();
442
443 assert_eq!(
444 host_test::register_read_counts().initialized_area_validations,
445 0,
446 "pin construction must reuse the area identity validated before installation",
447 );
448 assert_eq!(
449 host_test::register_read_counts().binding_observations,
450 0,
451 "pin construction must trust the current binding published by the switch boundary",
452 );
453 assert_eq!(
454 host_test::register_read_counts().current_context,
455 0,
456 "pin construction must not re-read the current context after publication",
457 );
458 }
459
460 #[test]
461 fn backend_default_observes_the_current_execution_context() {
462 let area = modeled_area(0);
463 let boot = area.prefix().boot_context().header();
464
465 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
467
468 let snapshot = current_preemption_snapshot()
469 .expect("host backend default should observe its boot context");
470 assert_eq!(snapshot.depth(), 1);
471 assert!(!snapshot.is_pending());
472 }
473
474 #[test]
475 #[cfg(not(feature = "tls"))]
476 fn architecture_current_is_authoritative_when_anchor_is_stale() {
477 let area = modeled_area(0);
478 let boot = area.prefix().boot_context().header();
479 let next = Box::pin(ExecutionContextHeader::new());
480
481 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
484 unsafe {
485 crate::with_cpu_pin(|pin| {
486 let next_epoch = next.as_ref().bind_cpu(area).unwrap();
487 imp::set_architecture_current(next.as_ref().as_non_null().as_ptr() as usize);
488
489 assert_eq!(current_context(pin), Ok(next.as_ref().as_non_null()));
490
491 imp::set_architecture_current(0);
492 next.as_ref().unbind_cpu(next_epoch).unwrap();
493 })
494 }
495 .unwrap();
496 }
497}
498
499#[doc(hidden)]
506pub unsafe fn install_bootstrap_context(
507 pin: &CpuPin<'_>,
508 header: Pin<&ExecutionContextHeader>,
509) -> Result<(), ContextSwitchError> {
510 let epoch = unsafe { header.bind_cpu(pin.area()) }?;
511 let pointer = header.as_non_null().as_ptr() as usize;
512 match imp::CURRENT_MODEL.current_context_source(cfg!(feature = "tls")) {
513 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
514 CurrentContextSource::RuntimeAnchor => unsafe {
515 commit_current_context(pin.area(), pointer)
516 },
517 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
518 CurrentContextSource::ArchitectureRegister => unsafe {
519 imp::write_current_context(pointer)
520 },
521 }
522 if current_context(pin) != Ok(header.as_non_null()) || !header.is_bound_to(pin.area()) {
523 let _ = epoch;
526 fatal_register_invariant();
527 }
528 Ok(())
529}
530
531#[cfg(feature = "tls")]
533pub fn kernel_tls(_pin: &CpuPin<'_>) -> usize {
534 unsafe { imp::read_kernel_tls() }
535}
536
537#[cfg(feature = "tls")]
544#[doc(hidden)]
545pub unsafe fn install_kernel_tls(_pin: &CpuPin<'_>, value: usize) {
546 unsafe { imp::write_kernel_tls(value) };
547}
548
549#[cold]
550#[inline(never)]
551pub(crate) fn fatal_register_invariant() -> ! {
552 panic!("CPU-local register commit did not retain the validated state")
553}
554
555#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
556#[inline(always)]
557pub(crate) unsafe fn enter_x86_preemption() {
558 unsafe { imp::enter_preemption() };
559}
560
561#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
562#[inline(always)]
563pub(crate) unsafe fn current_x86_preemption_state() -> &'static PreemptionState {
564 unsafe { imp::current_preemption_state() }
567}
568
569#[cfg(all(test, target_arch = "x86_64", not(feature = "host-test")))]
576#[inline(always)]
577pub(crate) unsafe fn compare_exchange_x86_preemption_state(
578 state: &PreemptionState,
579 current: u32,
580 next: u32,
581) -> bool {
582 unsafe { imp::compare_exchange_preemption_state(state, current, next) }
585}
586
587#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
588#[inline(always)]
589pub(crate) unsafe fn read_current_x86_preemption_state_raw() -> u32 {
590 unsafe { imp::read_preemption_state() }
592}
593
594#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
595#[inline(always)]
596pub(crate) unsafe fn compare_exchange_current_x86_preemption_state(
597 current: u32,
598 next: u32,
599) -> bool {
600 unsafe { imp::compare_exchange_current_preemption_state(current, next) }
602}
603
604#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
605#[inline(always)]
606pub(crate) unsafe fn decrement_current_x86_preemption_state() {
607 unsafe { imp::decrement_current_preemption_state() }
609}