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}