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