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(
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: SpinNoIrq<TlsfInfo>,
44    usages: SpinNoIrq<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: SpinNoIrq::new(TlsfInfo::new()),
58            usages: SpinNoIrq::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();
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();
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()
100            .tlsf
101            .allocate(layout)
102            .ok_or(crate::AllocError::NoMemory)?;
103        self.inner.lock().used_bytes += layout.size();
104        self.usages.lock().alloc(UsageKind::RustHeap, layout.size());
105        Ok(ptr)
106    }
107
108    /// Gives back the allocated region.
109    pub fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
110        unsafe {
111            self.inner.lock().tlsf.deallocate(pos, layout.align());
112        }
113        self.inner.lock().used_bytes -= layout.size();
114        self.usages
115            .lock()
116            .dealloc(UsageKind::RustHeap, layout.size());
117    }
118
119    /// Allocates contiguous pages by allocating page-aligned bytes from TLSF.
120    pub fn alloc_pages(
121        &self,
122        num_pages: usize,
123        alignment: usize,
124        kind: UsageKind,
125    ) -> AllocResult<usize> {
126        let size = num_pages * PAGE_SIZE;
127        let align = alignment.max(PAGE_SIZE);
128        let layout =
129            Layout::from_size_align(size, align).map_err(|_| crate::AllocError::InvalidParam)?;
130        let ptr = self
131            .inner
132            .lock()
133            .tlsf
134            .allocate(layout)
135            .ok_or(crate::AllocError::NoMemory)?;
136        self.inner.lock().used_bytes += size;
137        if !matches!(kind, UsageKind::RustHeap) {
138            self.usages.lock().alloc(kind, size);
139        }
140        Ok(ptr.as_ptr() as usize)
141    }
142
143    /// Allocates contiguous low-memory pages (physical address < 4 GiB).
144    pub fn alloc_dma32_pages(
145        &self,
146        _num_pages: usize,
147        _alignment: usize,
148        _kind: UsageKind,
149    ) -> AllocResult<usize> {
150        unimplemented!("TLSF allocator does not support alloc_dma32_pages")
151    }
152
153    /// Allocates contiguous pages starting from the given address.
154    pub fn alloc_pages_at(
155        &self,
156        _start: usize,
157        _num_pages: usize,
158        _alignment: usize,
159        _kind: UsageKind,
160    ) -> AllocResult<usize> {
161        unimplemented!("TLSF allocator does not support alloc_pages_at")
162    }
163
164    /// Gives back the allocated pages.
165    pub fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) {
166        let size = num_pages * PAGE_SIZE;
167        let ptr = NonNull::new(pos as *mut u8).expect("dealloc_pages null ptr");
168        unsafe {
169            self.inner.lock().tlsf.deallocate(ptr, PAGE_SIZE);
170        }
171        self.inner.lock().used_bytes -= size;
172        self.usages.lock().dealloc(kind, size);
173    }
174
175    /// Returns the number of allocated bytes.
176    pub fn used_bytes(&self) -> usize {
177        self.inner.lock().used_bytes
178    }
179
180    /// Returns the number of available bytes.
181    pub fn available_bytes(&self) -> usize {
182        let inner = self.inner.lock();
183        inner.total_bytes.saturating_sub(inner.used_bytes)
184    }
185
186    /// Returns the number of allocated pages.
187    pub fn used_pages(&self) -> usize {
188        self.used_bytes() / PAGE_SIZE
189    }
190
191    /// Returns the number of available pages.
192    pub fn available_pages(&self) -> usize {
193        self.available_bytes() / PAGE_SIZE
194    }
195
196    /// Returns the usage statistics.
197    pub fn usages(&self) -> Usages {
198        *self.usages.lock()
199    }
200}
201
202impl AllocatorOps for GlobalAllocator {
203    fn name(&self) -> &'static str {
204        GlobalAllocator::name(self)
205    }
206
207    fn init(&self, start_vaddr: usize, size: usize) -> AllocResult {
208        GlobalAllocator::init(self, start_vaddr, size)
209    }
210
211    fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult {
212        GlobalAllocator::add_memory(self, start_vaddr, size)
213    }
214
215    fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>> {
216        GlobalAllocator::alloc(self, layout)
217    }
218
219    fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
220        GlobalAllocator::dealloc(self, pos, layout)
221    }
222
223    fn alloc_pages(
224        &self,
225        num_pages: usize,
226        alignment: usize,
227        kind: UsageKind,
228    ) -> AllocResult<usize> {
229        GlobalAllocator::alloc_pages(self, num_pages, alignment, kind)
230    }
231
232    fn alloc_dma32_pages(
233        &self,
234        num_pages: usize,
235        alignment: usize,
236        kind: UsageKind,
237    ) -> AllocResult<usize> {
238        GlobalAllocator::alloc_dma32_pages(self, num_pages, alignment, kind)
239    }
240
241    fn alloc_pages_at(
242        &self,
243        start: usize,
244        num_pages: usize,
245        alignment: usize,
246        kind: UsageKind,
247    ) -> AllocResult<usize> {
248        GlobalAllocator::alloc_pages_at(self, start, num_pages, alignment, kind)
249    }
250
251    fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) {
252        GlobalAllocator::dealloc_pages(self, pos, num_pages, kind)
253    }
254
255    fn used_bytes(&self) -> usize {
256        GlobalAllocator::used_bytes(self)
257    }
258
259    fn available_bytes(&self) -> usize {
260        GlobalAllocator::available_bytes(self)
261    }
262
263    fn used_pages(&self) -> usize {
264        GlobalAllocator::used_pages(self)
265    }
266
267    fn available_pages(&self) -> usize {
268        GlobalAllocator::available_pages(self)
269    }
270
271    fn usages(&self) -> Usages {
272        GlobalAllocator::usages(self)
273    }
274}
275
276/// Returns the reference to the global allocator.
277pub fn global_allocator() -> &'static GlobalAllocator {
278    &GLOBAL_ALLOCATOR
279}
280
281/// Initializes per-CPU allocator state.
282///
283/// TLSF does not use per-CPU slabs, so this is intentionally a no-op.
284pub fn init_percpu_slab(_cpu_id: usize) {}
285
286/// Initializes the global allocator with the given memory region.
287pub fn global_init(start_vaddr: usize, size: usize) -> AllocResult {
288    debug!(
289        "initialize global allocator at: [{:#x}, {:#x})",
290        start_vaddr,
291        start_vaddr + size
292    );
293    GLOBAL_ALLOCATOR.init(start_vaddr, size)
294}
295
296/// Add the given memory region to the global allocator.
297pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult {
298    debug!(
299        "add a memory region to global allocator: [{:#x}, {:#x})",
300        start_vaddr,
301        start_vaddr + size
302    );
303    GLOBAL_ALLOCATOR.add_memory(start_vaddr, size)
304}
305
306unsafe impl GlobalAlloc for GlobalAllocator {
307    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
308        let inner = move || {
309            if let Ok(ptr) = GlobalAllocator::alloc(self, layout) {
310                ptr.as_ptr()
311            } else {
312                alloc::alloc::handle_alloc_error(layout)
313            }
314        };
315
316        #[cfg(feature = "tracking")]
317        {
318            crate::tracking::with_state(|state| match state {
319                None => inner(),
320                Some(state) => {
321                    let ptr = inner();
322                    let generation = state.generation;
323                    state.generation += 1;
324                    state.map.insert(
325                        ptr as usize,
326                        crate::tracking::AllocationInfo {
327                            layout,
328                            backtrace: axbacktrace::Backtrace::capture(),
329                            generation,
330                        },
331                    );
332                    ptr
333                }
334            })
335        }
336
337        #[cfg(not(feature = "tracking"))]
338        inner()
339    }
340
341    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
342        let ptr = NonNull::new(ptr).expect("dealloc null ptr");
343        let inner = || GlobalAllocator::dealloc(self, ptr, layout);
344
345        #[cfg(feature = "tracking")]
346        crate::tracking::with_state(|state| match state {
347            None => inner(),
348            Some(state) => {
349                let address = ptr.as_ptr() as usize;
350                state.map.remove(&address);
351                inner()
352            }
353        });
354
355        #[cfg(not(feature = "tracking"))]
356        inner();
357    }
358}