From 35d18495ec0b3f51e27f0e92ebea926563d4133a Mon Sep 17 00:00:00 2001 From: Zoe Date: Wed, 19 Aug 2026 09:58:43 -0500 Subject: [PATCH] feat: frame allocation --- src/arch/x86_64/gdt.rs | 1 + src/arch/x86_64/interrupts/exceptions.rs | 19 +- src/arch/x86_64/port.rs | 144 ++++----- src/boot/limine.rs | 12 +- src/main.rs | 33 ++- src/memory/frame.rs | 354 +++++++++++++++++++++++ src/memory/mod.rs | 39 ++- 7 files changed, 498 insertions(+), 104 deletions(-) create mode 100644 src/memory/frame.rs diff --git a/src/arch/x86_64/gdt.rs b/src/arch/x86_64/gdt.rs index c1f324a..74abdb7 100644 --- a/src/arch/x86_64/gdt.rs +++ b/src/arch/x86_64/gdt.rs @@ -50,6 +50,7 @@ pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8; pub(super) const TSS_SELECTOR: u16 = 3 * 8; #[repr(align(16))] +#[allow(dead_code)] // field 0 is read, rust just cant tell struct ExceptionStack([u8; DOUBLE_FAULT_STACK_SIZE]); static mut GDT: Gdt = Gdt::new(); diff --git a/src/arch/x86_64/interrupts/exceptions.rs b/src/arch/x86_64/interrupts/exceptions.rs index ed1614a..0754055 100644 --- a/src/arch/x86_64/interrupts/exceptions.rs +++ b/src/arch/x86_64/interrupts/exceptions.rs @@ -19,22 +19,10 @@ macro_rules! fatal_with_error_code { }; } -extern "x86-interrupt" fn debug_handler(frame: InterruptStackFrame) { - fatal_exception("DEBUG EXCEPTION", &frame, None); -} - -extern "x86-interrupt" fn non_maskable_interrupt_handler(frame: InterruptStackFrame) { - fatal_exception("NON-MASKABLE INTERRUPT", &frame, None); -} - extern "x86-interrupt" fn breakpoint_handler(frame: InterruptStackFrame) { report_exception("BREAKPOINT", &frame, None); } -extern "x86-interrupt" fn double_fault_handler(frame: InterruptStackFrame, error_code: u64) { - fatal_exception("DOUBLE FAULT", &frame, Some(error_code)); -} - extern "x86-interrupt" fn page_fault_handler(frame: InterruptStackFrame, error_code: u64) { report_exception("PAGE FAULT", &frame, Some(error_code)); println!("Faulting address: {:#X}", read_cr2()); @@ -43,12 +31,15 @@ extern "x86-interrupt" fn page_fault_handler(frame: InterruptStackFrame, error_c } fatal_without_error_code!(divide_error_handler, "DIVIDE ERROR"); +fatal_without_error_code!(debug_handler, "DEBUG EXCEPTION"); +fatal_without_error_code!(non_maskable_interrupt_handler, "NON-MASKABLE INTERRUPT"); fatal_without_error_code!(invalid_opcode_handler, "INVALID OPCODE"); fatal_without_error_code!(device_not_available_handler, "DEVICE NOT AVAILABLE"); fatal_without_error_code!(x87_floating_point_handler, "X87 FLOATING-POINT EXCEPTION"); fatal_without_error_code!(machine_check_handler, "MACHINE CHECK"); fatal_without_error_code!(simd_floating_point_handler, "SIMD FLOATING-POINT EXCEPTION"); +fatal_with_error_code!(double_fault_handler, "DOUBLE FAULT"); fatal_with_error_code!(invalid_tss_handler, "INVALID TSS"); fatal_with_error_code!(segment_not_present_handler, "SEGMENT NOT PRESENT"); fatal_with_error_code!(stack_segment_fault_handler, "STACK-SEGMENT FAULT"); @@ -134,10 +125,10 @@ fn report_exception(name: &'static str, frame: &InterruptStackFrame, error_code: "kernel" } ); - println!("RIP: {:#X}", frame.instruction_pointer.as_u64()); + println!("RIP: {:#X}", frame.instruction_pointer.as_usize()); println!("CS: {:#X}", frame.code_segment); println!("FLAGS: {:#X}", frame.cpu_flags); - println!("RSP: {:#X}", frame.stack_pointer.as_u64()); + println!("RSP: {:#X}", frame.stack_pointer.as_usize()); println!("SS: {:#X}", frame.stack_segment); if let Some(error_code) = error_code { diff --git a/src/arch/x86_64/port.rs b/src/arch/x86_64/port.rs index cf1c21a..4b4ba60 100644 --- a/src/arch/x86_64/port.rs +++ b/src/arch/x86_64/port.rs @@ -14,46 +14,46 @@ pub unsafe fn read_u8(port: u16) -> u8 { value } -#[inline(always)] -pub unsafe fn read_u8_slice(port: u16, slice: &mut [u8]) { - unsafe { - asm!( - "rep insb", - in("dx") port, - inout("rdi") slice.as_mut_ptr() => _, - inout("rcx") slice.len() => _, - options(nostack, preserves_flags), - ); - } -} +// #[inline(always)] +// pub unsafe fn read_u8_slice(port: u16, slice: &mut [u8]) { +// unsafe { +// asm!( +// "rep insb", +// in("dx") port, +// inout("rdi") slice.as_mut_ptr() => _, +// inout("rcx") slice.len() => _, +// options(nostack, preserves_flags), +// ); +// } +// } -#[inline(always)] -pub unsafe fn read_u16(port: u16) -> u16 { - let value: u16; - unsafe { - asm!( - "in ax, dx", - in("dx") port, - out("ax") value, - options(nomem, nostack, preserves_flags), - ); - } - value -} +// #[inline(always)] +// pub unsafe fn read_u16(port: u16) -> u16 { +// let value: u16; +// unsafe { +// asm!( +// "in ax, dx", +// in("dx") port, +// out("ax") value, +// options(nomem, nostack, preserves_flags), +// ); +// } +// value +// } -#[inline(always)] -pub unsafe fn read_u32(port: u16) -> u32 { - let value: u32; - unsafe { - asm!( - "in eax, dx", - in("dx") port, - out("eax") value, - options(nomem, nostack, preserves_flags), - ); - } - value -} +// #[inline(always)] +// pub unsafe fn read_u32(port: u16) -> u32 { +// let value: u32; +// unsafe { +// asm!( +// "in eax, dx", +// in("dx") port, +// out("eax") value, +// options(nomem, nostack, preserves_flags), +// ); +// } +// value +// } #[inline(always)] pub unsafe fn write_u8(port: u16, value: u8) { @@ -67,39 +67,39 @@ pub unsafe fn write_u8(port: u16, value: u8) { } } -#[inline(always)] -pub unsafe fn write_u8_slice(port: u16, slice: &[u8]) { - unsafe { - asm!( - "rep outsb", - in("dx") port, - inout("rsi") slice.as_ptr() => _, - inout("rcx") slice.len() => _, - options(nostack, preserves_flags), - ); - } -} +// #[inline(always)] +// pub unsafe fn write_u8_slice(port: u16, slice: &[u8]) { +// unsafe { +// asm!( +// "rep outsb", +// in("dx") port, +// inout("rsi") slice.as_ptr() => _, +// inout("rcx") slice.len() => _, +// options(nostack, preserves_flags), +// ); +// } +// } -#[inline(always)] -pub unsafe fn write_u16(port: u16, value: u16) { - unsafe { - asm!( - "out dx, ax", - in("dx") port, - in("ax") value, - options(nomem, nostack, preserves_flags), - ); - } -} +// #[inline(always)] +// pub unsafe fn write_u16(port: u16, value: u16) { +// unsafe { +// asm!( +// "out dx, ax", +// in("dx") port, +// in("ax") value, +// options(nomem, nostack, preserves_flags), +// ); +// } +// } -#[inline(always)] -pub unsafe fn write_u32(port: u16, value: u32) { - unsafe { - asm!( - "out dx, eax", - in("dx") port, - in("eax") value, - options(nomem, nostack, preserves_flags), - ); - } -} +// #[inline(always)] +// pub unsafe fn write_u32(port: u16, value: u32) { +// unsafe { +// asm!( +// "out dx, eax", +// in("dx") port, +// in("eax") value, +// options(nomem, nostack, preserves_flags), +// ); +// } +// } diff --git a/src/boot/limine.rs b/src/boot/limine.rs index bcf02ea..a749294 100644 --- a/src/boot/limine.rs +++ b/src/boot/limine.rs @@ -33,16 +33,16 @@ static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new(); pub struct BootInfo { pub kernel_address: crate::memory::PhysicalAddr, - pub hhdm_offset: u64, + pub hhdm_offset: usize, entries: &'static [&'static limine_api::memmap::Entry], } impl BootInfo { - pub fn memory_regions(&self) -> impl Iterator + '_ { + pub fn memory_regions(&self) -> impl Iterator + Clone + '_ { use crate::memory::{MemoryRegion, MemoryRegionKind}; self.entries.iter().map(|&entry| MemoryRegion { - start: crate::memory::PhysicalAddr::new(entry.base), - length: entry.length, + start: crate::memory::PhysicalAddr::new(entry.base as usize), + length: entry.length as usize, kind: match entry.type_ { limine_api::memmap::MEMMAP_USABLE => MemoryRegionKind::Usable, limine_api::memmap::MEMMAP_RESERVED => MemoryRegionKind::Reserved, @@ -91,8 +91,8 @@ pub fn load_boot_info() -> Result { .entries(); Ok(BootInfo { - kernel_address: crate::memory::PhysicalAddr::new(kernel_address), - hhdm_offset, + kernel_address: crate::memory::PhysicalAddr::new(kernel_address as usize), + hhdm_offset: hhdm_offset as usize, entries: memmap, }) } diff --git a/src/main.rs b/src/main.rs index be2cb20..e39c601 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,13 +15,40 @@ pub extern "C" fn _start() -> ! { serial::init().unwrap(); arch::init(); - let _boot_info = boot::load_boot_info().unwrap(); + let boot_info = boot::load_boot_info().unwrap(); + + let mut allocator = memory::FrameAllocator::new( + boot_info.memory_regions(), + memory::DirectMap::new(boot_info.hhdm_offset), + ) + .expect("failed to create frame allocator"); + + let initial = allocator.free_frames(); + + let first = allocator.alloc().unwrap(); + let second = allocator.alloc().unwrap(); + let third = allocator.alloc().unwrap(); + + assert_ne!(first, second); + assert_ne!(second, third); + assert_eq!(allocator.free_frames(), initial - 3); - let addr = 0xDEADBEEF as *mut u32; unsafe { - *addr = 0xDEADBEEF; + allocator.dealloc(second); + allocator.dealloc(first); + allocator.dealloc(third); } + assert_eq!(allocator.free_frames(), initial); + + let a = allocator.alloc().unwrap(); + let b = allocator.alloc().unwrap(); + let c = allocator.alloc().unwrap(); + + assert_ne!(a, b); + assert_ne!(b, c); + assert_ne!(a, c); + hcf(); } diff --git a/src/memory/frame.rs b/src/memory/frame.rs new file mode 100644 index 0000000..6e409aa --- /dev/null +++ b/src/memory/frame.rs @@ -0,0 +1,354 @@ +use core::ops::Range; + +use crate::memory::{DirectMap, MemoryRegion, MemoryRegionKind, PhysicalAddr, VirtualAddr}; + +pub const FRAME_SIZE: usize = 4096; + +pub fn align_up_to_frame(addr: usize) -> Option { + addr.checked_add(FRAME_SIZE as usize - 1) + .map(|addr| addr & !(FRAME_SIZE - 1)) +} + +pub fn align_down_to_frame(addr: usize) -> usize { + addr & !(FRAME_SIZE - 1) +} + +#[derive(Debug)] +struct Bitmap { + start: VirtualAddr, + bit_count: usize, +} + +// 1 = available, 0 = unavailable +impl Bitmap { + fn new(start: VirtualAddr, bit_count: usize) -> Self { + Self { start, bit_count } + } + + fn is_available(&self, idx: usize) -> bool { + assert!(idx < self.bit_count, "index out of bounds"); + + let byte = idx / 8; + let bit = idx % 8; + + unsafe { self.start.as_mut_ptr::().add(byte).read() & (1 << bit) != 0 } + } + + fn set_available(&mut self, idx: usize, available: bool) { + assert!(idx < self.bit_count, "index out of bounds"); + + let byte = idx / 8; + let bit = idx % 8; + + let val = unsafe { self.start.as_mut_ptr::().add(byte).read() }; + unsafe { + self.start.as_mut_ptr::().add(byte).write(if available { + val | (1 << bit) + } else { + val & !(1 << bit) + }); + } + } + + // fn fill_available(&mut self, range: Range, available: bool) -> bool { + // if range.start > range.end { + // return false; + // } + + // if range.end > self.bit_count { + // return false; + // } + + // if range.is_empty() { + // return true; + // } + + // let ptr = unsafe { self.start.as_mut_ptr::() }; + // let first_byte = range.start / 8; + // let last_byte = (range.end - 1) / 8; + // let start_bit = range.start % 8; + // let end_bit = range.end % 8; + + // unsafe { + // if first_byte == last_byte { + // let width = range.end - range.start; + // let mask = (((1u16 << width) - 1) << start_bit) as u8; + // let byte = ptr.add(first_byte).read(); + + // ptr.add(first_byte) + // .write(if available { byte | mask } else { byte & !mask }); + + // return true; + // } + + // let mut full_start = first_byte; + + // if start_bit != 0 { + // let mask = u8::MAX << start_bit; + // let byte = ptr.add(first_byte).read(); + + // ptr.add(first_byte) + // .write(if available { byte | mask } else { byte & !mask }); + + // full_start += 1; + // } + + // let full_end = range.end / 8; + + // ptr.add(full_start) + // .write_bytes(if available { u8::MAX } else { 0 }, full_end - full_start); + + // if end_bit != 0 { + // let mask = (1u8 << end_bit) - 1; + // let byte = ptr.add(full_end).read(); + + // ptr.add(full_end) + // .write(if available { byte | mask } else { byte & !mask }); + // } + // } + + // true + // } +} + +#[derive(Debug)] +pub enum FrameAllocatorInitError { + AddressOverflow, + NoUsableFrames, + NoBitmapStorage, + BitmapOutsideDirectMap, +} + +// very very simple linked list frame/page allocator +#[derive(Debug)] +pub struct FrameAllocator { + bitmap: Bitmap, + bitmap_start_frame: usize, + bitmap_frame_count: usize, + next_search: usize, + allocatable_frames: usize, + free_frames: usize, + direct_map: DirectMap, +} + +impl FrameAllocator { + pub fn new(regions: I, direct_map: DirectMap) -> Result + where + I: Iterator + Clone, + { + let mut highest_frame: Option = None; + + for region in regions.clone() { + if region.kind != MemoryRegionKind::Usable { + continue; + } + + let range = Self::usable_frame_range(region)?; + if range.is_empty() { + continue; + } + + highest_frame = Some(highest_frame.map_or(range.end, |current| current.max(range.end))); + } + + let highest_frame = highest_frame.ok_or(FrameAllocatorInitError::NoUsableFrames)?; + + let bitmap_bytes = highest_frame.div_ceil(8); + let bitmap_frame_count = bitmap_bytes.div_ceil(FRAME_SIZE); + let bitmap_storage_bytes = bitmap_frame_count + .checked_mul(FRAME_SIZE) + .ok_or(FrameAllocatorInitError::AddressOverflow)?; + + let mut bitmap_start_frame: Option = None; + + for region in regions.clone() { + if region.kind != MemoryRegionKind::Usable { + continue; + } + + let range = Self::usable_frame_range(region)?; + if range.is_empty() { + continue; + } + + if range.len() < bitmap_frame_count { + continue; + } + + bitmap_start_frame = Some(range.start); + break; + } + + if bitmap_start_frame.is_none() { + return Err(FrameAllocatorInitError::NoBitmapStorage); + } + + let bitmap_start_frame = bitmap_start_frame.unwrap(); + + let bitmap_physical_addr = PhysicalAddr::new(bitmap_start_frame * FRAME_SIZE); + let bitmap_virtual = direct_map + .translate(bitmap_physical_addr) + .ok_or(FrameAllocatorInitError::BitmapOutsideDirectMap)?; + + unsafe { + // set everyting to unavailable + core::ptr::write_bytes(bitmap_virtual.as_mut_ptr::(), 0, bitmap_storage_bytes); + } + + let mut allocatable_frames = 0; + let mut free_frames = 0; + let mut bitmap = Bitmap::new(bitmap_virtual, highest_frame); + + let bitmap_end_frame = bitmap_start_frame + .checked_add(bitmap_frame_count) + .ok_or(FrameAllocatorInitError::AddressOverflow)?; + + for region in regions { + if region.kind != MemoryRegionKind::Usable { + continue; + } + + for frame_idx in Self::usable_frame_range(region)? { + let is_bitmap_storage = + frame_idx >= bitmap_start_frame && frame_idx < bitmap_end_frame; + if is_bitmap_storage { + continue; + } + + bitmap.set_available(frame_idx, true); + allocatable_frames += 1; + free_frames += 1; + } + } + + Ok(Self { + bitmap, + bitmap_start_frame: bitmap_start_frame, + bitmap_frame_count, + next_search: bitmap_start_frame + bitmap_frame_count, + allocatable_frames, + free_frames, + direct_map, + }) + } + + fn find_free_in(&self, start: usize, end: usize) -> Option { + (start..end).find(|&index| self.bitmap.is_available(index)) + } + + fn find_free_frame(&self) -> Option { + self.find_free_in(self.next_search, self.bitmap.bit_count) + .or_else(|| self.find_free_in(0, self.next_search)) + } + + pub fn alloc_nozero(&mut self) -> Option { + if self.free_frames == 0 { + return None; + } + + let frame_idx = self.find_free_frame()?; + + self.bitmap.set_available(frame_idx, false); + self.free_frames -= 1; + self.next_search = frame_idx.saturating_add(1); + + Some(PhysicalFrame::from_index(frame_idx)) + } + + pub fn alloc(&mut self) -> Option { + let frame = self.alloc_nozero()?; + + let start = self + .direct_map + .translate(frame.start_address()) + .expect("frame is outside the direct map"); + + unsafe { + core::ptr::write_bytes(start.as_mut_ptr::(), 0, FRAME_SIZE); + } + + Some(frame) + } + + /// # Safety + /// + /// The caller must ensure: + /// - The frame is currently owned by the caller + /// - it is not currently in use + /// - it has not been freed + pub unsafe fn dealloc(&mut self, frame: PhysicalFrame) { + let frame_idx = frame.index(); + + assert!( + frame_idx < self.bitmap.bit_count, + "frame index out of bounds" + ); + assert!( + !self.is_bitmap_storage(frame_idx), + "attempted to free frame allocator bitmap" + ); + assert!( + !self.bitmap.is_available(frame_idx), + "frame is already free" + ); + + self.bitmap.set_available(frame_idx, true); + self.free_frames += 1; + self.next_search = self.next_search.min(frame_idx); + } + + pub const fn free_frames(&self) -> usize { + self.free_frames + } + + pub const fn allocatable_frames(&self) -> usize { + self.allocatable_frames + } + + fn is_bitmap_storage(&self, index: usize) -> bool { + index >= self.bitmap_start_frame + && index < (self.bitmap_start_frame + self.bitmap_frame_count) + } + + fn usable_frame_range( + region: MemoryRegion, + ) -> Result, FrameAllocatorInitError> { + let region_end = region + .start + .as_usize() + .checked_add(region.length) + .ok_or(FrameAllocatorInitError::AddressOverflow)?; + + let start = align_up_to_frame(region.start.as_usize()) + .ok_or(FrameAllocatorInitError::AddressOverflow)?; + let end = align_down_to_frame(region_end); + + Ok((start / FRAME_SIZE)..(end / FRAME_SIZE)) + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PhysicalFrame(PhysicalAddr); + +impl PhysicalFrame { + pub fn from_start_address(address: PhysicalAddr) -> Option { + if address.as_usize() % FRAME_SIZE != 0 { + return None; + } + + Some(Self(address)) + } + + pub fn start_address(&self) -> PhysicalAddr { + self.0 + } + + fn from_index(index: usize) -> Self { + Self(PhysicalAddr::new(index * FRAME_SIZE)) + } + + fn index(&self) -> usize { + self.0.as_usize() / FRAME_SIZE + } +} diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 0ce9a79..d7b0306 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,32 +1,36 @@ +mod frame; + +pub use frame::{FRAME_SIZE, FrameAllocator, PhysicalFrame}; + #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PhysicalAddr(u64); +pub struct PhysicalAddr(usize); impl PhysicalAddr { - pub fn new(addr: u64) -> Self { + pub fn new(addr: usize) -> Self { Self(addr) } - pub const fn as_u64(self) -> u64 { + pub const fn as_usize(self) -> usize { self.0 } } #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct VirtualAddr(u64); +pub struct VirtualAddr(usize); impl VirtualAddr { - pub fn new(addr: u64) -> Self { + pub fn new(addr: usize) -> Self { Self(addr) } - pub const fn as_u64(self) -> u64 { - self.0 + pub const fn as_usize(self) -> usize { + self.0 as usize } pub unsafe fn as_mut_ptr(self) -> *mut T { - self.as_u64() as *mut T + self.as_usize() as *mut T } } @@ -46,6 +50,23 @@ pub enum MemoryRegionKind { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MemoryRegion { pub start: PhysicalAddr, - pub length: u64, + pub length: usize, pub kind: MemoryRegionKind, } + +#[derive(Debug, Clone, Copy)] +pub struct DirectMap { + offset: usize, +} + +impl DirectMap { + pub fn new(offset: usize) -> Self { + Self { offset } + } + + pub fn translate(self, addr: PhysicalAddr) -> Option { + addr.as_usize() + .checked_add(self.offset) + .map(VirtualAddr::new) + } +}