1#![no_std]
9
10#[allow(unused_imports)]
11#[macro_use]
12extern crate log;
13extern crate alloc;
14
15use core::{
16 alloc::Layout,
17 fmt,
18 ptr::NonNull,
19 sync::atomic::{AtomicBool, Ordering},
20};
21
22use strum::{IntoStaticStr, VariantArray};
23
24const PAGE_SIZE: usize = 0x1000;
25#[cfg(any(tlsf, buddy_slab, test))]
26const MIN_RECLAIM_PAGES: usize = 16;
27#[cfg(any(tlsf, buddy_slab, test))]
28const MAX_RECLAIM_ATTEMPTS: usize = 4;
29
30pub type PageReclaimFn = fn(num_pages: usize) -> usize;
33
34static PAGE_RECLAIM_FN: ax_sync::SpinLock<Option<PageReclaimFn>> = ax_sync::SpinLock::new(None);
35static PAGE_RECLAIM_ACTIVE: AtomicBool = AtomicBool::new(false);
36
37struct PageReclaimLease;
38
39impl PageReclaimLease {
40 fn try_acquire() -> Option<Self> {
41 PAGE_RECLAIM_ACTIVE
42 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
43 .ok()
44 .map(|_| Self)
45 }
46}
47
48impl Drop for PageReclaimLease {
49 fn drop(&mut self) {
50 PAGE_RECLAIM_ACTIVE.store(false, Ordering::Release);
51 }
52}
53
54pub fn register_page_reclaim_fn(f: PageReclaimFn) {
61 *PAGE_RECLAIM_FN.lock_irqsave() = Some(f);
62}
63
64pub fn try_page_reclaim(num_pages: usize) -> usize {
71 let Some(_lease) = PageReclaimLease::try_acquire() else {
72 return 0;
73 };
74 let reclaim_fn = { *PAGE_RECLAIM_FN.lock_irqsave() };
75 reclaim_fn.map_or(0, |f| f(num_pages))
76}
77
78#[cfg(any(tlsf, buddy_slab, test))]
79pub(crate) fn retry_after_page_reclaim<T>(
80 target_pages: usize,
81 mut attempt: impl FnMut() -> AllocResult<T>,
82 mut reclaim: impl FnMut(usize) -> usize,
83) -> AllocResult<T> {
84 match attempt() {
85 Ok(value) => return Ok(value),
86 Err(AllocError::NoMemory) => {}
87 Err(error) => return Err(error),
88 }
89
90 let target_pages = target_pages.max(MIN_RECLAIM_PAGES);
91 for _ in 0..MAX_RECLAIM_ATTEMPTS {
92 let reclaimed = reclaim(target_pages);
93
94 match attempt() {
97 Ok(value) => return Ok(value),
98 Err(AllocError::NoMemory) if reclaimed != 0 => {}
99 Err(error) => return Err(error),
100 }
101 }
102 Err(AllocError::NoMemory)
103}
104
105#[cfg(any(tlsf, buddy_slab))]
106pub(crate) fn retry_after_registered_reclaim<T>(
107 target_pages: usize,
108 attempt: impl FnMut() -> AllocResult<T>,
109) -> AllocResult<T> {
110 retry_after_page_reclaim(target_pages, attempt, try_page_reclaim)
111}
112
113#[cfg(any(tlsf, buddy_slab))]
114pub(crate) const fn layout_reclaim_pages(layout: Layout) -> usize {
115 layout.size().div_ceil(PAGE_SIZE)
116}
117
118mod page;
119pub use page::GlobalPage;
120
121#[cfg(feature = "tracking")]
123pub mod tracking;
124
125#[repr(u8)]
127#[derive(Debug, Clone, Copy, PartialEq, Eq, VariantArray, IntoStaticStr)]
128pub enum UsageKind {
129 RustHeap,
131 VirtMem,
133 PageCache,
135 PageTable,
137 TaskStack,
139 Dma,
141 Global,
143}
144
145#[derive(Clone, Copy)]
147pub struct Usages([usize; UsageKind::VARIANTS.len()]);
148
149impl Usages {
150 const fn new() -> Self {
151 Self([0; UsageKind::VARIANTS.len()])
152 }
153
154 #[allow(dead_code)]
155 fn alloc(&mut self, kind: UsageKind, size: usize) {
156 self.0[kind as usize] += size;
157 }
158
159 #[allow(dead_code)]
160 fn dealloc(&mut self, kind: UsageKind, size: usize) {
161 self.0[kind as usize] -= size;
162 }
163
164 pub fn get(&self, kind: UsageKind) -> usize {
166 self.0[kind as usize]
167 }
168}
169
170impl fmt::Debug for Usages {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 let mut d = f.debug_struct("UsageStats");
173 for &kind in UsageKind::VARIANTS {
174 d.field(kind.into(), &self.0[kind as usize]);
175 }
176 d.finish()
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
182pub enum AllocError {
183 #[error("invalid allocation parameter")]
185 InvalidParam,
186 #[error("allocator is already initialized")]
188 AlreadyInitialized,
189 #[error("memory region overlaps an existing allocation region")]
191 MemoryOverlap,
192 #[error("not enough memory")]
194 NoMemory,
195 #[error("memory was not allocated by this allocator")]
197 NotAllocated,
198 #[error("allocator is not initialized")]
200 NotInitialized,
201 #[error("allocation was not found")]
203 NotFound,
204}
205
206pub type AllocResult<T = ()> = Result<T, AllocError>;
208
209pub trait AllocatorOps {
211 fn name(&self) -> &'static str;
213
214 fn init(&self, start_vaddr: usize, size: usize) -> AllocResult;
216
217 fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult;
219
220 fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>>;
222
223 fn dealloc(&self, pos: NonNull<u8>, layout: Layout);
225
226 fn alloc_pages(&self, num_pages: usize, align: usize, kind: UsageKind) -> AllocResult<usize>;
231
232 fn alloc_dma32_pages(
237 &self,
238 num_pages: usize,
239 align: usize,
240 kind: UsageKind,
241 ) -> AllocResult<usize>;
242
243 fn alloc_pages_at(
248 &self,
249 start: usize,
250 num_pages: usize,
251 align: usize,
252 kind: UsageKind,
253 ) -> AllocResult<usize>;
254
255 fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind);
257
258 fn used_bytes(&self) -> usize;
260
261 fn available_bytes(&self) -> usize;
263
264 fn used_pages(&self) -> usize;
266
267 fn available_pages(&self) -> usize;
269
270 fn usages(&self) -> Usages;
272}
273
274#[cfg(buddy_slab)]
276mod buddy_slab;
277#[cfg(not(any(tlsf, buddy_slab)))]
278mod stub_impl;
279#[cfg(tlsf)]
280mod tlsf_impl;
281
282#[cfg(buddy_slab)]
283use buddy_slab as imp;
284pub use imp::{
285 DefaultByteAllocator, GlobalAllocator, global_add_memory, global_init, init_percpu_slab,
286};
287#[cfg(not(any(tlsf, buddy_slab)))]
288use stub_impl as imp;
289#[cfg(tlsf)]
290use tlsf_impl as imp;
291
292pub fn global_allocator() -> &'static GlobalAllocator {
294 imp::global_allocator()
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn no_memory_retries_after_reclaim_progress() {
303 let mut attempts = 0;
304 let mut reclaims = 0;
305 let result = retry_after_page_reclaim(
306 1,
307 || {
308 attempts += 1;
309 (attempts == 2).then_some(42).ok_or(AllocError::NoMemory)
310 },
311 |target| {
312 reclaims += 1;
313 assert_eq!(target, MIN_RECLAIM_PAGES);
314 1
315 },
316 );
317
318 assert_eq!(result, Ok(42));
319 assert_eq!(attempts, 2);
320 assert_eq!(reclaims, 1);
321 }
322
323 #[test]
324 fn zero_reclaim_progress_gets_one_concurrent_retry() {
325 let mut attempts = 0;
326 let mut reclaims = 0;
327 let result = retry_after_page_reclaim::<()>(
328 32,
329 || {
330 attempts += 1;
331 Err(AllocError::NoMemory)
332 },
333 |target| {
334 reclaims += 1;
335 assert_eq!(target, 32);
336 0
337 },
338 );
339
340 assert_eq!(result, Err(AllocError::NoMemory));
341 assert_eq!(attempts, 2);
342 assert_eq!(reclaims, 1);
343 }
344
345 #[test]
346 fn non_memory_error_does_not_enter_reclaim() {
347 let mut reclaims = 0;
348 let result = retry_after_page_reclaim::<()>(
349 1,
350 || Err(AllocError::InvalidParam),
351 |_| {
352 reclaims += 1;
353 1
354 },
355 );
356
357 assert_eq!(result, Err(AllocError::InvalidParam));
358 assert_eq!(reclaims, 0);
359 }
360
361 #[test]
362 fn reclaim_progress_has_a_bounded_retry_budget() {
363 let mut attempts = 0;
364 let mut reclaims = 0;
365 let result = retry_after_page_reclaim::<()>(
366 usize::MAX,
367 || {
368 attempts += 1;
369 Err(AllocError::NoMemory)
370 },
371 |_| {
372 reclaims += 1;
373 1
374 },
375 );
376
377 assert_eq!(result, Err(AllocError::NoMemory));
378 assert_eq!(attempts, MAX_RECLAIM_ATTEMPTS + 1);
379 assert_eq!(reclaims, MAX_RECLAIM_ATTEMPTS);
380 }
381}