#![no_std]
#![feature(asm_experimental_arch)]
use core::alloc::{GlobalAlloc, Layout};
use core::arch::asm;
use core::cell::RefCell;
use core::ptr::{self, NonNull};
use critical_section::{self, Mutex};
use linked_list_allocator::Heap;
use mips_rt::heap_start;
#[cfg(feature = "log")]
use log::{debug, trace};
const EXTEND_INCREMENT: usize = 1024;
pub struct MipsMcuHeap {
heap: Mutex<RefCell<Heap>>,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
InsufficientHeadroom,
}
impl MipsMcuHeap {
pub const fn empty() -> MipsMcuHeap {
MipsMcuHeap {
heap: Mutex::new(RefCell::new(Heap::empty())),
}
}
pub fn init(&self) {
let bottom = heap_start() as *mut u8;
#[cfg(feature = "log")]
debug!("heap init, bottom = {bottom:?}, size = {EXTEND_INCREMENT}");
critical_section::with(|cs| {
unsafe {
self.heap
.borrow(cs)
.borrow_mut()
.init(bottom, EXTEND_INCREMENT)
};
});
}
pub fn used(&self) -> usize {
critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().used())
}
pub fn free(&self) -> usize {
critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free())
}
pub fn bottom(&self) -> *mut u8 {
critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().bottom())
}
pub fn top(&self) -> *mut u8 {
critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().top())
}
pub fn headroom(&self) -> usize {
let sp = stack_pointer() as usize;
let top = self.top() as usize;
if sp >= top {
sp - top
} else {
0
}
}
pub unsafe fn reserve(&self, free_bytes: usize) -> Result<(), Error> {
if free_bytes <= self.free() {
return Ok(());
}
let additional_bytes = free_bytes - self.free();
critical_section::with(|cs| {
let mut heap = self.heap.borrow_ref_mut(cs);
let new_top: *mut u8 = heap.top().add(additional_bytes);
if new_top >= stack_pointer() {
return Err(Error::InsufficientHeadroom);
}
heap.extend(additional_bytes);
Ok(())
})
}
}
unsafe impl GlobalAlloc for MipsMcuHeap {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
loop {
if let Ok(p) = critical_section::with(|cs| {
self.heap.borrow(cs).borrow_mut().allocate_first_fit(layout)
}) {
#[cfg(feature = "log")]
trace!("alloc at {:?}, {:?}, stack_pointer = {:?}", p.as_ptr(), layout, stack_pointer());
break p.as_ptr();
} else {
let new_top: *mut u8 =
critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().top())
.add(EXTEND_INCREMENT);
if new_top < stack_pointer() {
critical_section::with(|cs| {
self.heap.borrow(cs).borrow_mut().extend(EXTEND_INCREMENT)
});
#[cfg(feature = "log")]
debug!("extended heap, top = {:?}, stack_pointer = {:?}", self.top(), stack_pointer());
} else {
break ptr::null_mut();
}
}
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
#[cfg(feature = "log")]
trace!("dealloc at {ptr:?}, {layout:?}");
critical_section::with(|cs| {
self.heap
.borrow(cs)
.borrow_mut()
.deallocate(NonNull::new_unchecked(ptr), layout)
});
}
}
fn stack_pointer() -> *mut u8 {
let sp: *mut u8;
unsafe {
asm!(".set noat",
"move {0}, $29", out(reg) sp);
}
sp
}