diff --git a/os/Cargo.lock b/os/Cargo.lock index 866197f2..4a33af91 100644 --- a/os/Cargo.lock +++ b/os/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "critical-section" version = "1.2.0" @@ -27,6 +33,7 @@ dependencies = [ name = "os" version = "0.1.0" dependencies = [ + "bitflags", "lock_api", "riscv", "sbi-rt", diff --git a/os/Cargo.toml b/os/Cargo.toml index da6b057b..c95d43ef 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -8,3 +8,4 @@ sbi-rt = { version = "0.0.2", features = ["legacy"] } riscv = "0.15.0" talc = { version = "4" } lock_api = "0.4" +bitflags = {version = "1.2.1"} diff --git a/os/src/arch/riscv/mm/mod.rs b/os/src/arch/riscv/mm/mod.rs index 93edf2f6..26490d71 100644 --- a/os/src/arch/riscv/mm/mod.rs +++ b/os/src/arch/riscv/mm/mod.rs @@ -17,6 +17,12 @@ //! - Physical addresses are extracted using bitwise AND with `PADDR_MASK` //! - Virtual addresses are created using bitwise OR with `VADDR_START` +mod page_table; +mod page_table_entry; + +pub use page_table::PageTableInner; +pub use page_table_entry::PageTableEntry; + /// starting address of the virtual address space in SV39 /// /// This constant defines the starting position of the kernel's high virtual address space. diff --git a/os/src/arch/riscv/mm/page_table.rs b/os/src/arch/riscv/mm/page_table.rs new file mode 100644 index 00000000..447d3a48 --- /dev/null +++ b/os/src/arch/riscv/mm/page_table.rs @@ -0,0 +1,344 @@ +use super::PageTableEntry; +use crate::mm::address::{AlignOps, PageNum, Ppn, PpnRange, UsizeConvert, Vaddr, Vpn, VpnRange, Paddr, ConvertablePaddr}; +use crate::mm::frame_allocator::FrameTracker; +use crate::mm::page_table::{ + PageSize, PageTableInner as PageTableInnerTrait, PagingError, PagingResult, UniversalPTEFlag, PageTableEntry as PageTableEntryTrait +}; +use alloc::vec::Vec; + +pub struct PageTableInner { + root: Ppn, + // only store middle-level frames here + frames: Vec, + is_user: bool, +} + +impl PageTableInnerTrait for PageTableInner { + const LEVELS: usize = 3; + const MAX_VA_BITS: usize = 39; + const MAX_PA_BITS: usize = 56; + + fn tlb_flush(vpn: Vpn) { + let vaddr = vpn.start_addr(); + unsafe { + core::arch::asm!( + "sfence.vma {0} {1}", + in(reg) vaddr.as_usize(), + in(reg) 0usize + ) + } + } + + fn tlb_flush_all() { + unsafe { core::arch::asm!("sfence.vma") } + } + + fn is_user_table(&self) -> bool { + self.is_user + } + + fn activate(ppn: Ppn) { + let satp_value = ppn_to_satp(ppn); + unsafe { + core::arch::asm!( + "csrw satp, {0}", + "sfence.vma", + in(reg) satp_value + ) + } + } + + fn activating_table_ppn() -> Ppn { + let satp_value: usize; + unsafe { + core::arch::asm!("csrr {0}, satp", out(reg) satp_value); + } + let ppn = satp_value & ((1usize << 44) - 1); // lower 44 bits for PPN in SV39 + Ppn::from_usize(ppn) + } + + fn new() -> Self { + let frame = crate::mm::frame_allocator::alloc_frame().unwrap(); + Self { + root: frame.ppn(), + frames: alloc::vec![frame], + is_user: true, + } + } + fn from_ppn(ppn: Ppn) -> Self { + Self { + root: ppn, + frames: Vec::new(), + is_user: true, + } + } + fn new_as_kernel_table() -> Self { + let frame = crate::mm::frame_allocator::alloc_frame().unwrap(); + Self { + root: frame.ppn(), + frames: alloc::vec![frame], + is_user: false, + } + } + + fn root_ppn(&self) -> Ppn { + self.root + } + + fn get_entry(&self, vpn: Vpn, level: usize) -> Option<(super::PageTableEntry, PageSize)> { + if level >= Self::LEVELS { + return None; + } + + let mut ppn = self.root; + let vpn_value = vpn.as_usize(); + + // Walk through page table levels from root to the target level + for current_level in (level..Self::LEVELS).rev() { + let idx = (vpn_value >> (9 * current_level)) & 0x1ff; + let pte_array = unsafe { + core::slice::from_raw_parts( + ppn.start_addr().to_vaddr().as_usize() as *const super::PageTableEntry, + 512, + ) + }; + let pte = &pte_array[idx]; + + if !pte.is_valid() { + return None; + } + + if current_level == level { + let page_size = match level { + 2 => PageSize::Size1G, + 1 => PageSize::Size2M, + 0 => PageSize::Size4K, + _ => unreachable!(), + }; + return Some((*pte, page_size)); + } + + ppn = pte.ppn(); + } + + None + } + + fn translate(&self, vaddr: Vaddr) -> Option { + let vpn = Vpn::from_addr_ceil(vaddr); + let offset = vaddr.as_usize() & 0xfff; // Lower 12 bits for page offset + + match self.walk(vpn) { + Ok((ppn, page_size, _flags)) => { + let paddr_base = match page_size { + PageSize::Size4K => ppn.start_addr().as_usize(), + PageSize::Size2M => { + // For 2M pages, preserve the lower 21 bits from vaddr + let offset_2m = vaddr.as_usize() & 0x1f_ffff; + ppn.start_addr().as_usize() + offset_2m - offset + } + PageSize::Size1G => { + // For 1G pages, preserve the lower 30 bits from vaddr + let offset_1g = vaddr.as_usize() & 0x3fff_ffff; + ppn.start_addr().as_usize() + offset_1g - offset + } + }; + Some(Paddr::from_usize(paddr_base + offset)) + } + Err(_) => None, + } + } + + fn map( + &mut self, + vpn: Vpn, + ppn: Ppn, + page_size: PageSize, + flags: UniversalPTEFlag, + ) -> PagingResult<()> { + // Validate flags: leaf pages must have at least one of R/W/X set + if !flags.intersects( + UniversalPTEFlag::Readable + | UniversalPTEFlag::Writeable + | UniversalPTEFlag::Executable, + ) { + return Err(PagingError::InvalidFlags); + } + + // Determine the target level based on page size + let target_level = match page_size { + PageSize::Size1G => 2, + PageSize::Size2M => 1, + PageSize::Size4K => 0, + }; + + let mut current_ppn = self.root; + let vpn_value = vpn.as_usize(); + + // Walk through page table levels from root to target level + for level in (target_level..Self::LEVELS).rev() { + let idx = (vpn_value >> (9 * level)) & 0x1ff; + let pte_array = unsafe { + core::slice::from_raw_parts_mut( + current_ppn.start_addr().to_vaddr().as_usize() as *mut super::PageTableEntry, + 512, + ) + }; + let pte = &mut pte_array[idx]; + + if level == target_level { + // We've reached the target level, create leaf entry + if pte.is_valid() { + return Err(PagingError::AlreadyMapped); + } + *pte = super::PageTableEntry::new_leaf(ppn, flags | UniversalPTEFlag::Valid); + return Ok(()); + } else { + // Intermediate level - need to continue walking + if !pte.is_valid() { + // Allocate a new page table for this level + let new_frame = crate::mm::frame_allocator::alloc_frame() + .ok_or(PagingError::FrameAllocFailed)?; + let new_ppn = new_frame.ppn(); + + // Clear the new page table + let new_table = unsafe { + core::slice::from_raw_parts_mut( + new_ppn.start_addr().to_vaddr().as_usize() + as *mut super::PageTableEntry, + 512, + ) + }; + for entry in new_table.iter_mut() { + *entry = super::PageTableEntry::empty(); + } + + *pte = super::PageTableEntry::new_table(new_ppn); + self.frames.push(new_frame); + } else if pte.is_huge() { + // There's already a huge page mapping here + return Err(PagingError::HugePageConflict); + } + + current_ppn = pte.ppn(); + } + } + + Err(PagingError::InvalidAddress) + } + + fn unmap(&mut self, vpn: Vpn) -> PagingResult<()> { + let mut current_ppn = self.root; + let vpn_value = vpn.as_usize(); + + // Walk through page table to find the leaf entry + for level in (0..Self::LEVELS).rev() { + let idx = (vpn_value >> (9 * level)) & 0x1ff; + let pte_array = unsafe { + core::slice::from_raw_parts_mut( + current_ppn.start_addr().to_vaddr().as_usize() as *mut super::PageTableEntry, + 512, + ) + }; + let pte = &mut pte_array[idx]; + + if !pte.is_valid() { + return Err(PagingError::NotMapped); + } + + // Check if this is a leaf entry (has R/W/X permissions or is level 0) + if pte.is_huge() || level == 0 { + Self::tlb_flush(vpn); + return Ok(()); + } + + current_ppn = pte.ppn(); + } + + Err(PagingError::NotMapped) + } + + fn mvmap( + &mut self, + vpn: Vpn, + target_ppn: Ppn, + page_size: PageSize, + flags: UniversalPTEFlag, + ) -> PagingResult<()> { + // First unmap the old mapping + self.unmap(vpn)?; + // Then map to the new physical page + self.map(vpn, target_ppn, page_size, flags) + } + + fn update_flags(&mut self, vpn: Vpn, flags: UniversalPTEFlag) -> PagingResult<()> { + let mut current_ppn = self.root; + let vpn_value = vpn.as_usize(); + + // Walk through page table to find the leaf entry + for level in (0..Self::LEVELS).rev() { + let idx = (vpn_value >> (9 * level)) & 0x1ff; + let pte_array = unsafe { + core::slice::from_raw_parts_mut( + current_ppn.start_addr().to_vaddr().as_usize() as *mut super::PageTableEntry, + 512, + ) + }; + let pte = &mut pte_array[idx]; + + if !pte.is_valid() { + return Err(PagingError::NotMapped); + } + + // Check if this is a leaf entry (has R/W/X permissions or is level 0) + if pte.is_huge() || level == 0 { + pte.set_flags(flags | UniversalPTEFlag::Valid); + Self::tlb_flush(vpn); + return Ok(()); + } + + current_ppn = pte.ppn(); + } + + Err(PagingError::NotMapped) + } + + fn walk(&self, vpn: Vpn) -> PagingResult<(Ppn, PageSize, UniversalPTEFlag)> { + let mut ppn = self.root; + let vpn_value = vpn.as_usize(); + + // SV39: VPN[2] = bits[38:30], VPN[1] = bits[29:21], VPN[0] = bits[20:12] + for level in (0..Self::LEVELS).rev() { + let idx = (vpn_value >> (9 * level)) & 0x1ff; + let pte_array = unsafe { + core::slice::from_raw_parts( + ppn.start_addr().to_vaddr().as_usize() as *const super::PageTableEntry, + 512, + ) + }; + let pte = &pte_array[idx]; + + if !pte.is_valid() { + return Err(PagingError::NotMapped); + } + + if pte.is_huge() || level == 0 { + let page_size = match level { + 2 => PageSize::Size1G, + 1 => PageSize::Size2M, + 0 => PageSize::Size4K, + _ => unreachable!(), + }; + return Ok((pte.ppn(), page_size, pte.flags())); + } + + ppn = pte.ppn(); + } + + Err(PagingError::NotMapped) + } +} + +fn ppn_to_satp(ppn: Ppn) -> usize { + ppn.as_usize() | (8usize << 60) // MODE=8 for SV39 +} diff --git a/os/src/arch/riscv/mm/page_table_entry.rs b/os/src/arch/riscv/mm/page_table_entry.rs new file mode 100644 index 00000000..892e2699 --- /dev/null +++ b/os/src/arch/riscv/mm/page_table_entry.rs @@ -0,0 +1,130 @@ +use crate::mm::address::{Ppn, UsizeConvert}; +use crate::mm::page_table::PageTableEntry as PageTableEntryTrait; +use crate::mm::page_table::{UniversalConvertableFlag, UniversalPTEFlag}; + +bitflags::bitflags! { + pub struct SV39PTEFlags: usize { + const VALID = 1 << 0; // Indicates whether the entry is valid + const READ = 1 << 1; // Indicates whether the page is readable + const WRITE = 1 << 2; // Indicates whether the page is writeable + const EXECUTE = 1 << 3; // Indicates whether the page is executable + const USER = 1 << 4; // Indicates whether the page is accessible from user mode + const GLOBAL = 1 << 5; // Indicates whether the page is global + const ACCESSED = 1 << 6; // Indicates whether the page has been accessed + const DIRTY = 1 << 7; // Indicates whether the page has been written to + const _RESERVED = 1 << 8; // Reserved for future use (according to SV39 spec) + } +} + + + +/* + * SV39 Page Table Entry (PTE) format: + * ------------------------------------------------ + * | Bits | Description | + * ------------------------------------------------ + * | 0-7 | Flags (Valid, Read, Write, Execute, | + * | | User, Global, Accessed, Dirty) | + * ------------------------------------------------ + * | 8-9 | Reserved (must be zero) | + * ------------------------------------------------ + * | 10-53 | Physical Page Number (PPN) | + * ------------------------------------------------ + * | 54-63 | Reserved (must be zero) | + * ------------------------------------------------ + */ + +const SV39_PTE_FLAG_MASK: usize = 0xff; // Lower 8 bits for SV39 PTE flags +const SV39_PTE_PPN_OFFSET: usize = 10; // PPN starts from bit 10 +const SV39_PTE_PPN_MASK: u64 = 0x000f_ffff_ffff_c00; // Bits 10-53 for PPN + +impl UniversalConvertableFlag for SV39PTEFlags { + fn from_universal(flag: UniversalPTEFlag) -> Self { + Self::from_bits(flag.bits() & SV39_PTE_FLAG_MASK).unwrap() + } + + fn to_universal(&self) -> UniversalPTEFlag { + UniversalPTEFlag::from_bits(self.bits() & SV39_PTE_FLAG_MASK).unwrap() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct PageTableEntry(u64); + +impl PageTableEntryTrait for PageTableEntry { + type Bits = u64; + + fn from_bits(bits: Self::Bits) -> Self { + PageTableEntry(bits) + } + + fn to_bits(&self) -> Self::Bits { + self.0 + } + + fn empty() -> Self { + PageTableEntry(0) + } + + fn new_leaf(ppn: Ppn, flags: UniversalPTEFlag) -> Self { + let ppn_bits: u64 = ppn.as_usize() as u64; + let sv39_flags = SV39PTEFlags::from_universal(flags); + PageTableEntry((ppn_bits << SV39_PTE_PPN_OFFSET) | (sv39_flags.bits() as u64)) + } + + fn new_table(ppn: Ppn) -> Self { + let ppn_bits: u64 = ppn.as_usize() as u64; + let sv39_flags = SV39PTEFlags::VALID; // Table entries must be valid + PageTableEntry((ppn_bits << SV39_PTE_PPN_OFFSET) | (sv39_flags.bits() as u64)) + } + + fn is_valid(&self) -> bool { + (self.0 & SV39PTEFlags::VALID.bits() as u64) != 0 + } + + fn is_huge(&self) -> bool { + // In SV39, we can't directly determine huge pages from the PTE alone. + let sv39_flags = SV39PTEFlags::from_bits((self.0 & SV39_PTE_FLAG_MASK as u64) as usize).unwrap(); + sv39_flags.intersects(SV39PTEFlags::union(SV39PTEFlags::READ, SV39PTEFlags::EXECUTE).union(SV39PTEFlags::WRITE)) + } + + fn is_empty(&self) -> bool { + self.0 == 0 + } + + fn ppn(&self) -> Ppn { + let ppn = (self.0 & SV39_PTE_PPN_MASK) >> SV39_PTE_PPN_OFFSET; + Ppn::from_usize(ppn as usize) + } + + fn flags(&self) -> UniversalPTEFlag { + let sv39_flags = SV39PTEFlags::from_bits((self.0 & SV39_PTE_FLAG_MASK as u64) as usize).unwrap(); + sv39_flags.to_universal() + } + + fn set_ppn(&mut self, ppn: Ppn) { + let ppn_bits = ppn.as_usize() as u64; + self.0 = (self.0 & !SV39_PTE_PPN_MASK) | (ppn_bits << SV39_PTE_PPN_OFFSET); + } + + fn set_flags(&mut self, flags: UniversalPTEFlag) { + let sv39_flags = SV39PTEFlags::from_universal(flags); + self.0 = (self.0 & !(SV39_PTE_FLAG_MASK as u64)) | (sv39_flags.bits() as u64); + } + + fn clear(&mut self) { + self.0 = 0; + } + + // current_flags & !flags + fn remove_flags(&mut self, flags: UniversalPTEFlag) { + let current_flags = self.flags(); + let new_flags = current_flags & !flags; + self.set_flags(new_flags); + } + + // current_flags | flags + fn add_flags(&mut self, flags: UniversalPTEFlag) { + self.set_flags(self.flags() | flags); + } +} diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index 83765fc7..d8aba2b2 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -8,10 +8,12 @@ //! - [`address`]: Address and page number abstractions //! - [`frame_allocator`]: Physical frame allocation //! - [`global_allocator`]: Global heap allocator +//! - [`page_table`]: Page table abstractions and implementations(arch-independent) -mod address; -mod frame_allocator; -mod global_allocator; +pub mod address; +pub mod frame_allocator; +pub mod global_allocator; +pub mod page_table; pub use frame_allocator::init_frame_allocator; pub use global_allocator::init_heap; diff --git a/os/src/mm/page_table/mod.rs b/os/src/mm/page_table/mod.rs new file mode 100644 index 00000000..b8dcc5b4 --- /dev/null +++ b/os/src/mm/page_table/mod.rs @@ -0,0 +1,34 @@ +mod page_table; +mod page_table_entry; + +pub use page_table::*; +pub use page_table_entry::*; + +pub type ActivePageTableInner = crate::arch::mm::PageTableInner; + +/// Supported page sizes +pub enum PageSize { + Size4K = 0x1000, + Size2M = 0x20_0000, + Size1G = 0x4000_0000, + // ban bigger sizes for now +} + +/// Errors that can occur during paging operations +pub enum PagingError { + /// The virtual address is not mapped + NotMapped, + /// The virtual address is already mapped + AlreadyMapped, + /// Invalid address provided + InvalidAddress, + /// The operation failed due to a conflict with an existing huge page mapping. + HugePageConflict, + /// Invalid Flags provided + InvalidFlags, + /// Failed to alloc frame + FrameAllocFailed, +} + +pub type PagingResult = Result; + diff --git a/os/src/mm/page_table/page_table.rs b/os/src/mm/page_table/page_table.rs new file mode 100644 index 00000000..ae93c5d6 --- /dev/null +++ b/os/src/mm/page_table/page_table.rs @@ -0,0 +1,51 @@ +use super::{ActivePageTableInner, PageSize, PageTableEntry, PagingResult, UniversalPTEFlag}; +use crate::mm::address::{Paddr, Ppn, PpnRange, Vaddr, Vpn, VpnRange}; + +pub trait PageTableInner +where + T: PageTableEntry, +{ + const LEVELS: usize; + const MAX_VA_BITS: usize; + const MAX_PA_BITS: usize; + + fn tlb_flush(vpn: Vpn); + fn tlb_flush_all(); + + fn is_user_table(&self) -> bool; + + fn activate(ppn: Ppn); + fn activating_table_ppn() -> Ppn; + + fn new() -> Self; + fn from_ppn(ppn: Ppn) -> Self; + fn new_as_kernel_table() -> Self; + + fn root_ppn(&self) -> Ppn; + + fn get_entry(&self, vpn: Vpn, level: usize) -> Option<(T, PageSize)>; + + fn translate(&self, vaddr: Vaddr) -> Option; + + fn map( + &mut self, + vpn: Vpn, + ppn: Ppn, + page_size: PageSize, + flags: UniversalPTEFlag, + ) -> PagingResult<()>; + + fn unmap(&mut self, vpn: Vpn) -> PagingResult<()>; + + fn mvmap( + &mut self, + vpn: Vpn, + target_ppn: Ppn, + page_size: PageSize, + flags: UniversalPTEFlag, + ) -> PagingResult<()>; + + fn update_flags(&mut self, vpn: Vpn, flags: UniversalPTEFlag) -> PagingResult<()>; + + fn walk(&self, vpn: Vpn) -> PagingResult<(Ppn, PageSize, UniversalPTEFlag)>; +} diff --git a/os/src/mm/page_table/page_table_entry.rs b/os/src/mm/page_table/page_table_entry.rs new file mode 100644 index 00000000..39e6be7e --- /dev/null +++ b/os/src/mm/page_table/page_table_entry.rs @@ -0,0 +1,53 @@ +use crate::mm::address::Ppn; + +bitflags::bitflags! { + /// Designs a universal set of page table entry flags that can be mapped to various architectures. + /// Be same to Risc-V SV39 in lower 8 bits.(add more flag-bit if needed for other archs) + pub struct UniversalPTEFlag: usize { + + // ---- RISC-V SV39 compatible flags ---- + const Valid = 1 << 0; // Indicates whether the entry is valid + const Readable = 1 << 1; // Indicates whether the page is readable + const Writeable = 1 << 2; // Indicates whether the page is writeable + const Executable = 1 << 3; // Indicates whether the page is executable + const UserAccessible = 1 << 4; // Indicates whether the page is accessible from user mode + const Global = 1 << 5; // Indicates whether the page is global + const Accessed = 1 << 6; // Indicates whether the page has been accessed + const Dirty = 1 << 7; // Indicates whether the page has been written to + + // ---- Additional universal flags ---- + const Huge = 1 << 8; // Indicates whether the page is a huge page () + } +} + +pub trait UniversalConvertableFlag { + fn from_universal(flag: UniversalPTEFlag) -> Self; + fn to_universal(&self) -> UniversalPTEFlag; +} + +pub trait PageTableEntry { + type Bits; + + fn from_bits(bits: Self::Bits) -> Self; + fn to_bits(&self) -> Self::Bits; + fn empty() -> Self; + fn new_leaf(ppn: Ppn, flags: UniversalPTEFlag) -> Self; + fn new_table(ppn: Ppn) -> Self; + + fn is_valid(&self) -> bool; + fn is_huge(&self) -> bool; + fn is_empty(&self) -> bool; + + fn ppn(&self) -> Ppn; + fn flags(&self) -> UniversalPTEFlag; + + fn set_ppn(&mut self, ppn: Ppn); + fn set_flags(&mut self, flags: UniversalPTEFlag); + fn clear(&mut self); + + // current_flags & !flags + fn remove_flags(&mut self, flags: UniversalPTEFlag); + + // current_flags | flags + fn add_flags(&mut self, flags: UniversalPTEFlag); +} diff --git a/os/src/mm/pagetable/.gitkeep b/os/src/mm/pagetable/.gitkeep deleted file mode 100644 index e69de29b..00000000