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}