Skip to main content

ax_alloc/
tlsf_impl.rs

1//! TLSF memory allocator implementation using the `rlsf` crate.
2
3use core::{
4    alloc::{GlobalAlloc, Layout},
5    ptr::NonNull,
6};
7
8use ax_sync::SpinLock;
9use rlsf::Tlsf;
10
11use super::{AllocResult, AllocatorOps, UsageKind, Usages};
12
13/// The global allocator instance for TLSF mode.
14#[cfg_attr(
15    all(any(target_os = "none", feature = "global-allocator"), not(test)),
16    global_allocator
17)]
18static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new();
19
20const PAGE_SIZE: usize = 0x1000;
21
22/// The default byte allocator for TLSF mode.
23pub type DefaultByteAllocator = Tlsf<'static, u32, u32, 28, 32>;
24
25struct TlsfInfo {
26    tlsf: Tlsf<'static, u32, u32, 28, 32>,
27    total_bytes: usize,
28    used_bytes: usize,
29}
30
31impl TlsfInfo {
32    const fn new() -> Self {
33        Self {
34            tlsf: Tlsf::new(),
35            total_bytes: 0,
36            used_bytes: 0,
37        }
38    }
39}
40
41/// The global allocator used by ArceOS when TLSF is enabled.
42pub struct GlobalAllocator {
43    inner: SpinLock<TlsfInfo>,
44    usages: SpinLock<Usages>,
45}
46
47impl Default for GlobalAllocator {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl GlobalAllocator {
54    /// Creates an empty [`GlobalAllocator`].
55    pub const fn new() -> Self {
56        Self {
57            inner: SpinLock::new(TlsfInfo::new()),
58            usages: SpinLock::new(Usages::new()),
59        }
60    }
61
62    /// Returns the name of the allocator.
63    pub const fn name(&self) -> &'static str {
64        "TLSF"
65    }
66
67    /// Initializes the allocator with the given region.
68    pub fn init(&self, start_vaddr: usize, size: usize) -> AllocResult {
69        let mut inner = self.inner.lock_irqsave();
70        unsafe {
71            let pool = core::slice::from_raw_parts_mut(start_vaddr as *mut u8, size);
72            inner
73                .tlsf
74                .insert_free_block_ptr(NonNull::new(pool).unwrap())
75                .unwrap();
76        }
77        inner.total_bytes = size;
78        Ok(())
79    }
80
81    /// Add the given region to the allocator.
82    pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult {
83        let mut inner = self.inner.lock_irqsave();
84        unsafe {
85            let pool = core::slice::from_raw_parts_mut(start_vaddr as *mut u8, size);
86            inner
87                .tlsf
88                .insert_free_block_ptr(NonNull::new(pool).unwrap())
89                .ok_or(crate::AllocError::InvalidParam)?;
90        }
91        inner.total_bytes += size;
92        Ok(())
93    }
94
95    /// Allocate arbitrary number of bytes.
96    pub fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>> {
97        let ptr = self
98            .inner
99            .lock_irqsave()
100            .tlsf
101            .allocate(layout)
102            .ok_or(crate::AllocError::NoMemory)?;
103        self.inner.lock_irqsave().used_bytes += layout.size();
104        self.usages
105            .lock_irqsave()
106            .alloc(UsageKind::RustHeap, layout.size());
107        Ok(ptr)
108    }
109
110    /// Gives back the allocated region.
111    pub fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
112        unsafe {
113            self.inner
114                .lock_irqsave()
115                .tlsf
116                .deallocate(pos, layout.align());
117        }
118        self.inner.lock_irqsave().used_bytes -= layout.size();
119        self.usages
120            .lock_irqsave()
121            .dealloc(UsageKind::RustHeap, layout.size());
122    }
123
124    /// Allocates contiguous pages by allocating page-aligned bytes from TLSF.
125    pub fn alloc_pages(
126        &self,
127        num_pages: usize,
128        alignment: usize,
129        kind: UsageKind,
130    ) -> AllocResult<usize> {
131        let size = num_pages * PAGE_SIZE;
132        let align = alignment.max(PAGE_SIZE);
133        let layout =
134            Layout::from_size_align(size, align).map_err(|_| crate::AllocError::InvalidParam)?;
135        let ptr = self
136            .inner
137            .lock_irqsave()
138            .tlsf
139            .allocate(layout)
140            .ok_or(crate::AllocError::NoMemory)?;
141        self.inner.lock_irqsave().used_bytes += size;
142        if !matches!(kind, UsageKind::RustHeap) {
143            self.usages.lock_irqsave().alloc(kind, size);
144        }
145        Ok(ptr.as_ptr() as usize)
146    }
147
148    /// Allocates contiguous low-memory pages (physical address < 4 GiB).
149    pub fn alloc_dma32_pages(
150        &self,
151        _num_pages: usize,
152        _alignment: usize,
153        _kind: UsageKind,
154    ) -> AllocResult<usize> {
155        unimplemented!("TLSF allocator does not support alloc_dma32_pages")
156    }
157
158    /// Allocates contiguous pages starting from the given address.
159    pub fn alloc_pages_at(
160        &self,
161        _start: usize,
162        _num_pages: usize,
163        _alignment: usize,
164        _kind: UsageKind,
165    ) -> AllocResult<usize> {
166        unimplemented!("TLSF allocator does not support alloc_pages_at")
167    }
168
169    /// Gives back the allocated pages.
170    pub fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) {
171        let size = num_pages * PAGE_SIZE;
172        let ptr = NonNull::new(pos as *mut u8).expect("dealloc_pages null ptr");
173        unsafe {
174            self.inner.lock_irqsave().tlsf.deallocate(ptr, PAGE_SIZE);
175        }
176        self.inner.lock_irqsave().used_bytes -= size;
177        self.usages.lock_irqsave().dealloc(kind, size);
178    }
179
180    /// Returns the number of allocated bytes.
181    pub fn used_bytes(&self) -> usize {
182        self.inner.lock_irqsave().used_bytes
183    }
184
185    /// Returns the number of available bytes.
186    pub fn available_bytes(&self) -> usize {
187        let inner = self.inner.lock_irqsave();
188        inner.total_bytes.saturating_sub(inner.used_bytes)
189    }
190
191    /// Returns the number of allocated pages.
192    pub fn used_pages(&self) -> usize {
193        self.used_bytes() / PAGE_SIZE
194    }
195
196    /// Returns the number of available pages.
197    pub fn available_pages(&self) -> usize {
198        self.available_bytes() / PAGE_SIZE
199    }
200
201    /// Returns the usage statistics.
202    pub fn usages(&self) -> Usages {
203        *self.usages.lock_irqsave()
204    }
205}
206
207impl AllocatorOps for GlobalAllocator {
208    fn name(&self) -> &'static str {
209        GlobalAllocator::name(self)
210    }
211
212    fn init(&self, start_vaddr: usize, size: usize) -> AllocResult {
213        GlobalAllocator::init(self, start_vaddr, size)
214    }
215
216    fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult {
217        GlobalAllocator::add_memory(self, start_vaddr, size)
218    }
219
220    fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>> {
221        GlobalAllocator::alloc(self, layout)
222    }
223
224    fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
225        GlobalAllocator::dealloc(self, pos, layout)
226    }
227
228    fn alloc_pages(
229        &self,
230        num_pages: usize,
231        alignment: usize,
232        kind: UsageKind,
233    ) -> AllocResult<usize> {
234        GlobalAllocator::alloc_pages(self, num_pages, alignment, kind)
235    }
236
237    fn alloc_dma32_pages(
238        &self,
239        num_pages: usize,
240        alignment: usize,
241        kind: UsageKind,
242    ) -> AllocResult<usize> {
243        GlobalAllocator::alloc_dma32_pages(self, num_pages, alignment, kind)
244    }
245
246    fn alloc_pages_at(
247        &self,
248        start: usize,
249        num_pages: usize,
250        alignment: usize,
251        kind: UsageKind,
252    ) -> AllocResult<usize> {
253        GlobalAllocator::alloc_pages_at(self, start, num_pages, alignment, kind)
254    }
255
256    fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) {
257        GlobalAllocator::dealloc_pages(self, pos, num_pages, kind)
258    }
259
260    fn used_bytes(&self) -> usize {
261        GlobalAllocator::used_bytes(self)
262    }
263
264    fn available_bytes(&self) -> usize {
265        GlobalAllocator::available_bytes(self)
266    }
267
268    fn used_pages(&self) -> usize {
269        GlobalAllocator::used_pages(self)
270    }
271
272    fn available_pages(&self) -> usize {
273        GlobalAllocator::available_pages(self)
274    }
275
276    fn usages(&self) -> Usages {
277        GlobalAllocator::usages(self)
278    }
279}
280
281/// Returns the reference to the global allocator.
282pub fn global_allocator() -> &'static GlobalAllocator {
283    &GLOBAL_ALLOCATOR
284}
285
286/// Initializes per-CPU allocator state.
287///
288/// TLSF does not use per-CPU slabs, so this is intentionally a no-op.
289pub fn init_percpu_slab(_cpu_id: usize) {}
290
291/// Initializes the global allocator with the given memory region.
292pub fn global_init(start_vaddr: usize, size: usize) -> AllocResult {
293    debug!(
294        "initialize global allocator at: [{:#x}, {:#x})",
295        start_vaddr,
296        start_vaddr + size
297    );
298    GLOBAL_ALLOCATOR.init(start_vaddr, size)
299}
300
301/// Add the given memory region to the global allocator.
302pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult {
303    debug!(
304        "add a memory region to global allocator: [{:#x}, {:#x})",
305        start_vaddr,
306        start_vaddr + size
307    );
308    GLOBAL_ALLOCATOR.add_memory(start_vaddr, size)
309}
310
311unsafe impl GlobalAlloc for GlobalAllocator {
312    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
313        let inner = move || {
314            if let Ok(ptr) = GlobalAllocator::alloc(self, layout) {
315                ptr.as_ptr()
316            } else {
317                alloc::alloc::handle_alloc_error(layout)
318            }
319        };
320
321        #[cfg(feature = "tracking")]
322        {
323            crate::tracking::with_state(|state| match state {
324                None => inner(),
325                Some(state) => {
326                    let ptr = inner();
327                    let generation = state.generation;
328                    state.generation += 1;
329                    state.map.insert(
330                        ptr as usize,
331                        crate::tracking::AllocationInfo {
332                            layout,
333                            backtrace: axbacktrace::Backtrace::capture(),
334                            generation,
335                        },
336                    );
337                    ptr
338                }
339            })
340        }
341
342        #[cfg(not(feature = "tracking"))]
343        inner()
344    }
345
346    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
347        let ptr = NonNull::new(ptr).expect("dealloc null ptr");
348        let inner = || GlobalAllocator::dealloc(self, ptr, layout);
349
350        #[cfg(feature = "tracking")]
351        crate::tracking::with_state(|state| match state {
352            None => inner(),
353            Some(state) => {
354                let address = ptr.as_ptr() as usize;
355                state.map.remove(&address);
356                inner()
357            }
358        });
359
360        #[cfg(not(feature = "tracking"))]
361        inner();
362    }
363}