Skip to main content

frame_alloc/
allocator.rs

1use crate::page_size::PageSize;
2use core::num::NonZeroUsize;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum AllocError {
6    /// No free memory is available to satisfy the request.
7    OutOfMemory,
8    /// The requested `PageSize` is not managed by this allocator.
9    InvalidPageSize,
10    /// The request is too large for the allocator's configuration.
11    RequestTooLarge,
12}
13
14/// Structured failure reason for [`RegionInit::try_init`](RegionInit::try_init).
15///
16/// These describe the *checkable* preconditions of initialisation. The remaining
17/// unverifiable preconditions live in the `# Safety` contract instead.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[non_exhaustive]
20pub enum InitError {
21    /// init already completed successfully.
22    AlreadyInitialized,
23    /// `phys_base` fails the implementation's alignment requirement.
24    Misaligned {
25        /// The alignment, in bytes, that was required but not met.
26        required: usize,
27    },
28    /// `span_len` is zero, not a base-frame multiple, or overflows the address space.
29    InvalidSpan,
30    /// `usable[index]` is empty, misaligned, out of order, overlapping, or escapes
31    /// the span.
32    InvalidUsable {
33        /// Index of the offending range in the `usable` slice.
34        index: usize,
35    },
36    /// No usable range is large enough to host the allocator's metadata.
37    MetadataWontFit {
38        /// Number of contiguous bytes a single usable range would need.
39        required_bytes: usize,
40    },
41    /// (regioned only) span overlaps the already-initialised region `other`.
42    OverlapsRegion {
43        /// Index of the already-initialised region the span collides with.
44        other: usize,
45    },
46}
47
48impl core::fmt::Display for InitError {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        match self {
51            InitError::AlreadyInitialized => write!(f, "allocator already initialised"),
52            InitError::Misaligned { required } => {
53                write!(f, "base misaligned: requires alignment {required:#x}")
54            }
55            InitError::InvalidSpan => write!(
56                f,
57                "invalid span: zero, not a base-frame multiple, or overflows the address space"
58            ),
59            InitError::InvalidUsable { index } => {
60                write!(f, "invalid usable range at index {index}")
61            }
62            InitError::MetadataWontFit { required_bytes } => write!(
63                f,
64                "metadata won't fit: no usable range holds {required_bytes} contiguous bytes"
65            ),
66            InitError::OverlapsRegion { other } => {
67                write!(f, "span overlaps already-initialised region {other}")
68            }
69        }
70    }
71}
72
73/// Trait for physical frame allocators.
74///
75/// # Contract guarantees
76///
77/// [`allocate_physical`](Self::allocate_physical) is a safe method whose returned
78/// address may later be passed to unsafe deallocation paths. Implementors must
79/// guarantee that every successful allocation:
80///
81/// * returns the base of `count` contiguous frames of size `ps`;
82/// * returns a base address aligned to `ps.bytes()`;
83/// * represents the whole byte range `[base, base + count * ps.bytes())` without
84///   arithmetic overflow;
85/// * transfers exclusive ownership of that range to the caller until it is
86///   returned with [`deallocate_physical`](Self::deallocate_physical); and
87/// * never returns a range that overlaps any still-live allocation.
88///
89/// On `Err`, the allocator must not transfer ownership of any frame.
90///
91/// # Safety
92///
93/// This is an unsafe trait because safe callers and generic wrappers may rely on
94/// successful allocations being real, unique physical frames. Implementors must
95/// ensure that every successful allocation is backed by physical memory managed
96/// by this allocator and exclusively owned until deallocation.
97///
98/// # Errors
99///
100/// Unsupported page sizes, unrepresentable requests, requests too large for the
101/// allocator's configuration, and exhausted memory must be reported with
102/// [`AllocError`] rather than by returning an address that violates the contract.
103pub unsafe trait PhysicalAllocator {
104    /// Allocate `count` contiguous frames of size `ps`.
105    ///
106    /// Returns the **physical** base address on success. When called on an
107    /// uninitialised allocator it returns [`AllocError::OutOfMemory`].
108    ///
109    /// # Errors
110    ///
111    /// See the trait-level `# Errors` documentation.
112    fn allocate_physical(&self, ps: PageSize, count: NonZeroUsize) -> Result<usize, AllocError>;
113
114    /// Return `count` contiguous frames of size `ps` starting at `phys`.
115    ///
116    /// # Safety
117    ///
118    /// `phys` must be the address previously returned by `allocate_physical`
119    ///  with the same `ps` and `count`, and must not be used after this call.
120    unsafe fn deallocate_physical(&self, ps: PageSize, count: NonZeroUsize, phys: usize);
121}
122
123/// A physically-contiguous, base-frame-aligned range of usable RAM.
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub struct PhysRange {
126    /// Physical base address. Must be aligned to the allocator's base frame size.
127    pub base: usize,
128    /// Length in bytes. Must be a non-zero multiple of the base frame size.
129    pub len: usize,
130}
131
132/// Memory-map-aware initialisation interface for physical frame allocators.
133///
134/// [`try_init`](RegionInit::try_init) configures the allocator's metadata over the
135/// whole *span* and frees only the `usable` ranges; holes between them stay
136/// reserved (never handed out).
137///
138/// # Contract guarantees
139///
140/// * **On `Err`, the allocator is untouched** - it remains in the valid empty
141///   state it had before the call, and a corrected retry is permitted.
142/// * **At most one successful call.** A returned `Ok(())` consumes the one-time
143///   initialisation; a returned `Err(_)` does not, so a caller may retry with
144///   corrected arguments after any failure.
145///
146/// # Safety
147///
148/// These preconditions are unverifiable from the arguments and so remain the
149/// caller's responsibility (violating any of them is undefined behaviour, not an
150/// [`InitError`]):
151///
152/// * Backends reach the managed memory through a [`Provenance`](crate::Provenance)
153///   strategy of their own, obtaining pointers for the physical addresses they
154///   are given; the caller supplies no mapping pointer. Each backend's `Provenance`
155///   `# Safety` contract states what it requires of that mapping.
156/// * Each `usable` range's memory must be exclusively owned and not aliased while
157///   registered.
158/// * Must be called single-threaded, and the allocator must be published to
159///   other threads with a happens-before edge (thread spawn, mutex, or a
160///   Release store / Acquire load of a ready flag) before any concurrent use.
161///
162/// # Errors
163///
164/// The *checkable* preconditions are reported rather than assumed. See
165/// [`InitError`] for the full list; in summary `try_init` returns:
166///
167/// * [`InitError::AlreadyInitialized`] if a previous call already succeeded;
168/// * [`InitError::Misaligned`] if `phys_base` is not base-frame aligned (it need
169///   not itself be usable RAM);
170/// * [`InitError::InvalidSpan`] if `span_len` is zero, not a base-frame multiple,
171///   or overflows the address space;
172/// * [`InitError::InvalidUsable`] if any `usable` range is empty, misaligned, out
173///   of order, overlapping, or escapes the span;
174/// * [`InitError::MetadataWontFit`] if no usable range can host the allocator's
175///   in-pool metadata.
176///
177/// An empty `usable` slice is permitted by this interface, but some
178/// implementations may reject it with [`InitError::MetadataWontFit`]: an allocator
179/// that keeps its metadata inside the managed pool needs enough usable RAM to host
180/// that metadata.
181pub unsafe trait RegionInit {
182    /// Configure metadata over `[phys_base, phys_base + span_len)` and mark only
183    /// the `usable` ranges free. Holes stay reserved.
184    ///
185    /// On success the one-time initialisation is consumed; on failure the
186    /// allocator is untouched and the call may be retried. See the trait-level
187    /// documentation for the full contract, the `# Errors` conditions, and the
188    /// `# Safety` preconditions.
189    ///
190    /// # Errors
191    ///
192    /// See the trait-level `# Errors` documentation.
193    ///
194    /// # Safety
195    ///
196    /// See the trait-level safety documentation.
197    unsafe fn try_init(
198        &self,
199        phys_base: usize,
200        span_len: usize,
201        usable: &[PhysRange],
202    ) -> Result<(), InitError>;
203
204    /// Transition a reserved (never-freed) in-span range to free. Repeatable
205    /// after [`try_init`](RegionInit::try_init). `base`/`len` must satisfy the same
206    /// per-range constraints as a `usable` entry.
207    ///
208    /// # Safety
209    ///
210    /// `try_init` must have succeeded first. `[base, base + len)` must be
211    /// base-frame aligned, lie within the span, be currently reserved (not already
212    /// free), exclusively owned, and not aliased while registered.
213    unsafe fn add_usable(&self, base: usize, len: usize);
214
215    /// As [`try_init`](RegionInit::try_init) but panic on error.
216    ///
217    /// # Safety
218    ///
219    /// See the trait-level safety documentation.
220    ///
221    /// # Panics
222    ///
223    /// Panics if [`try_init`](RegionInit::try_init) returns an [`InitError`].
224    unsafe fn init(&self, phys_base: usize, span_len: usize, usable: &[PhysRange]) {
225        // SAFETY: forwarded under the caller's trait-level guarantees.
226        match unsafe { self.try_init(phys_base, span_len, usable) } {
227            Ok(()) => {}
228            Err(e) => panic!("RegionInit::init failed: {e:?}"),
229        }
230    }
231
232    /// Convenience: initialise from a single fully-usable contiguous
233    /// region `[phys_base, phys_base + len)`.
234    ///
235    /// # Errors
236    ///
237    /// See the trait-level `# Errors` documentation.
238    ///
239    /// # Safety
240    ///
241    /// See the trait-level safety documentation; the whole region is `usable`.
242    unsafe fn try_init_region(&self, phys_base: usize, len: usize) -> Result<(), InitError> {
243        let all = [PhysRange {
244            base: phys_base,
245            len,
246        }];
247        // SAFETY: forwarded under the caller's trait-level guarantees; the single
248        // range covers the whole span and trivially satisfies the slice preconditions.
249        unsafe { self.try_init(phys_base, len, &all) }
250    }
251
252    /// As [`try_init_region`](RegionInit::try_init_region) but panic on error.
253    ///
254    /// # Safety
255    ///
256    /// See the trait-level safety documentation; the whole region is `usable`.
257    ///
258    /// # Panics
259    ///
260    /// Panics if initialisation returns an [`InitError`].
261    unsafe fn init_region(&self, phys_base: usize, len: usize) {
262        let all = [PhysRange {
263            base: phys_base,
264            len,
265        }];
266        // SAFETY: forwarded under the caller's trait-level guarantees; the single
267        // range covers the whole span and trivially satisfies the slice preconditions.
268        unsafe { self.init(phys_base, len, &all) };
269    }
270}
271
272/// All three figures are in **bytes**. They are diagnostics - utilisation
273/// reporting, fragmentation tracking, tests - not a basis for allocation
274/// decisions: each is **non-linearizable** under concurrent use, so a value may be
275/// stale the instant it is returned. Gated behind the `stats` feature.
276#[cfg(any(feature = "stats", test))]
277pub trait AllocatorStats {
278    /// Total managed capacity in bytes - what the allocator can hand out when
279    /// fully free. Counts usable frames only; excludes any metadata the allocator
280    /// reserves inside its own pool.
281    fn total_bytes(&self) -> usize;
282
283    /// Currently-free bytes.
284    fn free_bytes(&self) -> usize;
285
286    /// Largest single allocation, in bytes, that can currently succeed.
287    fn largest_free_bytes(&self) -> usize;
288}