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(not(feature = "host-test"), target_arch = "riscv64"))]
19mod riscv;
20#[cfg(all(not(feature = "host-test"), target_arch = "x86_64"))]
21mod x86_64;
22
23#[cfg(all(not(feature = "host-test"), target_arch = "aarch64"))]
24use aarch64 as imp;
25#[cfg(feature = "host-test")]
26use host as imp;
27#[cfg(all(not(feature = "host-test"), target_arch = "loongarch64"))]
28use loongarch64 as imp;
29#[cfg(all(not(feature = "host-test"), target_arch = "riscv64"))]
30use riscv as imp;
31#[cfg(all(not(feature = "host-test"), target_arch = "x86_64"))]
32use x86_64 as imp;
33
34#[cfg(all(
35 not(feature = "host-test"),
36 not(any(
37 target_arch = "x86_64",
38 target_arch = "aarch64",
39 target_arch = "riscv64",
40 target_arch = "loongarch64"
41 ))
42))]
43compile_error!("cpu-local supports x86_64, AArch64, RISC-V, and LoongArch64 only");
44
45#[derive(Clone, Copy, Debug)]
46pub(super) struct ArchitectureCurrentModel {
47 pub(super) linux_current: CurrentContextSource,
48 pub(super) unikernel_tls: CurrentContextSource,
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub(super) enum CurrentContextSource {
53 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
54 ArchitectureRegister,
55 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
56 RuntimeAnchor,
57}
58
59impl ArchitectureCurrentModel {
60 const fn current_context_source(self, tls_enabled: bool) -> CurrentContextSource {
61 if tls_enabled {
62 self.unikernel_tls
63 } else {
64 self.linux_current
65 }
66 }
67}
68
69pub(super) trait ArchitectureRegisterBackend {
75 #[inline(always)]
76 fn current_cpu_index() -> Result<CpuIndex, CpuLocalError> {
77 default_current_cpu_index()
78 }
79
80 #[inline(always)]
81 fn current_preemption_snapshot() -> Result<PreemptionSnapshot, CpuLocalError> {
82 default_current_preemption_snapshot()
83 }
84}
85
86fn default_current_cpu_index() -> Result<CpuIndex, CpuLocalError> {
87 Ok(current_area()?.cpu_index())
88}
89
90#[doc(hidden)]
97pub unsafe fn install_cpu_area(area: CpuAreaRef) -> Result<(), CpuLocalError> {
98 imp::validate_environment()?;
99 let boot_context = area.prefix().boot_context().header();
100 let boot_pointer = boot_context as *const ExecutionContextHeader as usize;
101 unsafe { imp::install_cpu_base(area.base(), boot_pointer) };
103 if unsafe { imp::read_cpu_base()? } != area.base() {
104 fatal_register_invariant();
105 }
106 Ok(())
107}
108
109pub(crate) fn current_area() -> Result<CpuAreaRef, CpuLocalError> {
110 let area_base = unsafe { imp::read_cpu_base()? };
111 if area_base == 0 {
112 return Err(CpuLocalError::AreaNotInstalled);
113 }
114 Ok(unsafe { CpuAreaRef::from_installed_base(area_base) })
117}
118
119#[inline(always)]
126pub unsafe fn current_cpu_index() -> Result<CpuIndex, CpuLocalError> {
127 imp::Backend::current_cpu_index()
128}
129
130#[inline(always)]
137pub(crate) unsafe fn current_cpu_area_base() -> Result<usize, CpuLocalError> {
138 let area_base = unsafe { imp::read_cpu_base()? };
139 if area_base == 0 {
140 return Err(CpuLocalError::AreaNotInstalled);
141 }
142 if !area_base.is_multiple_of(core::mem::align_of::<crate::CpuAreaPrefix>()) {
143 return Err(CpuLocalError::InvalidAreaBase { base: area_base });
144 }
145 Ok(area_base)
146}
147
148#[inline(always)]
149pub(crate) fn current_preemption_snapshot() -> Result<PreemptionSnapshot, CpuLocalError> {
150 imp::Backend::current_preemption_snapshot()
151}
152
153#[inline(always)]
154fn default_current_preemption_snapshot() -> Result<PreemptionSnapshot, CpuLocalError> {
155 let current = unsafe { current_context_unpinned()? };
156 Ok(unsafe { current.as_ref() }.preemption_state().snapshot())
161}
162
163pub(crate) unsafe fn commit_current_context(_area: CpuAreaRef, _value: usize) {
170 match imp::CURRENT_MODEL.current_context_source(cfg!(kernel_tls)) {
171 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
172 CurrentContextSource::RuntimeAnchor => _area
173 .runtime_anchor()
174 .current_context_slot()
175 .store(_value, Ordering::Release),
176 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
177 CurrentContextSource::ArchitectureRegister => {
178 core::sync::atomic::compiler_fence(Ordering::Release);
179 #[cfg(feature = "host-test")]
180 unsafe {
181 imp::write_current_context(_value)
182 };
183 }
184 }
185}
186
187pub fn current_context(pin: &CpuPin<'_>) -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
193 let area = pin.area();
194 let raw = match imp::CURRENT_MODEL.current_context_source(cfg!(kernel_tls)) {
195 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
196 CurrentContextSource::ArchitectureRegister => unsafe {
197 imp::read_current_context(area.base())
198 },
199 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
200 CurrentContextSource::RuntimeAnchor => area.runtime_anchor().current_context_raw(),
201 };
202 validated_context_pointer(raw)
203}
204
205#[doc(hidden)]
212pub unsafe fn current_context_unpinned() -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
213 match imp::CURRENT_MODEL.current_context_source(cfg!(kernel_tls)) {
214 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
215 CurrentContextSource::ArchitectureRegister => {
216 let register = unsafe { imp::read_current_context(0) };
220 validated_context_pointer(register)
221 }
222 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
223 CurrentContextSource::RuntimeAnchor => {
224 #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
225 {
226 validated_context_pointer(unsafe { imp::read_current_context(0) })
231 }
232 #[cfg(not(all(target_arch = "x86_64", not(feature = "host-test"))))]
233 loop {
234 let area = current_area()?;
237 let register = unsafe { imp::read_current_context(area.base()) };
238 if unsafe { imp::read_cpu_base()? } != area.base() {
239 continue;
240 }
241 return validated_context_pointer(register);
242 }
243 }
244 }
245}
246
247#[doc(hidden)]
253pub fn is_permanent_boot_context(
254 context: NonNull<ExecutionContextHeader>,
255) -> Result<bool, CpuLocalError> {
256 let area = current_area()?;
257 Ok(context == NonNull::from(area.prefix().boot_context().header()))
258}
259
260fn validated_context_pointer(raw: usize) -> Result<NonNull<ExecutionContextHeader>, CpuLocalError> {
261 if raw == 0 || !raw.is_multiple_of(core::mem::align_of::<ExecutionContextHeader>()) {
262 return Err(CpuLocalError::CurrentContextMismatch);
263 }
264 NonNull::new(raw as *mut ExecutionContextHeader).ok_or(CpuLocalError::CurrentContextMismatch)
265}
266
267#[cfg(feature = "host-test")]
268pub(crate) mod host_test {
269 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
271 pub struct RegisterReadCounts {
272 pub cpu_base: usize,
274 pub current_context: usize,
276 pub binding_observations: usize,
278 pub initialized_area_validations: usize,
280 }
281
282 pub fn reset_register_read_counts() {
284 super::imp::reset_register_read_counts();
285 }
286
287 pub fn register_read_counts() -> RegisterReadCounts {
289 super::imp::register_read_counts()
290 }
291
292 pub(crate) fn record_initialized_area_validation() {
293 super::imp::record_initialized_area_validation();
294 }
295
296 pub(crate) fn record_binding_observation() {
297 super::imp::record_binding_observation();
298 }
299}
300
301#[cfg(all(test, feature = "host-test"))]
302mod tests {
303 use core::mem::MaybeUninit;
304
305 use super::*;
306 use crate::{CpuAreaPrefix, CpuIndex};
307
308 fn modeled_area(cpu_index: usize) -> CpuAreaRef {
309 let storage = Box::leak(Box::new(MaybeUninit::<CpuAreaPrefix>::uninit()));
310 let base = storage.as_mut_ptr() as usize;
311 storage.write(
312 CpuAreaPrefix::initialize(CpuIndex::try_from(cpu_index).unwrap(), base).unwrap(),
313 );
314 unsafe { CpuAreaRef::from_initialized_base(base) }.unwrap()
316 }
317
318 #[test]
319 fn independent_current_register_ignores_kernel_tls_feature() {
320 let independent = ArchitectureCurrentModel {
321 linux_current: CurrentContextSource::ArchitectureRegister,
322 unikernel_tls: CurrentContextSource::ArchitectureRegister,
323 };
324 assert_eq!(
325 independent.current_context_source(false),
326 CurrentContextSource::ArchitectureRegister,
327 );
328 assert_eq!(
329 independent.current_context_source(true),
330 CurrentContextSource::ArchitectureRegister,
331 );
332 }
333
334 #[test]
335 fn aliased_current_register_follows_kernel_tls_feature() {
336 let aliased = ArchitectureCurrentModel {
337 linux_current: CurrentContextSource::ArchitectureRegister,
338 unikernel_tls: CurrentContextSource::RuntimeAnchor,
339 };
340 assert_eq!(
341 aliased.current_context_source(false),
342 CurrentContextSource::ArchitectureRegister,
343 );
344 assert_eq!(
345 aliased.current_context_source(true),
346 CurrentContextSource::RuntimeAnchor,
347 );
348 }
349
350 #[test]
351 fn current_context_unpinned_survives_migration_during_bootstrap_read() {
352 let first = modeled_area(0);
353 let second = modeled_area(1);
354 let first_boot = first.prefix().boot_context().header();
355
356 unsafe { imp::install_cpu_base(first.base(), first_boot as *const _ as usize) };
358 imp::migrate_on_next_current_read(second.base());
359
360 assert_eq!(
361 unsafe { current_context_unpinned() },
363 if cfg!(kernel_tls) {
364 Ok(NonNull::from(second.prefix().boot_context().header()))
365 } else {
366 Ok(NonNull::from(first_boot))
367 },
368 );
369 }
370
371 #[test]
372 fn current_context_unpinned_rejects_an_uninstalled_host_area() {
373 let rejected = std::thread::spawn(move || {
374 unsafe { current_context_unpinned() }.is_err()
377 })
378 .join()
379 .expect("host current-context probe panicked");
380
381 assert!(rejected);
382 }
383
384 #[test]
385 fn permanent_boot_context_is_classified_by_area_identity() {
386 let area = modeled_area(0);
387 let boot = area.prefix().boot_context().header();
388 let runtime_context = Box::pin(ExecutionContextHeader::new());
389
390 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
392
393 assert!(boot.is_permanent_boot_context());
394 assert!(!runtime_context.is_permanent_boot_context());
395 assert_eq!(is_permanent_boot_context(NonNull::from(boot)), Ok(true));
396 assert_eq!(
397 is_permanent_boot_context(runtime_context.as_ref().as_non_null()),
398 Ok(false)
399 );
400 }
401
402 #[test]
403 fn installed_current_area_reuses_install_time_identity_validation() {
404 let area = modeled_area(0);
405 let boot = area.prefix().boot_context().header();
406
407 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
409 host_test::reset_register_read_counts();
410
411 assert_eq!(current_area(), Ok(area));
412 assert_eq!(
413 host_test::register_read_counts(),
414 host_test::RegisterReadCounts {
415 cpu_base: 1,
416 current_context: 0,
417 binding_observations: 0,
418 initialized_area_validations: 0,
419 },
420 "a live installed base must not repeat shutdown-lifetime identity validation",
421 );
422 }
423
424 #[test]
425 fn pin_construction_trusts_published_area_and_context_identity() {
426 let area = modeled_area(0);
427 let boot = area.prefix().boot_context().header();
428
429 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
431 host_test::reset_register_read_counts();
432
433 unsafe { crate::with_cpu_pin(|_| ()) }.unwrap();
435
436 assert_eq!(
437 host_test::register_read_counts().initialized_area_validations,
438 0,
439 "pin construction must reuse the area identity validated before installation",
440 );
441 assert_eq!(
442 host_test::register_read_counts().binding_observations,
443 0,
444 "pin construction must trust the current binding published by the switch boundary",
445 );
446 assert_eq!(
447 host_test::register_read_counts().current_context,
448 0,
449 "pin construction must not re-read the current context after publication",
450 );
451 }
452
453 #[test]
454 fn backend_default_observes_the_current_execution_context() {
455 let area = modeled_area(0);
456 let boot = area.prefix().boot_context().header();
457
458 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
460
461 let snapshot = current_preemption_snapshot()
462 .expect("host backend default should observe its boot context");
463 assert_eq!(snapshot.depth(), 1);
464 assert!(!snapshot.is_pending());
465 }
466
467 #[test]
468 #[cfg(not(kernel_tls))]
469 fn architecture_current_is_authoritative_when_anchor_is_stale() {
470 let area = modeled_area(0);
471 let boot = area.prefix().boot_context().header();
472 let next = Box::pin(ExecutionContextHeader::new());
473
474 unsafe { imp::install_cpu_base(area.base(), boot as *const _ as usize) };
477 unsafe {
478 crate::with_cpu_pin(|pin| {
479 let next_epoch = next.as_ref().bind_cpu(area).unwrap();
480 imp::set_architecture_current(next.as_ref().as_non_null().as_ptr() as usize);
481
482 assert_eq!(current_context(pin), Ok(next.as_ref().as_non_null()));
483
484 imp::set_architecture_current(0);
485 next.as_ref().unbind_cpu(next_epoch).unwrap();
486 })
487 }
488 .unwrap();
489 }
490}
491
492#[doc(hidden)]
499pub unsafe fn install_bootstrap_context(
500 pin: &CpuPin<'_>,
501 header: Pin<&ExecutionContextHeader>,
502) -> Result<(), ContextSwitchError> {
503 let epoch = unsafe { header.bind_cpu(pin.area()) }?;
504 let pointer = header.as_non_null().as_ptr() as usize;
505 match imp::CURRENT_MODEL.current_context_source(cfg!(kernel_tls)) {
506 #[cfg(not(all(target_arch = "aarch64", not(feature = "host-test"))))]
507 CurrentContextSource::RuntimeAnchor => unsafe {
508 commit_current_context(pin.area(), pointer)
509 },
510 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
511 CurrentContextSource::ArchitectureRegister => unsafe {
512 imp::write_current_context(pointer)
513 },
514 }
515 if current_context(pin) != Ok(header.as_non_null()) || !header.is_bound_to(pin.area()) {
516 let _ = epoch;
519 fatal_register_invariant();
520 }
521 Ok(())
522}
523
524#[cfg(kernel_tls)]
526pub fn kernel_tls(_pin: &CpuPin<'_>) -> usize {
527 unsafe { imp::read_kernel_tls() }
528}
529
530#[cfg(kernel_tls)]
537#[doc(hidden)]
538pub unsafe fn install_kernel_tls(_pin: &CpuPin<'_>, value: usize) {
539 unsafe { imp::write_kernel_tls(value) };
540}
541
542#[cold]
543#[inline(never)]
544pub(crate) fn fatal_register_invariant() -> ! {
545 panic!("CPU-local register commit did not retain the validated state")
546}
547
548#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
549#[inline(always)]
550pub(crate) unsafe fn enter_x86_preemption() {
551 unsafe { imp::enter_preemption() };
552}
553
554#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
555#[inline(always)]
556pub(crate) unsafe fn current_x86_preemption_state() -> &'static PreemptionState {
557 unsafe { imp::current_preemption_state() }
560}
561
562#[cfg(all(test, target_arch = "x86_64", not(feature = "host-test")))]
569#[inline(always)]
570pub(crate) unsafe fn compare_exchange_x86_preemption_state(
571 state: &PreemptionState,
572 current: u32,
573 next: u32,
574) -> bool {
575 unsafe { imp::compare_exchange_preemption_state(state, current, next) }
578}
579
580#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
581#[inline(always)]
582pub(crate) unsafe fn read_current_x86_preemption_state_raw() -> u32 {
583 unsafe { imp::read_preemption_state() }
585}
586
587#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
588#[inline(always)]
589pub(crate) unsafe fn compare_exchange_current_x86_preemption_state(
590 current: u32,
591 next: u32,
592) -> bool {
593 unsafe { imp::compare_exchange_current_preemption_state(current, next) }
595}
596
597#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
598#[inline(always)]
599pub(crate) unsafe fn decrement_current_x86_preemption_state() {
600 unsafe { imp::decrement_current_preemption_state() }
602}