use core::ffi::c_void;
use core::ptr;
use crate::kernel::tasks::{
taskENTER_CRITICAL, taskEXIT_CRITICAL, vTaskSuspendAll, xTaskResumeAll,
};
use crate::port::portBYTE_ALIGNMENT;
const PORT_BYTE_ALIGNMENT_MASK: usize = portBYTE_ALIGNMENT - 1;
const HEAP_MINIMUM_BLOCK_SIZE: usize = HEAP_STRUCT_SIZE << 1;
const HEAP_BLOCK_ALLOCATED_BITMASK: usize = 1 << (usize::BITS - 1);
const HEAP_STRUCT_SIZE: usize = {
let base = core::mem::size_of::<BlockLink>();
(base + PORT_BYTE_ALIGNMENT_MASK) & !PORT_BYTE_ALIGNMENT_MASK
};
#[repr(C)]
struct BlockLink {
pxNextFreeBlock: *mut BlockLink,
xBlockSize: usize,
}
impl BlockLink {
const fn new() -> Self {
BlockLink {
pxNextFreeBlock: ptr::null_mut(),
xBlockSize: 0,
}
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct HeapRegion {
pub puc_start_address: *mut u8,
pub x_size_in_bytes: usize,
}
impl HeapRegion {
pub const fn new(start: *mut u8, size: usize) -> Self {
HeapRegion {
puc_start_address: start,
x_size_in_bytes: size,
}
}
}
static mut X_START: BlockLink = BlockLink::new();
static mut PX_END: *mut BlockLink = ptr::null_mut();
static mut X_FREE_BYTES_REMAINING: usize = 0;
static mut X_MINIMUM_EVER_FREE_BYTES_REMAINING: usize = 0;
static mut X_NUMBER_OF_SUCCESSFUL_ALLOCATIONS: usize = 0;
static mut X_NUMBER_OF_SUCCESSFUL_FREES: usize = 0;
#[inline(always)]
fn heap_block_size_is_valid(size: usize) -> bool {
(size & HEAP_BLOCK_ALLOCATED_BITMASK) == 0
}
#[inline(always)]
fn heap_block_is_allocated(block: &BlockLink) -> bool {
(block.xBlockSize & HEAP_BLOCK_ALLOCATED_BITMASK) != 0
}
#[inline(always)]
unsafe fn heap_allocate_block(block: *mut BlockLink) {
(*block).xBlockSize |= HEAP_BLOCK_ALLOCATED_BITMASK;
}
#[inline(always)]
unsafe fn heap_free_block(block: *mut BlockLink) {
(*block).xBlockSize &= !HEAP_BLOCK_ALLOCATED_BITMASK;
}
#[inline(always)]
fn heap_get_block_size(block: &BlockLink) -> usize {
block.xBlockSize & !HEAP_BLOCK_ALLOCATED_BITMASK
}
fn prv_region_bounds(region: &HeapRegion) -> Option<(usize, usize, usize)> {
if region.x_size_in_bytes == 0 || region.puc_start_address.is_null() {
return None;
}
let original_address = region.puc_start_address as usize;
let adjustment = if (original_address & PORT_BYTE_ALIGNMENT_MASK) != 0 {
portBYTE_ALIGNMENT - (original_address & PORT_BYTE_ALIGNMENT_MASK)
} else {
0
};
let ux_address = original_address.checked_add(adjustment)?;
let x_total_region_size = region.x_size_in_bytes.checked_sub(adjustment)?;
let ux_region_end = ux_address.checked_add(x_total_region_size)?;
let ux_end_address = ux_region_end.checked_sub(HEAP_STRUCT_SIZE)? & !PORT_BYTE_ALIGNMENT_MASK;
let block_size = ux_end_address.checked_sub(ux_address)?;
if block_size <= HEAP_MINIMUM_BLOCK_SIZE {
return None;
}
Some((ux_address, ux_end_address, block_size))
}
pub unsafe fn vPortDefineHeapRegions<const N: usize>(regions: &[HeapRegion; N]) {
const { assert!(N > 0, "At least one heap region must be provided") };
configASSERT!(PX_END.is_null(), "Heap regions may only be defined once");
let mut x_total_heap_size: usize = 0;
let mut ux_previous_end_address: Option<usize> = None;
for region in regions.iter() {
if region.x_size_in_bytes != 0 {
configASSERT!(
!region.puc_start_address.is_null(),
"Non-empty heap regions must have a non-null start address"
);
}
if let Some((ux_address, ux_end_address, block_size)) = prv_region_bounds(region) {
if let Some(previous_end) = ux_previous_end_address {
configASSERT!(
ux_address > previous_end,
"Heap regions must be non-overlapping and in ascending address order"
);
}
x_total_heap_size = match x_total_heap_size.checked_add(block_size) {
Some(size) => size,
None => panic!("Total heap region size overflow"),
};
ux_previous_end_address = Some(ux_end_address);
}
}
configASSERT!(
x_total_heap_size > 0,
"At least one usable heap region must be provided"
);
X_START.pxNextFreeBlock = ptr::null_mut();
X_START.xBlockSize = 0;
for region in regions.iter() {
let Some((ux_address, ux_end_address, block_size)) = prv_region_bounds(region) else {
continue;
};
let px_previous_end = PX_END;
PX_END = ux_end_address as *mut BlockLink;
(*PX_END).xBlockSize = 0;
(*PX_END).pxNextFreeBlock = ptr::null_mut();
let px_first_free_block_in_region = ux_address as *mut BlockLink;
(*px_first_free_block_in_region).xBlockSize = block_size;
(*px_first_free_block_in_region).pxNextFreeBlock = PX_END;
if px_previous_end.is_null() {
X_START.pxNextFreeBlock = px_first_free_block_in_region;
} else {
(*px_previous_end).pxNextFreeBlock = px_first_free_block_in_region;
}
}
X_MINIMUM_EVER_FREE_BYTES_REMAINING = x_total_heap_size;
X_FREE_BYTES_REMAINING = x_total_heap_size;
}
unsafe fn prv_insert_block_into_free_list(px_block_to_insert: *mut BlockLink) {
let mut px_iterator = &mut X_START as *mut BlockLink;
while (*px_iterator).pxNextFreeBlock < px_block_to_insert {
px_iterator = (*px_iterator).pxNextFreeBlock;
}
let puc_iterator = px_iterator as *mut u8;
let iterator_block_size = heap_get_block_size(&*px_iterator);
if puc_iterator.add(iterator_block_size) == px_block_to_insert as *mut u8 {
(*px_iterator).xBlockSize += heap_get_block_size(&*px_block_to_insert);
let px_block_to_insert = px_iterator;
let puc_block = px_block_to_insert as *mut u8;
let block_size = heap_get_block_size(&*px_block_to_insert);
let px_next = (*px_iterator).pxNextFreeBlock;
if puc_block.add(block_size) == px_next as *mut u8 && px_next != PX_END {
(*px_block_to_insert).xBlockSize += heap_get_block_size(&*px_next);
(*px_block_to_insert).pxNextFreeBlock = (*px_next).pxNextFreeBlock;
}
} else {
let puc_block = px_block_to_insert as *mut u8;
let block_size = heap_get_block_size(&*px_block_to_insert);
let px_next = (*px_iterator).pxNextFreeBlock;
if puc_block.add(block_size) == px_next as *mut u8 && px_next != PX_END {
(*px_block_to_insert).xBlockSize += heap_get_block_size(&*px_next);
(*px_block_to_insert).pxNextFreeBlock = (*px_next).pxNextFreeBlock;
} else {
(*px_block_to_insert).pxNextFreeBlock = px_next;
}
(*px_iterator).pxNextFreeBlock = px_block_to_insert;
}
}
pub unsafe fn pvPortMalloc(x_wanted_size: usize) -> *mut c_void {
let mut pv_return: *mut c_void = ptr::null_mut();
let mut x_wanted_size = x_wanted_size;
configASSERT!(
!PX_END.is_null(),
"Heap not initialized - call vPortDefineHeapRegions first"
);
if x_wanted_size > 0 {
if let Some(size) = x_wanted_size.checked_add(HEAP_STRUCT_SIZE) {
x_wanted_size = size;
if (x_wanted_size & PORT_BYTE_ALIGNMENT_MASK) != 0 {
let additional = portBYTE_ALIGNMENT - (x_wanted_size & PORT_BYTE_ALIGNMENT_MASK);
if let Some(size) = x_wanted_size.checked_add(additional) {
x_wanted_size = size;
} else {
x_wanted_size = 0; }
}
} else {
x_wanted_size = 0; }
}
vTaskSuspendAll();
{
if heap_block_size_is_valid(x_wanted_size)
&& x_wanted_size > 0
&& x_wanted_size <= X_FREE_BYTES_REMAINING
{
let mut px_previous_block = &mut X_START as *mut BlockLink;
let mut px_block = X_START.pxNextFreeBlock;
while heap_get_block_size(&*px_block) < x_wanted_size
&& !(*px_block).pxNextFreeBlock.is_null()
{
px_previous_block = px_block;
px_block = (*px_block).pxNextFreeBlock;
}
if px_block != PX_END {
pv_return = ((*px_previous_block).pxNextFreeBlock as *mut u8).add(HEAP_STRUCT_SIZE)
as *mut c_void;
(*px_previous_block).pxNextFreeBlock = (*px_block).pxNextFreeBlock;
let block_size = heap_get_block_size(&*px_block);
if block_size - x_wanted_size > HEAP_MINIMUM_BLOCK_SIZE {
let px_new_block = (px_block as *mut u8).add(x_wanted_size) as *mut BlockLink;
(*px_new_block).xBlockSize = block_size - x_wanted_size;
(*px_block).xBlockSize = x_wanted_size;
(*px_new_block).pxNextFreeBlock = (*px_previous_block).pxNextFreeBlock;
(*px_previous_block).pxNextFreeBlock = px_new_block;
}
X_FREE_BYTES_REMAINING -= heap_get_block_size(&*px_block);
if X_FREE_BYTES_REMAINING < X_MINIMUM_EVER_FREE_BYTES_REMAINING {
X_MINIMUM_EVER_FREE_BYTES_REMAINING = X_FREE_BYTES_REMAINING;
}
heap_allocate_block(px_block);
(*px_block).pxNextFreeBlock = ptr::null_mut();
X_NUMBER_OF_SUCCESSFUL_ALLOCATIONS += 1;
}
}
}
xTaskResumeAll();
pv_return
}
pub unsafe fn vPortFree(pv: *mut c_void) {
if pv.is_null() {
return;
}
let puc = pv as *mut u8;
let px_link = puc.sub(HEAP_STRUCT_SIZE) as *mut BlockLink;
debug_assert!(
heap_block_is_allocated(&*px_link),
"vPortFree: block not allocated"
);
debug_assert!(
(*px_link).pxNextFreeBlock.is_null(),
"vPortFree: block still in free list"
);
if heap_block_is_allocated(&*px_link) && (*px_link).pxNextFreeBlock.is_null() {
heap_free_block(px_link);
vTaskSuspendAll();
{
X_FREE_BYTES_REMAINING += heap_get_block_size(&*px_link);
prv_insert_block_into_free_list(px_link);
X_NUMBER_OF_SUCCESSFUL_FREES += 1;
}
xTaskResumeAll();
}
}
pub fn xPortGetFreeHeapSize() -> usize {
unsafe { X_FREE_BYTES_REMAINING }
}
pub fn xPortGetMinimumEverFreeHeapSize() -> usize {
unsafe { X_MINIMUM_EVER_FREE_BYTES_REMAINING }
}
pub fn xPortResetHeapMinimumEverFreeHeapSize() {
unsafe {
X_MINIMUM_EVER_FREE_BYTES_REMAINING = X_FREE_BYTES_REMAINING;
}
}
pub unsafe fn pvPortCalloc(x_num: usize, x_size: usize) -> *mut c_void {
let total = match x_num.checked_mul(x_size) {
Some(t) => t,
None => return ptr::null_mut(),
};
let pv = pvPortMalloc(total);
if !pv.is_null() {
ptr::write_bytes(pv as *mut u8, 0, total);
}
pv
}
pub unsafe fn vPortGetHeapStats(px_heap_stats: *mut super::HeapStats_t) {
if px_heap_stats.is_null() {
return;
}
let mut x_blocks: usize = 0;
let mut x_max_size: usize = 0;
let mut x_min_size: usize = usize::MAX;
unsafe {
vTaskSuspendAll();
{
let mut px_block = X_START.pxNextFreeBlock;
if !px_block.is_null() {
while px_block != PX_END {
x_blocks += 1;
let block_size = heap_get_block_size(&*px_block);
if block_size > x_max_size {
x_max_size = block_size;
}
if block_size != 0 && block_size < x_min_size {
x_min_size = block_size;
}
px_block = (*px_block).pxNextFreeBlock;
}
}
}
xTaskResumeAll();
(*px_heap_stats).xSizeOfLargestFreeBlockInBytes = x_max_size;
(*px_heap_stats).xSizeOfSmallestFreeBlockInBytes = x_min_size;
(*px_heap_stats).xNumberOfFreeBlocks = x_blocks;
taskENTER_CRITICAL();
(*px_heap_stats).xAvailableHeapSpaceInBytes = X_FREE_BYTES_REMAINING;
(*px_heap_stats).xNumberOfSuccessfulAllocations = X_NUMBER_OF_SUCCESSFUL_ALLOCATIONS;
(*px_heap_stats).xNumberOfSuccessfulFrees = X_NUMBER_OF_SUCCESSFUL_FREES;
(*px_heap_stats).xMinimumEverFreeBytesRemaining = X_MINIMUM_EVER_FREE_BYTES_REMAINING;
taskEXIT_CRITICAL();
}
}
pub fn vPortInitialiseBlocks() {
}
pub unsafe fn vPortHeapResetState() {
unsafe {
X_START.pxNextFreeBlock = ptr::null_mut();
PX_END = ptr::null_mut();
X_FREE_BYTES_REMAINING = 0;
X_MINIMUM_EVER_FREE_BYTES_REMAINING = 0;
X_NUMBER_OF_SUCCESSFUL_ALLOCATIONS = 0;
X_NUMBER_OF_SUCCESSFUL_FREES = 0;
}
}
use core::alloc::{GlobalAlloc, Layout};
pub struct FreeRtosAllocator;
const GLOBAL_ALLOC_HEADER_SIZE: usize = core::mem::size_of::<*mut u8>();
unsafe fn prv_alloc_layout(layout: Layout) -> *mut u8 {
let alignment = layout.align().max(portBYTE_ALIGNMENT);
let allocation_size = match layout
.size()
.checked_add(GLOBAL_ALLOC_HEADER_SIZE)
.and_then(|size| size.checked_add(alignment - 1))
{
Some(size) => size,
None => return ptr::null_mut(),
};
let raw = pvPortMalloc(allocation_size) as *mut u8;
if raw.is_null() {
return ptr::null_mut();
}
let unaligned_address = raw.add(GLOBAL_ALLOC_HEADER_SIZE) as usize;
let aligned_address = match unaligned_address.checked_add(alignment - 1) {
Some(address) => address & !(alignment - 1),
None => {
vPortFree(raw as *mut c_void);
return ptr::null_mut();
}
};
let aligned = aligned_address as *mut u8;
ptr::write_unaligned(aligned.sub(GLOBAL_ALLOC_HEADER_SIZE) as *mut *mut u8, raw);
aligned
}
unsafe fn prv_free_layout(ptr: *mut u8) {
if !ptr.is_null() {
let raw = ptr::read_unaligned(ptr.sub(GLOBAL_ALLOC_HEADER_SIZE) as *const *mut u8);
vPortFree(raw as *mut c_void);
}
}
unsafe impl GlobalAlloc for FreeRtosAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
prv_alloc_layout(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
prv_free_layout(ptr);
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let new_layout = match Layout::from_size_align(new_size, layout.align()) {
Ok(layout) => layout,
Err(_) => return ptr::null_mut(),
};
let new_ptr = prv_alloc_layout(new_layout);
if !new_ptr.is_null() && !ptr.is_null() {
let copy_size = layout.size().min(new_size);
ptr::copy_nonoverlapping(ptr, new_ptr, copy_size);
prv_free_layout(ptr);
}
new_ptr
}
}
macro_rules! configASSERT {
($cond:expr, $msg:expr) => {
if !$cond {
panic!($msg);
}
};
}
use configASSERT;