mips_mcu_alloc/
lib.rs

1//! A heap allocator for microcontollers with MIPS core such as PIC32 controllers
2//!
3//! The heap is placed at a location determined by the linker and automatically extended
4//! to fullfil allocation requests. Automatic heap extension fails if the heap would collide
5//! with the stack.
6//!
7//! # Example
8//!
9//! ```ignore
10//! #![feature(global_allocator)]
11//! #![feature(alloc_error_handler)]
12//!
13//! // Plug in the allocator crate
14//! extern crate alloc;
15//!
16//! use alloc::Vec;
17//! use mips_mcu_alloc::MipsMcuHeap;
18//!
19//! #[global_allocator]
20//! static ALLOCATOR: MipsMcuHeap = MipsMcuHeap::empty();
21//!
22//! #[entry]
23//! fn main() -> ! {
24//!     ALLOCATOR.init();
25//!
26//!     let mut xs = Vec::new();
27//!     xs.push(1);
28//!
29//!     loop { /* .. */ }
30//! }
31//!
32//! #[alloc_error_handler]
33//! fn alloc_error(layout: core::alloc::Layout) -> ! {
34//!     panic!("Cannot allocate heap memory: {:?}", layout);
35//! }
36//!
37//! ```
38
39#![no_std]
40#![feature(asm_experimental_arch)]
41
42use core::alloc::{GlobalAlloc, Layout};
43use core::arch::asm;
44use core::cell::RefCell;
45use core::ptr::{self, NonNull};
46
47use critical_section::{self, Mutex};
48use linked_list_allocator::Heap;
49use mips_rt::heap_start;
50
51#[cfg(feature = "log")]
52use log::{debug, trace};
53
54/// Heap extension is performed stepwise. This constant defines the size of one extension step.
55const EXTEND_INCREMENT: usize = 1024;
56
57pub struct MipsMcuHeap {
58    heap: Mutex<RefCell<Heap>>,
59}
60
61#[derive(Debug)]
62#[non_exhaustive]
63pub enum Error {
64    InsufficientHeadroom,
65}
66
67impl MipsMcuHeap {
68    /// Create a new UNINITIALIZED heap allocator.
69    ///
70    /// You must initialize this heap using the
71    /// [`init`](struct.CortexMHeap.html#method.init) method before using the allocator.
72    pub const fn empty() -> MipsMcuHeap {
73        MipsMcuHeap {
74            heap: Mutex::new(RefCell::new(Heap::empty())),
75        }
76    }
77
78    /// Initialize heap with heap start location from linker and a defined initial size.
79    pub fn init(&self) {
80        let bottom = heap_start() as *mut u8;
81        #[cfg(feature = "log")]
82        debug!("heap init, bottom = {bottom:?}, size = {EXTEND_INCREMENT}");
83        critical_section::with(|cs| {
84            unsafe {
85                self.heap
86                    .borrow(cs)
87                    .borrow_mut()
88                    .init(bottom, EXTEND_INCREMENT)
89            };
90        });
91    }
92
93    /// Returns an estimate of the amount of bytes in use.
94    pub fn used(&self) -> usize {
95        critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().used())
96    }
97
98    /// Returns the amount of bytes currently available.
99    ///
100    /// This method does not consider possible heap extensions
101    pub fn free(&self) -> usize {
102        critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free())
103    }
104
105    /// Returns the start (bottom) of the heap.
106    pub fn bottom(&self) -> *mut u8 {
107        critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().bottom())
108    }
109
110    /// Returns the end (top) of the heap.
111    pub fn top(&self) -> *mut u8 {
112        critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().top())
113    }
114
115    /// Returns the current distance to the bottom of the stack.
116    ///
117    /// This is an estimate amount of memory that could be added to the heap by
118    /// automatic extension. An estimate of the total available free heap memory
119    /// is the sum of `free()` and `headroom()`.
120    pub fn headroom(&self) -> usize {
121        let sp = stack_pointer() as usize;
122        let top = self.top() as usize;
123        if sp >= top {
124            sp - top
125        } else {
126            0
127        }
128    }
129
130    /// Try to extend the stack so that it has at least an amount of
131    /// `free_bytes` available.
132    ///
133    /// Fails if there is not enough headroom. Does nothing if there are already
134    /// enough free bytes.
135    ///
136    /// # Safety
137    ///
138    /// This method is considered unsafe because it increases the likelihood of
139    /// heap/stack collisions.
140    pub unsafe fn reserve(&self, free_bytes: usize) -> Result<(), Error> {
141        if free_bytes <= self.free() {
142            return Ok(());
143        }
144        let additional_bytes = free_bytes - self.free();
145        critical_section::with(|cs| {
146            let mut heap = self.heap.borrow_ref_mut(cs);
147            let new_top: *mut u8 = heap.top().add(additional_bytes);
148            if new_top >= stack_pointer() {
149                return Err(Error::InsufficientHeadroom);
150            }
151            heap.extend(additional_bytes);
152            Ok(())
153        })
154    }
155}
156
157unsafe impl GlobalAlloc for MipsMcuHeap {
158    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
159        // try to allocate and successively extend by EXTEND_INCREMENT until memory is exhausted
160        loop {
161            if let Ok(p) = critical_section::with(|cs| {
162                self.heap.borrow(cs).borrow_mut().allocate_first_fit(layout)
163            }) {
164                #[cfg(feature = "log")]
165                trace!("alloc at {:?}, {:?}, stack_pointer = {:?}", p.as_ptr(), layout, stack_pointer());
166                break p.as_ptr();
167            } else {
168                // this must be a u8 pointer
169                let new_top: *mut u8 =
170                    critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().top())
171                        .add(EXTEND_INCREMENT);
172                // avoid collision with stack
173                if new_top < stack_pointer() {
174                    critical_section::with(|cs| {
175                        self.heap.borrow(cs).borrow_mut().extend(EXTEND_INCREMENT)
176                    });
177                    #[cfg(feature = "log")]
178                    debug!("extended heap, top = {:?}, stack_pointer = {:?}", self.top(), stack_pointer());
179                } else {
180                    break ptr::null_mut();
181                }
182            }
183        }
184    }
185
186    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
187        #[cfg(feature = "log")]
188        trace!("dealloc at {ptr:?}, {layout:?}");
189        critical_section::with(|cs| {
190            self.heap
191                .borrow(cs)
192                .borrow_mut()
193                .deallocate(NonNull::new_unchecked(ptr), layout)
194        });
195    }
196}
197
198fn stack_pointer() -> *mut u8 {
199    let sp: *mut u8;
200    unsafe {
201        asm!(".set noat",
202             "move {0}, $29", out(reg) sp);
203    }
204    sp
205}