use alloc::collections::linked_list::LinkedList;
use core::alloc::AllocError;
use core::cmp::Ordering;
use align_address::Align;
#[derive(Debug)]
pub struct FreeListEntry {
pub start: usize,
pub end: usize,
}
impl FreeListEntry {
pub const fn new(start: usize, end: usize) -> Self {
FreeListEntry { start, end }
}
}
#[derive(Debug)]
pub struct FreeList {
pub list: LinkedList<FreeListEntry>,
}
impl FreeList {
pub const fn new() -> Self {
Self {
list: LinkedList::new(),
}
}
pub fn allocate(&mut self, size: usize, alignment: Option<usize>) -> Result<usize, AllocError> {
trace!(
"Allocating {} bytes from Free List {:#X}",
size,
self as *const Self as usize
);
let new_size = if let Some(align) = alignment {
size + align
} else {
size
};
let mut cursor = self.list.cursor_front_mut();
while let Some(node) = cursor.current() {
let (region_start, region_size) = (node.start, node.end - node.start);
match region_size.cmp(&new_size) {
Ordering::Greater => {
if let Some(align) = alignment {
let new_addr = region_start.align_up(align);
node.start += size + (new_addr - region_start);
if new_addr != region_start {
let new_entry = FreeListEntry::new(region_start, new_addr);
cursor.insert_before(new_entry);
}
return Ok(new_addr);
} else {
node.start += size;
return Ok(region_start);
}
}
Ordering::Equal => {
if let Some(align) = alignment {
let new_addr = region_start.align_up(align);
if new_addr != region_start {
node.end = new_addr;
}
return Ok(new_addr);
} else {
cursor.remove_current();
return Ok(region_start);
}
}
Ordering::Less => {}
}
cursor.move_next();
}
Err(AllocError)
}
#[cfg(all(target_arch = "x86_64", not(feature = "pci")))]
pub fn reserve(&mut self, address: usize, size: usize) -> Result<(), AllocError> {
trace!(
"Try to reserve {} bytes at {:#X} from Free List {:#X}",
size,
address,
self as *const Self as usize
);
let mut cursor = self.list.cursor_front_mut();
while let Some(node) = cursor.current() {
let (region_start, region_size) = (node.start, node.end - node.start);
if address > region_start && address + size < region_start + region_size {
node.start = address + size;
let new_entry = FreeListEntry::new(region_start, address);
cursor.insert_before(new_entry);
return Ok(());
} else if address > region_start && address + size == region_start + region_size {
node.start = address + size;
return Ok(());
} else if address == region_start && address + size < region_start + region_size {
node.start = region_start + size;
return Ok(());
}
cursor.move_next();
}
Err(AllocError)
}
pub fn deallocate(&mut self, address: usize, size: usize) {
trace!(
"Deallocating {} bytes at {:#X} from Free List {:#X}",
size,
address,
self as *const Self as usize
);
let end = address + size;
let mut cursor = self.list.cursor_front_mut();
while let Some(node) = cursor.current() {
let (region_start, region_end) = (node.start, node.end);
if region_start == end {
node.start = address;
if let Some(prev_node) = cursor.peek_prev() {
let prev_region_end = prev_node.end;
if prev_region_end == address {
prev_node.end = region_end;
cursor.remove_current();
}
}
return;
} else if region_end == address {
node.end = end;
if let Some(next_node) = cursor.peek_next() {
let next_region_start = next_node.start;
if next_region_start == end {
next_node.start = region_start;
cursor.remove_current();
}
}
return;
} else if end < region_start {
let new_entry = FreeListEntry::new(address, end);
cursor.insert_before(new_entry);
return;
}
cursor.move_next();
}
let new_element = FreeListEntry::new(address, end);
self.list.push_back(new_element);
}
pub fn print_information(&self, header: &str) {
infoheader!(header);
for node in self.list.iter() {
info!("{:#016X} - {:#016X}", node.start, node.end);
}
infofooter!();
}
}
#[cfg(all(test, not(target_os = "none")))]
mod tests {
use super::*;
#[test]
fn add_element() {
let mut freelist = FreeList::new();
let entry = FreeListEntry::new(0x10000, 0x100000);
freelist.list.push_back(entry);
let mut cursor = freelist.list.cursor_front_mut();
while let Some(node) = cursor.peek_next() {
assert!(node.start != 0x1000);
assert!(node.end != 0x10000);
cursor.move_next();
}
}
#[test]
fn allocate() {
let mut freelist = FreeList::new();
let entry = FreeListEntry::new(0x10000, 0x100000);
freelist.list.push_back(entry);
let addr = freelist.allocate(0x1000, None);
assert_eq!(addr.unwrap(), 0x10000);
let mut cursor = freelist.list.cursor_front_mut();
while let Some(node) = cursor.current() {
assert_eq!(node.start, 0x11000);
assert_eq!(node.end, 0x100000);
cursor.move_next();
}
let addr = freelist.allocate(0x1000, Some(0x2000));
let mut cursor = freelist.list.cursor_front_mut();
assert!(cursor.current().is_some());
if let Some(node) = cursor.current() {
assert_eq!(node.start, 0x11000);
}
cursor.move_next();
assert!(cursor.current().is_some());
if let Some(node) = cursor.current() {
assert_eq!(node.start, 0x13000);
}
}
#[test]
fn deallocate() {
let mut freelist = FreeList::new();
let entry = FreeListEntry::new(0x10000, 0x100000);
freelist.list.push_back(entry);
let addr = freelist.allocate(0x1000, None);
freelist.deallocate(addr.unwrap(), 0x1000);
let mut cursor = freelist.list.cursor_front_mut();
while let Some(node) = cursor.current() {
assert_eq!(node.start, 0x10000);
assert_eq!(node.end, 0x100000);
cursor.move_next();
}
}
}