mirror of
https://github.com/xomboverlord/xomb.git
synced 2026-01-11 10:16:36 +01:00
Core kernel infrastructure: - Multiboot2 boot with GRUB, long mode setup, higher-half kernel - Serial port output for debugging - Unified boot info abstraction for future UEFI support Memory management: - Physical frame allocator with bitmap tracking - Page table manipulation via recursive mapping (PML4[510]) - Support for 4KB, 2MB, and 1GB page mappings - TLB invalidation and proper NXE support Build system: - Cargo-based build with custom x86_64 target - Makefile for QEMU and Bochs testing - GRUB ISO generation for multiboot2 boot
543 lines
16 KiB
Rust
543 lines
16 KiB
Rust
//! Physical Frame Allocator
|
|
//!
|
|
//! This module provides a bitmap-based physical frame allocator for tracking
|
|
//! free and used physical memory pages. The allocator is initialized from the
|
|
//! boot memory map and supports:
|
|
//!
|
|
//! - Allocating any free frame
|
|
//! - Allocating a specific physical frame (for device MMIO, etc.)
|
|
//! - Deallocating frames
|
|
//!
|
|
//! The exokernel design allows applications to request specific physical pages
|
|
//! for resources, so the allocator must support targeted allocation.
|
|
|
|
use core::fmt;
|
|
use crate::boot_info::{BootInfo, MemoryMap, MemoryRegionType};
|
|
use super::{PAGE_SIZE, align_down, align_up};
|
|
|
|
/// Physical address wrapper
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
#[repr(transparent)]
|
|
pub struct PhysAddr(u64);
|
|
|
|
impl PhysAddr {
|
|
/// Create a new physical address
|
|
#[inline]
|
|
pub const fn new(addr: u64) -> Self {
|
|
// On x86-64, physical addresses are limited to 52 bits
|
|
Self(addr & 0x000F_FFFF_FFFF_FFFF)
|
|
}
|
|
|
|
/// Create a physical address without masking (for known-good addresses)
|
|
#[inline]
|
|
pub const fn new_unchecked(addr: u64) -> Self {
|
|
Self(addr)
|
|
}
|
|
|
|
/// Get the raw address value
|
|
#[inline]
|
|
pub const fn as_u64(self) -> u64 {
|
|
self.0
|
|
}
|
|
|
|
/// Check if the address is page-aligned
|
|
#[inline]
|
|
pub const fn is_aligned(self) -> bool {
|
|
self.0 & (PAGE_SIZE as u64 - 1) == 0
|
|
}
|
|
|
|
/// Align the address down to the nearest page boundary
|
|
#[inline]
|
|
pub const fn align_down(self) -> Self {
|
|
Self(align_down(self.0, PAGE_SIZE as u64))
|
|
}
|
|
|
|
/// Align the address up to the nearest page boundary
|
|
#[inline]
|
|
pub const fn align_up(self) -> Self {
|
|
Self(align_up(self.0, PAGE_SIZE as u64))
|
|
}
|
|
|
|
/// Get the frame containing this address
|
|
#[inline]
|
|
pub const fn containing_frame(self) -> Frame {
|
|
Frame::containing_address(self)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for PhysAddr {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "PhysAddr({:#x})", self.0)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for PhysAddr {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{:#x}", self.0)
|
|
}
|
|
}
|
|
|
|
impl fmt::LowerHex for PhysAddr {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::LowerHex::fmt(&self.0, f)
|
|
}
|
|
}
|
|
|
|
impl fmt::UpperHex for PhysAddr {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::UpperHex::fmt(&self.0, f)
|
|
}
|
|
}
|
|
|
|
/// A physical memory frame (4KB page)
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct Frame {
|
|
/// Frame number (physical address / PAGE_SIZE)
|
|
number: usize,
|
|
}
|
|
|
|
impl Frame {
|
|
/// Create a frame from a frame number
|
|
#[inline]
|
|
pub const fn from_number(number: usize) -> Self {
|
|
Self { number }
|
|
}
|
|
|
|
/// Create a frame containing the given physical address
|
|
#[inline]
|
|
pub const fn containing_address(addr: PhysAddr) -> Self {
|
|
Self {
|
|
number: (addr.as_u64() / PAGE_SIZE as u64) as usize,
|
|
}
|
|
}
|
|
|
|
/// Create a frame from a page-aligned physical address
|
|
#[inline]
|
|
pub const fn from_start_address(addr: PhysAddr) -> Option<Self> {
|
|
if addr.is_aligned() {
|
|
Some(Self::containing_address(addr))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Get the frame number
|
|
#[inline]
|
|
pub const fn number(self) -> usize {
|
|
self.number
|
|
}
|
|
|
|
/// Get the start address of this frame
|
|
#[inline]
|
|
pub const fn start_address(self) -> PhysAddr {
|
|
PhysAddr::new_unchecked((self.number as u64) * PAGE_SIZE as u64)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Frame {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "Frame({})", self.number)
|
|
}
|
|
}
|
|
|
|
/// Errors that can occur during frame allocation
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum FrameAllocatorError {
|
|
/// No free frames available
|
|
OutOfMemory,
|
|
/// Requested frame is already allocated
|
|
FrameInUse,
|
|
/// Requested frame is outside valid memory
|
|
InvalidFrame,
|
|
/// Allocator not initialized
|
|
NotInitialized,
|
|
}
|
|
|
|
/// Maximum physical memory we support (16 GB = 4M frames)
|
|
/// This determines the size of our bitmap
|
|
const MAX_PHYSICAL_MEMORY: u64 = 16 * 1024 * 1024 * 1024;
|
|
const MAX_FRAMES: usize = (MAX_PHYSICAL_MEMORY / PAGE_SIZE as u64) as usize;
|
|
|
|
/// Bitmap size in u64 words (each u64 tracks 64 frames)
|
|
const BITMAP_WORDS: usize = MAX_FRAMES / 64;
|
|
|
|
/// Bitmap-based physical frame allocator
|
|
///
|
|
/// Uses a bitmap where each bit represents one 4KB frame:
|
|
/// - 0 = frame is free
|
|
/// - 1 = frame is allocated or reserved
|
|
///
|
|
/// The bitmap is stored in kernel BSS, so it's automatically zeroed at boot.
|
|
pub struct FrameAllocator {
|
|
/// Bitmap tracking frame usage (1 = used, 0 = free)
|
|
bitmap: [u64; BITMAP_WORDS],
|
|
/// Total number of frames in the system
|
|
total_frames: usize,
|
|
/// Number of free frames
|
|
free_frames: usize,
|
|
/// Whether the allocator has been initialized
|
|
initialized: bool,
|
|
/// Hint for next allocation search (optimization)
|
|
next_free_hint: usize,
|
|
}
|
|
|
|
impl FrameAllocator {
|
|
/// Create a new, uninitialized frame allocator
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
bitmap: [0; BITMAP_WORDS],
|
|
total_frames: 0,
|
|
free_frames: 0,
|
|
initialized: false,
|
|
next_free_hint: 0,
|
|
}
|
|
}
|
|
|
|
/// Initialize the allocator from the boot memory map
|
|
///
|
|
/// This marks all frames as used initially, then frees the usable regions.
|
|
/// This ensures reserved/MMIO regions stay marked as used.
|
|
pub fn init(&mut self, memory_map: &MemoryMap) {
|
|
// Start with all frames marked as used
|
|
for word in self.bitmap.iter_mut() {
|
|
*word = !0u64;
|
|
}
|
|
|
|
self.total_frames = 0;
|
|
self.free_frames = 0;
|
|
|
|
// Free the usable memory regions
|
|
for region in memory_map.iter() {
|
|
if region.region_type == MemoryRegionType::Usable {
|
|
let start_frame = Frame::containing_address(PhysAddr::new(region.base));
|
|
let end_addr = region.base + region.length;
|
|
let end_frame = Frame::containing_address(PhysAddr::new(end_addr));
|
|
|
|
// Align to frame boundaries (conservative: only free complete frames)
|
|
let first_frame = if PhysAddr::new(region.base).is_aligned() {
|
|
start_frame.number()
|
|
} else {
|
|
start_frame.number() + 1
|
|
};
|
|
let last_frame = end_frame.number();
|
|
|
|
for frame_num in first_frame..last_frame {
|
|
if frame_num < MAX_FRAMES {
|
|
self.mark_free(frame_num);
|
|
self.total_frames += 1;
|
|
self.free_frames += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.initialized = true;
|
|
self.next_free_hint = 0;
|
|
}
|
|
|
|
/// Mark frames used by the kernel as allocated
|
|
///
|
|
/// This should be called after init() to protect kernel memory.
|
|
/// The kernel spans from kernel_physical_base for some size.
|
|
pub fn reserve_kernel(&mut self, kernel_start: PhysAddr, kernel_size: usize) {
|
|
let start_frame = kernel_start.containing_frame().number();
|
|
let num_frames = (kernel_size + PAGE_SIZE - 1) / PAGE_SIZE;
|
|
|
|
for i in 0..num_frames {
|
|
let frame_num = start_frame + i;
|
|
if frame_num < MAX_FRAMES && !self.is_allocated(frame_num) {
|
|
self.mark_used(frame_num);
|
|
if self.free_frames > 0 {
|
|
self.free_frames -= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reserve a specific range of physical addresses
|
|
pub fn reserve_range(&mut self, start: PhysAddr, size: usize) {
|
|
self.reserve_kernel(start, size);
|
|
}
|
|
|
|
/// Allocate a free frame
|
|
///
|
|
/// Returns the allocated frame, or an error if no frames are available.
|
|
pub fn allocate(&mut self) -> Result<Frame, FrameAllocatorError> {
|
|
if !self.initialized {
|
|
return Err(FrameAllocatorError::NotInitialized);
|
|
}
|
|
|
|
if self.free_frames == 0 {
|
|
return Err(FrameAllocatorError::OutOfMemory);
|
|
}
|
|
|
|
// Search for a free frame starting from the hint
|
|
let start_word = self.next_free_hint / 64;
|
|
|
|
// Search from hint to end
|
|
for word_idx in start_word..BITMAP_WORDS {
|
|
if self.bitmap[word_idx] != !0u64 {
|
|
// This word has at least one free bit
|
|
let bit = self.find_free_bit(self.bitmap[word_idx]);
|
|
let frame_num = word_idx * 64 + bit;
|
|
|
|
self.mark_used(frame_num);
|
|
self.free_frames -= 1;
|
|
self.next_free_hint = frame_num + 1;
|
|
|
|
return Ok(Frame::from_number(frame_num));
|
|
}
|
|
}
|
|
|
|
// Wrap around and search from beginning to hint
|
|
for word_idx in 0..start_word {
|
|
if self.bitmap[word_idx] != !0u64 {
|
|
let bit = self.find_free_bit(self.bitmap[word_idx]);
|
|
let frame_num = word_idx * 64 + bit;
|
|
|
|
self.mark_used(frame_num);
|
|
self.free_frames -= 1;
|
|
self.next_free_hint = frame_num + 1;
|
|
|
|
return Ok(Frame::from_number(frame_num));
|
|
}
|
|
}
|
|
|
|
Err(FrameAllocatorError::OutOfMemory)
|
|
}
|
|
|
|
/// Allocate a specific physical frame
|
|
///
|
|
/// This is used when an application requests a specific physical page,
|
|
/// such as for device MMIO or DMA buffers.
|
|
pub fn allocate_specific(&mut self, frame: Frame) -> Result<(), FrameAllocatorError> {
|
|
if !self.initialized {
|
|
return Err(FrameAllocatorError::NotInitialized);
|
|
}
|
|
|
|
let frame_num = frame.number();
|
|
|
|
if frame_num >= MAX_FRAMES {
|
|
return Err(FrameAllocatorError::InvalidFrame);
|
|
}
|
|
|
|
if self.is_allocated(frame_num) {
|
|
return Err(FrameAllocatorError::FrameInUse);
|
|
}
|
|
|
|
self.mark_used(frame_num);
|
|
self.free_frames -= 1;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Deallocate a frame
|
|
pub fn deallocate(&mut self, frame: Frame) -> Result<(), FrameAllocatorError> {
|
|
if !self.initialized {
|
|
return Err(FrameAllocatorError::NotInitialized);
|
|
}
|
|
|
|
let frame_num = frame.number();
|
|
|
|
if frame_num >= MAX_FRAMES {
|
|
return Err(FrameAllocatorError::InvalidFrame);
|
|
}
|
|
|
|
if !self.is_allocated(frame_num) {
|
|
// Double-free is a bug, but we'll just ignore it
|
|
return Ok(());
|
|
}
|
|
|
|
self.mark_free(frame_num);
|
|
self.free_frames += 1;
|
|
|
|
// Update hint if this frame is before the current hint
|
|
if frame_num < self.next_free_hint {
|
|
self.next_free_hint = frame_num;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get the number of free frames
|
|
#[inline]
|
|
pub fn free_count(&self) -> usize {
|
|
self.free_frames
|
|
}
|
|
|
|
/// Get the total number of usable frames
|
|
#[inline]
|
|
pub fn total_count(&self) -> usize {
|
|
self.total_frames
|
|
}
|
|
|
|
/// Get the amount of free memory in bytes
|
|
#[inline]
|
|
pub fn free_memory(&self) -> usize {
|
|
self.free_frames * PAGE_SIZE
|
|
}
|
|
|
|
/// Get the total amount of usable memory in bytes
|
|
#[inline]
|
|
pub fn total_memory(&self) -> usize {
|
|
self.total_frames * PAGE_SIZE
|
|
}
|
|
|
|
/// Check if a frame is allocated
|
|
#[inline]
|
|
fn is_allocated(&self, frame_num: usize) -> bool {
|
|
let word_idx = frame_num / 64;
|
|
let bit_idx = frame_num % 64;
|
|
(self.bitmap[word_idx] & (1u64 << bit_idx)) != 0
|
|
}
|
|
|
|
/// Mark a frame as used
|
|
#[inline]
|
|
fn mark_used(&mut self, frame_num: usize) {
|
|
let word_idx = frame_num / 64;
|
|
let bit_idx = frame_num % 64;
|
|
self.bitmap[word_idx] |= 1u64 << bit_idx;
|
|
}
|
|
|
|
/// Mark a frame as free
|
|
#[inline]
|
|
fn mark_free(&mut self, frame_num: usize) {
|
|
let word_idx = frame_num / 64;
|
|
let bit_idx = frame_num % 64;
|
|
self.bitmap[word_idx] &= !(1u64 << bit_idx);
|
|
}
|
|
|
|
/// Find the first free bit (0) in a word
|
|
#[inline]
|
|
fn find_free_bit(&self, word: u64) -> usize {
|
|
// Find the first zero bit using bitwise NOT and trailing zeros
|
|
(!word).trailing_zeros() as usize
|
|
}
|
|
}
|
|
|
|
// Global frame allocator instance
|
|
use spin::Mutex;
|
|
|
|
/// Global frame allocator, protected by a spinlock
|
|
pub static FRAME_ALLOCATOR: Mutex<FrameAllocator> = Mutex::new(FrameAllocator::new());
|
|
|
|
/// Initialize the global frame allocator
|
|
pub fn init(boot_info: &BootInfo) {
|
|
let mut allocator = FRAME_ALLOCATOR.lock();
|
|
allocator.init(&boot_info.memory_map);
|
|
|
|
// Reserve the kernel's physical memory
|
|
// The kernel is loaded at 1MB and spans some amount
|
|
// We conservatively reserve 16MB for the kernel and its data structures
|
|
allocator.reserve_kernel(
|
|
PhysAddr::new(boot_info.kernel_physical_base),
|
|
16 * 1024 * 1024,
|
|
);
|
|
|
|
// Reserve the first 1MB (real mode IVT, BIOS data, etc.)
|
|
allocator.reserve_range(PhysAddr::new(0), 1024 * 1024);
|
|
}
|
|
|
|
/// Allocate a physical frame from the global allocator
|
|
pub fn allocate_frame() -> Result<Frame, FrameAllocatorError> {
|
|
FRAME_ALLOCATOR.lock().allocate()
|
|
}
|
|
|
|
/// Allocate a specific physical frame
|
|
pub fn allocate_frame_at(addr: PhysAddr) -> Result<Frame, FrameAllocatorError> {
|
|
let frame = addr.containing_frame();
|
|
FRAME_ALLOCATOR.lock().allocate_specific(frame)?;
|
|
Ok(frame)
|
|
}
|
|
|
|
/// Deallocate a physical frame
|
|
pub fn deallocate_frame(frame: Frame) -> Result<(), FrameAllocatorError> {
|
|
FRAME_ALLOCATOR.lock().deallocate(frame)
|
|
}
|
|
|
|
/// Get current memory statistics
|
|
pub fn memory_stats() -> (usize, usize) {
|
|
let allocator = FRAME_ALLOCATOR.lock();
|
|
(allocator.free_memory(), allocator.total_memory())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::boot_info::MemoryMap;
|
|
|
|
fn create_test_memory_map() -> MemoryMap {
|
|
let mut map = MemoryMap::empty();
|
|
// Add some usable memory: 1MB to 128MB
|
|
map.add(0x100000, 127 * 1024 * 1024, MemoryRegionType::Usable);
|
|
map
|
|
}
|
|
|
|
#[test]
|
|
fn test_phys_addr() {
|
|
let addr = PhysAddr::new(0x1000);
|
|
assert_eq!(addr.as_u64(), 0x1000);
|
|
assert!(addr.is_aligned());
|
|
|
|
let unaligned = PhysAddr::new(0x1234);
|
|
assert!(!unaligned.is_aligned());
|
|
assert_eq!(unaligned.align_down().as_u64(), 0x1000);
|
|
assert_eq!(unaligned.align_up().as_u64(), 0x2000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_frame() {
|
|
let frame = Frame::from_number(256);
|
|
assert_eq!(frame.number(), 256);
|
|
assert_eq!(frame.start_address().as_u64(), 256 * 4096);
|
|
|
|
let frame2 = Frame::containing_address(PhysAddr::new(0x100500));
|
|
assert_eq!(frame2.number(), 0x100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_allocator_init() {
|
|
let mut allocator = FrameAllocator::new();
|
|
let map = create_test_memory_map();
|
|
allocator.init(&map);
|
|
|
|
assert!(allocator.initialized);
|
|
assert!(allocator.free_frames > 0);
|
|
assert!(allocator.total_frames > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_allocate_deallocate() {
|
|
let mut allocator = FrameAllocator::new();
|
|
let map = create_test_memory_map();
|
|
allocator.init(&map);
|
|
|
|
let initial_free = allocator.free_count();
|
|
|
|
// Allocate a frame
|
|
let frame = allocator.allocate().unwrap();
|
|
assert_eq!(allocator.free_count(), initial_free - 1);
|
|
|
|
// Deallocate it
|
|
allocator.deallocate(frame).unwrap();
|
|
assert_eq!(allocator.free_count(), initial_free);
|
|
}
|
|
|
|
#[test]
|
|
fn test_allocate_specific() {
|
|
let mut allocator = FrameAllocator::new();
|
|
let map = create_test_memory_map();
|
|
allocator.init(&map);
|
|
|
|
// Allocate a specific frame in usable memory
|
|
let frame = Frame::from_number(512); // 2MB, should be in usable region
|
|
allocator.allocate_specific(frame).unwrap();
|
|
|
|
// Try to allocate it again - should fail
|
|
assert_eq!(
|
|
allocator.allocate_specific(frame),
|
|
Err(FrameAllocatorError::FrameInUse)
|
|
);
|
|
}
|
|
}
|