Skip to main content

ax_percpu/
layout.rs

1use core::mem::size_of;
2
3use cpu_local::{CpuAreaPrefix, CpuIndex};
4
5use crate::{PerCpuArea, PerCpuError, PerCpuRegion};
6
7static INSTALLED_LAYOUT: ax_lazyinit::OnceLock<PerCpuLayout> = ax_lazyinit::OnceLock::new();
8
9/// Frozen process-wide layout of initialized runtime per-CPU areas.
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub struct PerCpuLayout {
12    region: PerCpuRegion,
13    template_base: usize,
14    area_size: usize,
15    required_alignment: usize,
16}
17
18// SAFETY: the layout is immutable and its installation contract keeps the
19// described storage mapped until shutdown.
20unsafe impl Send for PerCpuLayout {}
21// SAFETY: see Send; access still requires typed per-CPU capabilities.
22unsafe impl Sync for PerCpuLayout {}
23
24impl PerCpuLayout {
25    pub(crate) fn validate(region: PerCpuRegion) -> Result<Self, PerCpuError> {
26        let area_size = crate::template_size();
27        let required_alignment = crate::required_area_alignment()?;
28        if area_size < size_of::<CpuAreaPrefix>() {
29            return Err(PerCpuError::AreaTooSmall {
30                actual: area_size,
31                minimum: size_of::<CpuAreaPrefix>(),
32            });
33        }
34        let runtime_base = region.runtime_base();
35        if !runtime_base.is_multiple_of(required_alignment) {
36            return Err(PerCpuError::MisalignedRuntimeBase {
37                base: runtime_base,
38                alignment: required_alignment,
39            });
40        }
41        if region.area_stride() < area_size {
42            return Err(PerCpuError::StrideTooSmall {
43                stride: region.area_stride(),
44                area_size,
45            });
46        }
47        if !region.area_stride().is_multiple_of(required_alignment) {
48            return Err(PerCpuError::MisalignedStride {
49                stride: region.area_stride(),
50                alignment: required_alignment,
51            });
52        }
53        let template_base = crate::template_base();
54        if !template_base.is_multiple_of(required_alignment) {
55            return Err(PerCpuError::MisalignedTemplateBase {
56                base: template_base,
57                alignment: required_alignment,
58            });
59        }
60        let prefix_address = cpu_local::cpu_area_template_base();
61        if template_base != prefix_address {
62            return Err(PerCpuError::PrefixPlacement {
63                template_base,
64                prefix_address,
65            });
66        }
67        let last_index = region.area_count().get() as usize - 1;
68        let last_offset = region
69            .area_stride()
70            .checked_mul(last_index)
71            .ok_or(PerCpuError::AddressOverflow)?;
72        runtime_base
73            .checked_add(last_offset)
74            .and_then(|base| base.checked_add(area_size))
75            .ok_or(PerCpuError::AddressOverflow)?;
76        Ok(Self {
77            region,
78            template_base,
79            area_size,
80            required_alignment,
81        })
82    }
83
84    /// Returns the platform-owned region geometry.
85    pub const fn region(&self) -> PerCpuRegion {
86        self.region
87    }
88
89    /// Returns CPU zero's runtime base.
90    pub fn runtime_base(&self) -> usize {
91        self.region.runtime_base()
92    }
93
94    /// Returns the byte stride between adjacent areas.
95    pub const fn area_stride(&self) -> usize {
96        self.region.area_stride()
97    }
98
99    /// Returns the number of initialized areas.
100    pub const fn area_count(&self) -> u32 {
101        self.region.area_count().get()
102    }
103
104    /// Returns the initialized byte size within each area.
105    pub const fn area_size(&self) -> usize {
106        self.area_size
107    }
108
109    /// Resolves one CPU area without consulting architecture registers.
110    pub fn area(&self, cpu_index: CpuIndex) -> Result<PerCpuArea, PerCpuError> {
111        if cpu_index.as_u32() >= self.area_count() {
112            return Err(PerCpuError::CpuOutOfRange {
113                cpu_index,
114                area_count: self.area_count(),
115            });
116        }
117        let offset = self
118            .area_stride()
119            .checked_mul(cpu_index.as_usize())
120            .ok_or(PerCpuError::AddressOverflow)?;
121        let runtime_base = self
122            .runtime_base()
123            .checked_add(offset)
124            .ok_or(PerCpuError::AddressOverflow)?;
125        Ok(PerCpuArea::new(cpu_index, runtime_base, self.area_size))
126    }
127
128    pub(crate) const fn template_base(&self) -> usize {
129        self.template_base
130    }
131
132    pub(crate) const fn required_alignment(&self) -> usize {
133        self.required_alignment
134    }
135}
136
137pub(crate) fn installed_layout() -> Result<&'static PerCpuLayout, PerCpuError> {
138    INSTALLED_LAYOUT
139        .get()
140        .ok_or(PerCpuError::LayoutNotInstalled)
141}
142
143pub(crate) fn freeze_initialized_layout(layout: PerCpuLayout) -> &'static PerCpuLayout {
144    assert!(
145        INSTALLED_LAYOUT.get().is_none(),
146        "per-CPU layout publication must occur exactly once after initialization"
147    );
148    INSTALLED_LAYOUT.call_once(|| layout)
149}