Skip to main content

ax_alloc/
buddy_slab.rs

1//! Memory allocator implementation backed by `buddy-slab-allocator`.
2
3use core::{
4    alloc::{GlobalAlloc, Layout},
5    ptr::NonNull,
6    slice,
7};
8
9use ax_kspin::SpinNoIrq;
10use buddy_slab_allocator::{
11    GlobalAllocator as InnerAllocator, SizeClass, SlabAllocResult, SlabAllocator,
12    SlabDeallocResult, SlabPoolTrait, SlabTrait,
13    eii::{slab_pool_impl, virt_to_phys_impl},
14};
15
16use super::{AllocResult, AllocatorOps, UsageKind, Usages};
17
18/// The global allocator instance for buddy-slab mode.
19#[cfg_attr(all(target_os = "none", not(test)), global_allocator)]
20static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new();
21
22/// The default byte allocator for buddy-slab mode.
23pub type DefaultByteAllocator = buddy_slab_allocator::SlabAllocator<PAGE_SIZE>;
24
25const PAGE_SIZE: usize = 0x1000;
26
27#[ax_percpu::def_percpu]
28static PERCPU_SLAB: PercpuSlab<PAGE_SIZE> = PercpuSlab::new_uninit();
29
30static SLAB_POOL: SlabPool = SlabPool;
31
32struct PercpuSlab<const PAGE_SIZE: usize = 0x1000> {
33    cpu_id: Option<u16>,
34    inner: SpinNoIrq<SlabAllocator<PAGE_SIZE>>,
35}
36
37impl<const PAGE_SIZE: usize> PercpuSlab<PAGE_SIZE> {
38    const fn new_uninit() -> Self {
39        Self {
40            cpu_id: None,
41            inner: SpinNoIrq::new(SlabAllocator::new()),
42        }
43    }
44
45    fn init(&mut self, cpu_id: usize) {
46        let cpu_id = u16::try_from(cpu_id).expect("CPU id exceeds per-CPU slab range");
47        assert!(
48            self.cpu_id.is_none(),
49            "per-CPU slab is already initialized on this CPU",
50        );
51        self.cpu_id = Some(cpu_id);
52        *self.inner.lock() = SlabAllocator::new();
53    }
54
55    fn cpu_id_checked(&self) -> u16 {
56        self.cpu_id
57            .expect("per-CPU slab is not initialized on this CPU")
58    }
59}
60
61impl<const PAGE_SIZE: usize> SlabTrait for PercpuSlab<PAGE_SIZE> {
62    fn cpu_id(&self) -> usize {
63        self.cpu_id_checked() as usize
64    }
65
66    fn page_size(&self) -> usize {
67        PAGE_SIZE
68    }
69
70    fn alloc(&self, layout: Layout) -> buddy_slab_allocator::AllocResult<SlabAllocResult> {
71        self.inner.lock().alloc(layout)
72    }
73
74    fn add_slab(&self, size_class: SizeClass, base: usize, bytes: usize) {
75        self.inner
76            .lock()
77            .add_slab(size_class, base, bytes, self.cpu_id_checked());
78    }
79
80    fn dealloc_local(&self, ptr: NonNull<u8>, layout: Layout) -> SlabDeallocResult {
81        self.inner.lock().dealloc(ptr, layout)
82    }
83}
84
85fn current_percpu_slab() -> &'static PercpuSlab<PAGE_SIZE> {
86    // Safety: the outer allocator lock disables local IRQs/preemption before
87    // upstream buddy-slab-allocator calls this hook.
88    unsafe { PERCPU_SLAB.current_ref_raw() }
89}
90
91fn remote_percpu_slab(cpu_idx: usize) -> &'static PercpuSlab<PAGE_SIZE> {
92    // Safety: the owner CPU id comes from slab metadata and references a valid
93    // per-CPU slab that was initialized during CPU bring-up.
94    unsafe { PERCPU_SLAB.remote_ref_raw(cpu_idx) }
95}
96
97struct SlabPool;
98
99impl SlabPoolTrait for SlabPool {
100    fn current_slab(&self) -> &dyn SlabTrait {
101        current_percpu_slab()
102    }
103
104    fn owner_slab(&self, cpu_idx: usize) -> &dyn SlabTrait {
105        remote_percpu_slab(cpu_idx)
106    }
107}
108
109#[slab_pool_impl]
110fn slab_pool() -> &'static dyn SlabPoolTrait {
111    &SLAB_POOL
112}
113
114#[virt_to_phys_impl]
115fn virt_to_phys(vaddr: usize) -> usize {
116    ax_plat::mem::virt_to_phys(vaddr.into()).as_usize()
117}
118
119/// The global allocator used by ArceOS when `buddy-slab` is enabled.
120pub struct GlobalAllocator {
121    inner: SpinNoIrq<InnerAllocator<PAGE_SIZE>>,
122    usages: SpinNoIrq<Usages>,
123}
124
125impl Default for GlobalAllocator {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131impl GlobalAllocator {
132    /// Creates an empty [`GlobalAllocator`].
133    pub const fn new() -> Self {
134        Self {
135            inner: SpinNoIrq::new(InnerAllocator::<PAGE_SIZE>::new()),
136            usages: SpinNoIrq::new(Usages::new()),
137        }
138    }
139
140    /// Returns the name of the allocator.
141    pub const fn name(&self) -> &'static str {
142        "buddy-slab-allocator"
143    }
144
145    /// Initializes the allocator with the given region.
146    pub fn init(&self, start_vaddr: usize, size: usize) -> AllocResult {
147        info!(
148            "Initialize global memory allocator, start_vaddr: {:#x}, size: {:#x}",
149            start_vaddr, size
150        );
151        let region = unsafe { slice::from_raw_parts_mut(start_vaddr as *mut u8, size) };
152        unsafe { self.inner.lock().init(region) }.map_err(Into::into)
153    }
154
155    /// Add the given region to the allocator.
156    pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult {
157        info!(
158            "Add memory region, start_vaddr: {:#x}, size: {:#x}",
159            start_vaddr, size
160        );
161        let region = unsafe { slice::from_raw_parts_mut(start_vaddr as *mut u8, size) };
162        unsafe { self.inner.lock().add_region(region) }.map_err(Into::into)
163    }
164
165    /// Allocate arbitrary number of bytes. Returns the left bound of the
166    /// allocated region.
167    pub fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>> {
168        let result = self
169            .inner
170            .lock()
171            .alloc(layout)
172            .map_err(crate::AllocError::from);
173        if result.is_ok() {
174            self.usages.lock().alloc(UsageKind::RustHeap, layout.size());
175        }
176        result
177    }
178
179    /// Gives back the allocated region to the byte allocator.
180    pub fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
181        self.usages
182            .lock()
183            .dealloc(UsageKind::RustHeap, layout.size());
184        unsafe { self.inner.lock().dealloc(pos, layout) };
185    }
186
187    /// Allocates contiguous pages.
188    pub fn alloc_pages(
189        &self,
190        num_pages: usize,
191        alignment: usize,
192        kind: UsageKind,
193    ) -> AllocResult<usize> {
194        let result = self
195            .inner
196            .lock()
197            .alloc_pages(num_pages, alignment)
198            .map_err(crate::AllocError::from);
199        if result.is_ok() {
200            self.usages.lock().alloc(kind, num_pages * PAGE_SIZE);
201        }
202        result
203    }
204
205    /// Allocates contiguous low-memory pages (physical address < 4 GiB).
206    pub fn alloc_dma32_pages(
207        &self,
208        num_pages: usize,
209        alignment: usize,
210        kind: UsageKind,
211    ) -> AllocResult<usize> {
212        let result = self
213            .inner
214            .lock()
215            .alloc_pages_lowmem(num_pages, alignment)
216            .map_err(crate::AllocError::from);
217        if result.is_ok() {
218            self.usages.lock().alloc(kind, num_pages * PAGE_SIZE);
219        }
220        result
221    }
222
223    /// Allocates contiguous pages starting from the given address.
224    pub fn alloc_pages_at(
225        &self,
226        _start: usize,
227        _num_pages: usize,
228        _alignment: usize,
229        _kind: UsageKind,
230    ) -> AllocResult<usize> {
231        unimplemented!("buddy-slab allocator does not support alloc_pages_at")
232    }
233
234    /// Gives back the allocated pages starts from `pos` to the page allocator.
235    pub fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) {
236        self.usages.lock().dealloc(kind, num_pages * PAGE_SIZE);
237        self.inner.lock().dealloc_pages(pos, num_pages);
238    }
239
240    /// Returns the number of allocated bytes in the allocator backend.
241    pub fn used_bytes(&self) -> usize {
242        self.inner.lock().allocated_bytes()
243    }
244
245    /// Returns the number of available bytes in the allocator backend.
246    pub fn available_bytes(&self) -> usize {
247        let inner = self.inner.lock();
248        inner
249            .managed_bytes()
250            .saturating_sub(inner.allocated_bytes())
251    }
252
253    /// Returns the number of allocated pages in the allocator backend.
254    pub fn used_pages(&self) -> usize {
255        self.used_bytes() / PAGE_SIZE
256    }
257
258    /// Returns the number of available pages in the allocator backend.
259    pub fn available_pages(&self) -> usize {
260        self.available_bytes() / PAGE_SIZE
261    }
262
263    /// Returns the usage statistics of the allocator.
264    pub fn usages(&self) -> Usages {
265        *self.usages.lock()
266    }
267}
268
269impl AllocatorOps for GlobalAllocator {
270    fn name(&self) -> &'static str {
271        GlobalAllocator::name(self)
272    }
273
274    fn init(&self, start_vaddr: usize, size: usize) -> AllocResult {
275        GlobalAllocator::init(self, start_vaddr, size)
276    }
277
278    fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult {
279        GlobalAllocator::add_memory(self, start_vaddr, size)
280    }
281
282    fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>> {
283        GlobalAllocator::alloc(self, layout)
284    }
285
286    fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
287        GlobalAllocator::dealloc(self, pos, layout)
288    }
289
290    fn alloc_pages(
291        &self,
292        num_pages: usize,
293        alignment: usize,
294        kind: UsageKind,
295    ) -> AllocResult<usize> {
296        GlobalAllocator::alloc_pages(self, num_pages, alignment, kind)
297    }
298
299    fn alloc_dma32_pages(
300        &self,
301        num_pages: usize,
302        alignment: usize,
303        kind: UsageKind,
304    ) -> AllocResult<usize> {
305        GlobalAllocator::alloc_dma32_pages(self, num_pages, alignment, kind)
306    }
307
308    fn alloc_pages_at(
309        &self,
310        start: usize,
311        num_pages: usize,
312        alignment: usize,
313        kind: UsageKind,
314    ) -> AllocResult<usize> {
315        GlobalAllocator::alloc_pages_at(self, start, num_pages, alignment, kind)
316    }
317
318    fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) {
319        GlobalAllocator::dealloc_pages(self, pos, num_pages, kind)
320    }
321
322    fn used_bytes(&self) -> usize {
323        GlobalAllocator::used_bytes(self)
324    }
325
326    fn available_bytes(&self) -> usize {
327        GlobalAllocator::available_bytes(self)
328    }
329
330    fn used_pages(&self) -> usize {
331        GlobalAllocator::used_pages(self)
332    }
333
334    fn available_pages(&self) -> usize {
335        GlobalAllocator::available_pages(self)
336    }
337
338    fn usages(&self) -> Usages {
339        GlobalAllocator::usages(self)
340    }
341}
342
343/// Returns the reference to the global allocator.
344pub fn global_allocator() -> &'static GlobalAllocator {
345    &GLOBAL_ALLOCATOR
346}
347
348/// Initializes the per-CPU slab for the current CPU.
349pub fn init_percpu_slab(cpu_id: usize) {
350    PERCPU_SLAB.with_current(|slab| slab.init(cpu_id));
351}
352
353/// Initializes the global allocator with the given memory region.
354pub fn global_init(start_vaddr: usize, size: usize) -> AllocResult {
355    debug!(
356        "initialize global allocator at: [{:#x}, {:#x})",
357        start_vaddr,
358        start_vaddr + size
359    );
360    GLOBAL_ALLOCATOR.init(start_vaddr, size)?;
361    info!("global allocator initialized");
362    Ok(())
363}
364
365/// Add the given memory region to the global allocator.
366pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult {
367    debug!(
368        "add a memory region to global allocator: [{:#x}, {:#x})",
369        start_vaddr,
370        start_vaddr + size
371    );
372    GLOBAL_ALLOCATOR.add_memory(start_vaddr, size)
373}
374
375unsafe impl GlobalAlloc for GlobalAllocator {
376    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
377        let inner = move || {
378            if let Ok(ptr) = GlobalAllocator::alloc(self, layout) {
379                ptr.as_ptr()
380            } else {
381                alloc::alloc::handle_alloc_error(layout)
382            }
383        };
384
385        #[cfg(feature = "tracking")]
386        {
387            crate::tracking::with_state(|state| match state {
388                None => inner(),
389                Some(state) => {
390                    let ptr = inner();
391                    let generation = state.generation;
392                    state.generation += 1;
393                    state.map.insert(
394                        ptr as usize,
395                        crate::tracking::AllocationInfo {
396                            layout,
397                            backtrace: axbacktrace::Backtrace::capture(),
398                            generation,
399                        },
400                    );
401                    ptr
402                }
403            })
404        }
405
406        #[cfg(not(feature = "tracking"))]
407        inner()
408    }
409
410    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
411        let ptr = NonNull::new(ptr).expect("dealloc null ptr");
412        let inner = || GlobalAllocator::dealloc(self, ptr, layout);
413
414        #[cfg(feature = "tracking")]
415        crate::tracking::with_state(|state| match state {
416            None => inner(),
417            Some(state) => {
418                let address = ptr.as_ptr() as usize;
419                state.map.remove(&address);
420                inner()
421            }
422        });
423
424        #[cfg(not(feature = "tracking"))]
425        inner();
426    }
427}
428
429impl From<buddy_slab_allocator::AllocError> for super::AllocError {
430    fn from(value: buddy_slab_allocator::AllocError) -> Self {
431        match value {
432            buddy_slab_allocator::AllocError::InvalidParam => Self::InvalidParam,
433            buddy_slab_allocator::AllocError::AlreadyInitialized => Self::AlreadyInitialized,
434            buddy_slab_allocator::AllocError::MemoryOverlap => Self::MemoryOverlap,
435            buddy_slab_allocator::AllocError::NoMemory => Self::NoMemory,
436            buddy_slab_allocator::AllocError::NotAllocated => Self::NotAllocated,
437            buddy_slab_allocator::AllocError::NotInitialized => Self::NotInitialized,
438            buddy_slab_allocator::AllocError::NotFound => Self::NotFound,
439        }
440    }
441}