9a716604fc
This fixes a warning that may in the future become an error, about the possibility for unaligned references, since the derive macros apparently rely on creating references to fields. Unaligned references are direct UB.
295 lines
9.0 KiB
Rust
295 lines
9.0 KiB
Rust
use core::{
|
|
marker::PhantomData,
|
|
mem,
|
|
};
|
|
|
|
use crate::{
|
|
Arch,
|
|
BumpAllocator,
|
|
FrameAllocator,
|
|
FrameCount,
|
|
FrameUsage,
|
|
PhysicalAddress,
|
|
VirtualAddress,
|
|
};
|
|
|
|
#[repr(transparent)]
|
|
struct BuddyUsage(u8);
|
|
|
|
#[repr(packed)]
|
|
struct BuddyEntry<A> {
|
|
base: PhysicalAddress,
|
|
size: usize,
|
|
// Number of first free page
|
|
skip: usize,
|
|
// Count of used pages
|
|
used: usize,
|
|
phantom: PhantomData<A>,
|
|
}
|
|
|
|
impl<A> Clone for BuddyEntry<A> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
base: self.base,
|
|
size: self.size,
|
|
skip: self.skip,
|
|
used: self.used,
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
impl<A> Copy for BuddyEntry<A> {}
|
|
|
|
impl<A: Arch> BuddyEntry<A> {
|
|
fn empty() -> Self {
|
|
Self {
|
|
base: PhysicalAddress::new(0),
|
|
size: 0,
|
|
skip: 0,
|
|
used: 0,
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn pages(&self) -> usize {
|
|
self.size >> A::PAGE_SHIFT
|
|
}
|
|
|
|
fn usage_pages(&self) -> usize {
|
|
let bytes = self.pages() * mem::size_of::<BuddyUsage>();
|
|
// Round bytes used for usage to next page
|
|
(bytes + A::PAGE_OFFSET_MASK) >> A::PAGE_SHIFT
|
|
}
|
|
|
|
unsafe fn usage_addr(&self, page: usize) -> Option<VirtualAddress> {
|
|
if page < self.pages() {
|
|
let phys = self.base.add(page * mem::size_of::<BuddyUsage>());
|
|
Some(A::phys_to_virt(phys))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
unsafe fn usage(&self, page: usize) -> Option<BuddyUsage> {
|
|
let addr = self.usage_addr(page)?;
|
|
Some(A::read(addr))
|
|
}
|
|
|
|
unsafe fn set_usage(&self, page: usize, usage: BuddyUsage) -> Option<()> {
|
|
let addr = self.usage_addr(page)?;
|
|
Some(A::write(addr, usage))
|
|
}
|
|
}
|
|
|
|
pub struct BuddyAllocator<A> {
|
|
table_virt: VirtualAddress,
|
|
phantom: PhantomData<A>,
|
|
}
|
|
|
|
impl<A: Arch> BuddyAllocator<A> {
|
|
const BUDDY_ENTRIES: usize = A::PAGE_SIZE / mem::size_of::<BuddyEntry<A>>();
|
|
|
|
pub unsafe fn new(mut bump_allocator: BumpAllocator<A>) -> Option<Self> {
|
|
// Allocate buddy table
|
|
let table_phys = bump_allocator.allocate_one()?;
|
|
let table_virt = A::phys_to_virt(table_phys);
|
|
for i in 0 .. (A::PAGE_SIZE / mem::size_of::<BuddyEntry<A>>()) {
|
|
let virt = table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
|
|
A::write(virt, BuddyEntry::<A>::empty());
|
|
}
|
|
|
|
let allocator = Self {
|
|
table_virt,
|
|
phantom: PhantomData,
|
|
};
|
|
|
|
// Add areas to buddy table, combining areas when possible, and skipping frames used
|
|
// by the bump allocator
|
|
let mut offset = bump_allocator.offset();
|
|
for old_area in bump_allocator.areas().iter() {
|
|
let mut area = old_area.clone();
|
|
if offset >= area.size {
|
|
offset -= area.size;
|
|
continue;
|
|
} else if offset > 0 {
|
|
area.base = area.base.add(offset);
|
|
area.size -= offset;
|
|
offset = 0;
|
|
}
|
|
for i in 0 .. (A::PAGE_SIZE / mem::size_of::<BuddyEntry<A>>()) {
|
|
let virt = table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
|
|
let mut entry = A::read::<BuddyEntry<A>>(virt);
|
|
let inserted = if area.base.add(area.size) == entry.base {
|
|
// Combine entry at start
|
|
entry.base = area.base;
|
|
entry.size += area.size;
|
|
true
|
|
} else if area.base == entry.base.add(entry.size) {
|
|
// Combine entry at end
|
|
entry.size += area.size;
|
|
true
|
|
} else if entry.size == 0 {
|
|
// Create new entry
|
|
entry.base = area.base;
|
|
entry.size = area.size;
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
if inserted {
|
|
A::write(virt, entry);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//TODO: sort areas?
|
|
|
|
// Allocate buddy maps
|
|
for i in 0 .. Self::BUDDY_ENTRIES {
|
|
let virt = table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
|
|
let mut entry = A::read::<BuddyEntry<A>>(virt);
|
|
|
|
// Only set up entries that have enough space for their own usage map
|
|
let usage_pages = entry.usage_pages();
|
|
if entry.pages() > usage_pages {
|
|
// Mark all usage bytes as unused
|
|
let usage_start = entry.usage_addr(0)?;
|
|
for page in 0..usage_pages {
|
|
A::write_bytes(usage_start.add(page << A::PAGE_SHIFT), 0, A::PAGE_SIZE);
|
|
}
|
|
|
|
// Mark bytes used for usage as used
|
|
for page in 0..usage_pages {
|
|
entry.set_usage(page, BuddyUsage(1))?;
|
|
}
|
|
}
|
|
|
|
// Skip the pages used for usage
|
|
entry.skip = usage_pages;
|
|
|
|
// Set used pages to pages used for usage
|
|
entry.used = usage_pages;
|
|
|
|
// Write updated entry
|
|
A::write(virt, entry);
|
|
}
|
|
|
|
Some(allocator)
|
|
}
|
|
}
|
|
|
|
impl<A: Arch> FrameAllocator for BuddyAllocator<A> {
|
|
unsafe fn allocate(&mut self, count: FrameCount) -> Option<PhysicalAddress> {
|
|
if self.table_virt.data() == 0 {
|
|
return None;
|
|
}
|
|
|
|
for entry_i in 0 .. Self::BUDDY_ENTRIES {
|
|
let virt = self.table_virt.add(entry_i * mem::size_of::<BuddyEntry<A>>());
|
|
let mut entry = A::read::<BuddyEntry<A>>(virt);
|
|
|
|
let mut free_page = entry.skip;
|
|
let mut free_count = 0;
|
|
for page in entry.skip .. entry.pages() {
|
|
let usage = entry.usage(page)?;
|
|
if usage.0 == 0 {
|
|
free_count += 1;
|
|
|
|
if free_count == count.data() {
|
|
break;
|
|
}
|
|
} else {
|
|
free_page = page + 1;
|
|
free_count = 0;
|
|
}
|
|
}
|
|
|
|
if free_count == count.data() {
|
|
for page in free_page .. free_page + free_count {
|
|
// Update usage
|
|
let mut usage = entry.usage(page)?;
|
|
usage.0 += 1;
|
|
entry.set_usage(page, usage);
|
|
|
|
// Zero page
|
|
let page_phys = entry.base.add(page << A::PAGE_SHIFT);
|
|
let page_virt = A::phys_to_virt(page_phys);
|
|
A::write_bytes(page_virt, 0, A::PAGE_SIZE);
|
|
}
|
|
|
|
// Update skip if necessary
|
|
if entry.skip == free_page {
|
|
entry.skip = free_page + free_count;
|
|
}
|
|
|
|
// Update used page count
|
|
entry.used += free_count;
|
|
|
|
// Write updated entry
|
|
A::write(virt, entry);
|
|
|
|
return Some(entry.base.add(free_page << A::PAGE_SHIFT));
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
unsafe fn free(&mut self, base: PhysicalAddress, count: FrameCount) {
|
|
if self.table_virt.data() == 0 {
|
|
return;
|
|
}
|
|
|
|
let size = count.data() * A::PAGE_SIZE;
|
|
for i in 0 .. Self::BUDDY_ENTRIES {
|
|
let virt = self.table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
|
|
let mut entry = A::read::<BuddyEntry<A>>(virt);
|
|
|
|
if base >= entry.base && base.add(size) <= entry.base.add(entry.size) {
|
|
let start_page = (base.data() - entry.base.data()) >> A::PAGE_SHIFT;
|
|
for page in start_page..start_page + count.data() {
|
|
let mut usage = entry.usage(page).expect("failed to get usage during free");
|
|
|
|
if usage.0 > 0 {
|
|
usage.0 -= 1;
|
|
} else {
|
|
panic!("tried to free already free frame");
|
|
}
|
|
|
|
// If page was freed
|
|
if usage.0 == 0 {
|
|
// Update skip if necessary
|
|
if page < entry.skip {
|
|
entry.skip = page;
|
|
}
|
|
|
|
// Update used page count
|
|
entry.used -= 1;
|
|
}
|
|
|
|
entry.set_usage(page, usage).expect("failed to set usage during free");
|
|
}
|
|
|
|
// Write updated entry
|
|
A::write(virt, entry);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn usage(&self) -> FrameUsage {
|
|
let mut total = 0;
|
|
let mut used = 0;
|
|
for i in 0 .. Self::BUDDY_ENTRIES {
|
|
let virt = self.table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
|
|
let entry = A::read::<BuddyEntry<A>>(virt);
|
|
total += entry.size >> A::PAGE_SHIFT;
|
|
used += entry.used;
|
|
}
|
|
FrameUsage::new(FrameCount::new(used), FrameCount::new(total))
|
|
}
|
|
}
|