From afcef918a31501e8526a2ed1dc479c742db4feb6 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 21 Aug 2026 11:18:40 -0500 Subject: [PATCH] feat: major overhaul and cleanup of paging and mm related code --- Cargo.toml | 5 + Makefile | 47 +- README.md | 3 +- build.rs | 3 + src/arch/mod.rs | 3 + src/arch/x86_64/cpu.rs | 92 ++ src/arch/x86_64/linker.ld | 19 +- src/arch/x86_64/mod.rs | 18 +- src/arch/x86_64/paging.rs | 1013 +++++++++++----------- src/arch/x86_64/x86_64-unknown-none.json | 40 +- src/boot/limine.rs | 97 ++- src/main.rs | 29 +- src/memory/address_space.rs | 269 ++++++ src/memory/frame.rs | 3 +- src/memory/mod.rs | 27 +- 15 files changed, 1040 insertions(+), 628 deletions(-) create mode 100644 build.rs create mode 100644 src/arch/x86_64/cpu.rs create mode 100644 src/memory/address_space.rs diff --git a/Cargo.toml b/Cargo.toml index d6e1b02..63be4f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,8 @@ edition = "2024" [dependencies] limine = "0.6.5" + +[[bin]] +name = "dusk" +test = false +bench = false diff --git a/Makefile b/Makefile index fdc46f9..7f8ae84 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ARTIFACTS_PATH ?= bin IMAGE_NAME ?= dusk.iso -MODE ?= release +MODE ?= debug ARCH ?= x86_64 MEMORY ?= 512M # In MB @@ -36,18 +36,9 @@ ifneq (${GDB},) QEMU_OPTS += -s -S endif -ifeq (${ARCH},aarch64) - LIMINE_BOOT_VARIATION := AA64 - UEFI := true -endif - ifneq (${UEFI},) RUN_OPTS := ovmf-${ARCH} - ifeq (${ARCH},aarch64) - QEMU_OPTS += -M virt -bios ovmf/ovmf-${ARCH}/OVMF.fd - else - QEMU_OPTS += -bios ovmf/ovmf-${ARCH}/OVMF.fd - endif + QEMU_OPTS += -bios ovmf/ovmf-${ARCH}/OVMF.fd endif .PHONY: all build @@ -57,7 +48,7 @@ all: build build: prepare-bin-files compile-bootloader compile-binaries run-scripts build-iso check: - cargo check + cargo check -Zjson-target-spec prepare-bin-files: # Remove ISO and everything in the bin directory @@ -69,35 +60,11 @@ prepare-bin-files: mkdir -p ${ISO_PATH} # mkdir -p ${INITRAMFS_PATH} mkdir -p ${ARTIFACTS_PATH}/mnt - -#copy-initramfs-files: -# echo "Hello World from Initramfs" > ${INITRAMFS_PATH}/example.txt -# echo "Second file for testing" > ${INITRAMFS_PATH}/example2.txt -# mkdir -p ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/ -# mkdir ${INITRAMFS_PATH}/mnt/ -# echo "Nexted file reads!!" > ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/yeah.txt - -#compile-initramfs: copy-initramfs-files -# # Make squashfs without compression temporaily so I can get it working before I have to write a gzip driver -# mksquashfs ${INITRAMFS_PATH} ${ARTIFACTS_PATH}/initramfs.img ${MKSQUASHFS_OPTS} - run-scripts: # Place the build ID into the binary so it can be read at runtime @HASH=$$(md5sum ${KERNEL_FILE} | cut -c1-12) && \ sed -i "s/__BUILD_ID__/$${HASH}/" ${KERNEL_FILE} -#ifeq (${EXPORT_SYMBOLS},true) -# nm ${KERNEL_FILE} > scripts/symbols.table -# @if [ ! -d "scripts/rustc_demangle" ]; then \ -# git clone "https://github.com/juls0730/rustc_demangle.py" "scripts/rustc_demangle"; \ -# fi -# python scripts/demangle-symbols.py -# mv scripts/symbols.table ${INITRAMFS_PATH}/ -#endif - -# python scripts/font.py -# mv scripts/font.psf ${INITRAMFS_PATH}/ - #python scripts/initramfs-test.py 100 ${INITRAMFS_PATH}/ copy-iso-files: @@ -167,12 +134,6 @@ ovmf-x86_64: cd ovmf/ovmf-x86_64 && curl -Lo OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd; \ fi -ovmf-aarch64: - mkdir -p ovmf/ovmf-aarch64 - @if [ ! -d "ovmf/ovmf-aarch64/OVMF.fd" ]; then \ - cd ovmf/ovmf-aarch64 && curl -o OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEAARCH64_QEMU_EFI.fd; \ - fi - # In debug mode, open a terminal and run this command: # gdb target/x86_64-unknown-none/debug/CappuccinOS.elf -ex "target remote :1234" @@ -183,7 +144,7 @@ run-x86_64: tmux new-session -d -s qemu 'qemu-system-x86_64 ${QEMU_OPTS}' run-x86_64-serial: - qemu-system-x86_64 ${QEMU_OPTS} -boot d -display none -serial stdio -monitor none -no-reboot -no-shutdown + qemu-system-x86_64 ${QEMU_OPTS} -boot d -display none -serial stdio -monitor none -no-reboot line-count: cloc --quiet --exclude-dir=bin --include-lang=Rust --csv src/ | tail -n 1 | awk -F, '{print $$5}' diff --git a/README.md b/README.md index 310d416..7cbb707 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # DuskOS -A simple microkernel and operating system written in Rust for x86_64. +A simple work-in-progress microkernel and operating system written in Rust for +x86_64. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..5680167 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-changed=src/arch/x86_64/linker.ld"); +} diff --git a/src/arch/mod.rs b/src/arch/mod.rs index ce1fde5..7807187 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -3,3 +3,6 @@ mod x86_64; #[cfg(target_arch = "x86_64")] pub use x86_64::*; + +#[cfg(target_arch = "x86_64")] +pub(crate) use x86_64::{PageTableCreateError, PageTableMapError, PageTableUnmapError}; diff --git a/src/arch/x86_64/cpu.rs b/src/arch/x86_64/cpu.rs new file mode 100644 index 0000000..2857eb6 --- /dev/null +++ b/src/arch/x86_64/cpu.rs @@ -0,0 +1,92 @@ +use core::arch::asm; + +#[derive(Debug)] +pub enum CpuFeaturesError { + CpuidFeaturesNotSupported, + InvalidPhysicalAddressWidth, +} + +#[derive(Clone, Copy, Debug)] +pub(crate) struct CpuFeatures { + pub nx_supported: bool, + pub nx_enabled: bool, + pub physical_address_bits: u8, + pub virtual_address_bits: u8, +} + +pub fn detect_features_and_enable() -> Result { + let mut features = CpuFeatures { + nx_supported: false, + nx_enabled: false, + physical_address_bits: 0, + virtual_address_bits: 0, + }; + + let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000000, 0); + + if cpuid_result.eax < 0x80000008 { + return Err(CpuFeaturesError::CpuidFeaturesNotSupported); + } + + let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000001, 0); + + features.nx_supported = cpuid_result.edx & (1 << 20) != 0; + + let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000008, 0); + + features.physical_address_bits = (cpuid_result.eax & 0xFF) as u8; + if !(12..=52).contains(&features.physical_address_bits) { + return Err(CpuFeaturesError::InvalidPhysicalAddressWidth); + } + + features.virtual_address_bits = (cpuid_result.eax >> 8 & 0xFF) as u8; + + if features.nx_supported { + let cpuid_result = core::arch::x86_64::__cpuid_count(0x1, 0); + let msr_supported = cpuid_result.edx & (1 << 5) != 0; + + if !msr_supported { + return Err(CpuFeaturesError::CpuidFeaturesNotSupported); + } + + // mother efer + let efer = unsafe { read_msr(0xC0000080) }; + + unsafe { + write_msr(0xC0000080, efer | (1 << 11)); + } + + features.nx_enabled = unsafe { read_msr(0xC0000080) } & (1 << 11) != 0; + } + + Ok(features) +} + +unsafe fn read_msr(msr: u32) -> u64 { + let low: u32; + let high: u32; + + unsafe { + asm!( + "rdmsr", + in("ecx") msr, + out("eax") low, + out("edx") high, + options(nomem, nostack, preserves_flags), + ); + } + + ((high as u64) << 32) | low as u64 +} + +unsafe fn write_msr(msr: u32, value: u64) { + unsafe { + asm!( + "wrmsr", + in("ecx") msr, + in("eax") value as u32, + in("edx") (value >> 32) as u32, + options(nomem, nostack, preserves_flags), + ); + } +} diff --git a/src/arch/x86_64/linker.ld b/src/arch/x86_64/linker.ld index 8de6521..76736fd 100644 --- a/src/arch/x86_64/linker.ld +++ b/src/arch/x86_64/linker.ld @@ -25,7 +25,10 @@ SECTIONS /* that is the beginning of the region. */ /* Additionally, leave space for the ELF headers by adding SIZEOF_HEADERS to the */ /* base load address. */ - . = 0xffffffff80000000 + SIZEOF_HEADERS; + . = 0xffffffff80000000; + + __text_start = .; + . += SIZEOF_HEADERS; .text : { *(.text .text.*) @@ -33,6 +36,8 @@ SECTIONS /* Move to the next memory page for .rodata */ . = ALIGN(CONSTANT(MAXPAGESIZE)); + __text_end = .; + __rodata_start = .; .rodata : { *(.rodata .rodata.*) @@ -40,7 +45,9 @@ SECTIONS /* Move to the next memory page for .data */ . = ALIGN(CONSTANT(MAXPAGESIZE)); - + __rodata_end = .; + __data_start = .; + .data : { *(.data .data.*) @@ -56,6 +63,11 @@ SECTIONS *(.dynamic) } :data :dynamic + .got : { + *(.got .got.*) + *(.got.plt .got.plt.*) + } :data + /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ /* unnecessary zeros will be written to the binary. */ /* If you need, for example, .init_array and .fini_array, those should be placed */ @@ -65,6 +77,9 @@ SECTIONS *(COMMON) } :data + . = ALIGN(CONSTANT(MAXPAGESIZE)); + __data_end = .; + /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ /* Also discard the program interpreter section since we do not need one. This is */ /* more or less equivalent to the --no-dynamic-linker linker flag, except that it */ diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 6f2268a..b6d51b0 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -1,20 +1,34 @@ +mod cpu; mod gdt; mod interrupts; -pub mod paging; +mod paging; pub mod port; use core::arch::asm; pub use interrupts::disable_interrupts; +pub(crate) use paging::{ + MapError as PageTableMapError, PageTableCreateError, UnmapError as PageTableUnmapError, +}; +pub use paging::{PageTable, PagingConfig}; + +pub struct ArchState { + pub paging: PagingConfig, +} use crate::println; -pub fn init() { +pub fn init() -> ArchState { disable_interrupts(); println!("Loading GDT..."); gdt::init(); println!("Loading IDT..."); interrupts::init(); + println!("Detecting CPU features..."); + let cpu_features = cpu::detect_features_and_enable(); + let paging = + PagingConfig::from_features(cpu_features.expect("required CPU features are not supported")); + ArchState { paging } } pub fn halt() { diff --git a/src/arch/x86_64/paging.rs b/src/arch/x86_64/paging.rs index 1c61592..7470d8c 100644 --- a/src/arch/x86_64/paging.rs +++ b/src/arch/x86_64/paging.rs @@ -1,17 +1,60 @@ use core::arch::asm; use crate::{ + arch::x86_64::cpu::CpuFeatures, memory::{ - DirectMap, FrameAllocator, KernelImage, MemoryRegion, MemoryRegionKind, PhysicalAddr, - PhysicalFrame, VirtualAddr, + DirectMap, FrameAllocator, PagePermissions, PhysicalAddr, PhysicalFrame, VirtualAddr, }, - println, }; pub const PAGE_SIZE: usize = 4096; pub const PAGE_TABLE_ENTRIES: usize = 512; -const ADDRESS_MASK: usize = 0x000F_FFFF_FFFF_F000; +#[derive(Clone, Copy, Debug)] +pub struct PagingConfig { + physical_address_bits: u8, + nx_enabled: bool, + mode: PagingMode, +} + +impl PagingConfig { + pub fn from_features(features: CpuFeatures) -> Self { + Self { + physical_address_bits: features.physical_address_bits, + nx_enabled: features.nx_enabled, + mode: PagingMode::FourLevel, + } + } + + pub const fn physical_address_mask(&self) -> usize { + ((1 << self.physical_address_bits) - 1) & !0xFFF + } + + pub const fn physical_address_limit(&self) -> usize { + 1 << self.physical_address_bits + } +} + +#[derive(Clone, Copy, Debug)] +enum PagingMode { + FourLevel, + FiveLevel, +} + +impl PagingMode { + const fn virtual_address_bits(&self) -> u32 { + match self { + PagingMode::FourLevel => 48, + PagingMode::FiveLevel => 57, + } + } +} + +#[derive(Debug)] +enum PageTableEntryError { + PhysicalAddressTooLarge, + NoExecuteUnsupported, +} #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -21,11 +64,18 @@ impl PageTableEntry { const PRESENT: u64 = 1 << 0; const WRITABLE: u64 = 1 << 1; const USER_ACCESSIBLE: u64 = 1 << 2; - const LARGE_PAGE: u64 = 1 << 7; + const HUGE_PAGE: u64 = 1 << 7; const NX: u64 = 1 << 63; - // TODO: validate that page is withing the CPU's mappable address space - const fn new(physical_address: PhysicalAddr, permissions: PagePermissions) -> Self { + const fn new( + physical_address: PhysicalAddr, + permissions: PagePermissions, + config: PagingConfig, + ) -> Result { + if physical_address.as_usize() >= config.physical_address_limit() { + return Err(PageTableEntryError::PhysicalAddressTooLarge); + } + let mut value = physical_address.as_usize() as u64 | Self::PRESENT; if permissions.writable { @@ -35,27 +85,39 @@ impl PageTableEntry { value |= Self::USER_ACCESSIBLE; } if !permissions.executable { + if !config.nx_enabled { + return Err(PageTableEntryError::NoExecuteUnsupported); + } + value |= Self::NX; } - Self(value) + Ok(Self(value)) } - fn new_table(frame: PhysicalFrame, user_accessible: bool) -> Self { + fn new_table( + frame: PhysicalFrame, + user_accessible: bool, + config: PagingConfig, + ) -> Result { + if frame.start_address().as_usize() >= config.physical_address_limit() { + return Err(PageTableEntryError::PhysicalAddressTooLarge); + } + let mut value = frame.start_address().as_usize() as u64 | Self::PRESENT | Self::WRITABLE; if user_accessible { value |= Self::USER_ACCESSIBLE; } - Self(value) + Ok(Self(value)) } const fn null() -> Self { Self(0) } - fn physical_address(&self) -> PhysicalAddr { - PhysicalAddr::new(self.0 as usize & ADDRESS_MASK) + fn physical_address(&self, config: PagingConfig) -> PhysicalAddr { + PhysicalAddr::new(self.0 as usize & config.physical_address_mask()) } fn is_present(&self) -> bool { @@ -67,90 +129,440 @@ impl PageTableEntry { } fn is_huge(&self) -> bool { - self.0 & Self::LARGE_PAGE != 0 + self.0 & Self::HUGE_PAGE != 0 } - fn frame(&self) -> Option { + fn frame(&self, config: PagingConfig) -> Option { if !self.is_present() || self.is_huge() { return None; } - PhysicalFrame::from_start_address(self.physical_address()) + PhysicalFrame::from_start_address(self.physical_address(config)) } } -#[repr(C, align(4096))] -struct PageTable { - entries: [PageTableEntry; PAGE_TABLE_ENTRIES], +#[derive(Debug)] +pub(crate) enum MapError { + InvalidVirtualAddress, + VirtualAddressUnaligned, + PhysicalAddressTooLarge, + PageAlreadyMapped, + HugePageConflict, + NoExecuteUnsupported, + OutOfFrames, + PageTableOutsideDirectMap, + InvalidPageTableEntry, +} + +#[derive(Debug)] +pub(crate) enum UnmapError { + InvalidVirtualAddress, + VirtualAddressUnaligned, + PageNotMapped, + HugePageConflict, + PageTableOutsideDirectMap, + InvalidPageTableEntry, +} + +#[derive(Clone, Copy)] +struct NewTable { + parent: PhysicalFrame, + index: usize, + child: PhysicalFrame, +} + +#[derive(Debug)] +pub(crate) enum PageTableCreateError { + PhysicalAddressTooLarge, + OutOfFrames, +} + +pub struct PageTable { + frame: PhysicalFrame, + direct_map: DirectMap, + config: PagingConfig, } impl PageTable { - pub fn is_empty(&self) -> bool { - self.entries.iter().all(|&entry| !entry.is_present()) + pub fn new( + direct_map: DirectMap, + config: PagingConfig, + allocator: &mut FrameAllocator, + ) -> Result { + let frame = allocator.alloc().ok_or(PageTableCreateError::OutOfFrames)?; + + if frame.start_address().as_usize() >= config.physical_address_limit() { + unsafe { allocator.dealloc(frame) }; + + return Err(PageTableCreateError::PhysicalAddressTooLarge); + } + + Ok(Self { + frame, + direct_map, + config, + }) + } + + fn is_active(&self) -> bool { + let cr3 = unsafe { read_cr3(self.config) }; + + cr3.start_address() == self.frame.start_address() + } + + fn table(&self, frame: PhysicalFrame) -> Option<&[PageTableEntry; PAGE_TABLE_ENTRIES]> { + let virtual_addr = self.direct_map.translate(frame.start_address())?; + + Some(unsafe { &*(virtual_addr.as_ptr::<[PageTableEntry; PAGE_TABLE_ENTRIES]>()) }) + } + + fn table_mut( + &mut self, + frame: PhysicalFrame, + ) -> Option<&mut [PageTableEntry; PAGE_TABLE_ENTRIES]> { + let virtual_addr = self.direct_map.translate(frame.start_address())?; + + Some(unsafe { &mut *(virtual_addr.as_mut_ptr::<[PageTableEntry; PAGE_TABLE_ENTRIES]>()) }) + } + + fn is_canonical(&self, addr: usize) -> bool { + let bits = self.config.mode.virtual_address_bits(); + let shift = usize::BITS - bits; + + (((addr << shift) as isize >> shift) as usize) == addr + } + + pub fn translate(&self, addr: VirtualAddr) -> Option { + let addr = addr.as_usize(); + + if !self.is_canonical(addr) { + return None; + } + + let p4 = self.table(self.frame)?; + let p4_entry = p4[p4_index(addr)]; + + if !p4_entry.is_present() { + return None; + } + + let p3 = self.table(p4_entry.frame(self.config)?)?; + let p3_entry = p3[p3_index(addr)]; + + if !p3_entry.is_present() { + return None; + } + + if p3_entry.is_huge() { + return translate_huge_page(p3_entry, addr, 1 << 30, self.config); + } + + let p2 = self.table(p3_entry.frame(self.config)?)?; + let p2_entry = p2[p2_index(addr)]; + + if !p2_entry.is_present() { + return None; + } + + if p2_entry.is_huge() { + return translate_huge_page(p2_entry, addr, 1 << 21, self.config); + } + + let p1 = self.table(p2_entry.frame(self.config)?)?; + let p1_entry = p1[p1_index(addr)]; + + if !p1_entry.is_present() { + return None; + } + + let physical_base = p1_entry.physical_address(self.config).as_usize(); + + physical_base + .checked_add(page_offset(addr)) + .map(PhysicalAddr::new) + } + + fn get_next_level( + &self, + parent: PhysicalFrame, + index: usize, + ) -> Result { + let parent_table = self + .table(parent) + .ok_or(UnmapError::PageTableOutsideDirectMap)?; + + // TODO: encode the level so bit 7 is only interpreted where huge pages are valid. + if parent_table[index].is_huge() { + return Err(UnmapError::HugePageConflict); + } + + parent_table[index] + .frame(self.config) + .ok_or(UnmapError::PageNotMapped) + } + + fn get_next_level_or_allocate( + &mut self, + parent: PhysicalFrame, + index: usize, + user_accessible: bool, + allocator: &mut FrameAllocator, + ) -> Result<(PhysicalFrame, bool), MapError> { + let config = self.config; + + let parent_table = self + .table_mut(parent) + .ok_or(MapError::PageTableOutsideDirectMap)?; + + let entry = &mut parent_table[index]; + + if !entry.is_present() { + let frame = allocator.alloc().ok_or(MapError::OutOfFrames)?; + let table = match PageTableEntry::new_table(frame, user_accessible, config) { + Ok(table) => table, + Err(PageTableEntryError::PhysicalAddressTooLarge) => { + unsafe { allocator.dealloc(frame) }; + return Err(MapError::PhysicalAddressTooLarge); + } + Err(PageTableEntryError::NoExecuteUnsupported) => unreachable!(), + }; + *entry = table; + return Ok((frame, true)); + } + + if entry.is_huge() { + return Err(MapError::HugePageConflict); + } + + // TODO: we upgrade intermediate entries, and dont carefully rollback if we fail + if user_accessible && !entry.is_user_accessible() { + entry.0 |= PageTableEntry::USER_ACCESSIBLE; + } + + entry + .frame(config) + .map(|frame| (frame, false)) + .ok_or(MapError::InvalidPageTableEntry) + } + + fn rollback_tables( + &mut self, + new_tables: &[Option], + count: usize, + allocator: &mut FrameAllocator, + ) { + for table in new_tables[..count].iter().rev().flatten() { + self.table_mut(table.parent).unwrap()[table.index] = PageTableEntry::null(); + + unsafe { allocator.dealloc(table.child) }; + } + } + + pub fn map( + &mut self, + mapped_addr: VirtualAddr, + frame: PhysicalFrame, + permissions: PagePermissions, + allocator: &mut FrameAllocator, + ) -> Result<(), MapError> { + if !self.is_canonical(mapped_addr.as_usize()) { + return Err(MapError::InvalidVirtualAddress); + } + + if mapped_addr.as_usize() % PAGE_SIZE != 0 { + return Err(MapError::VirtualAddressUnaligned); + } + + if frame.start_address().as_usize() >= self.config.physical_address_limit() { + return Err(MapError::PhysicalAddressTooLarge); + } + + let mut new_tables: [Option; 3] = [None; 3]; + let mut new_table_count = 0; + + let result = (|| { + let p4_entry_index = p4_index(mapped_addr.as_usize()); + let (pdpt_frame, allocated) = self.get_next_level_or_allocate( + self.frame, + p4_entry_index, + permissions.user_accessible, + allocator, + )?; + if allocated { + new_tables[new_table_count] = Some(NewTable { + parent: self.frame, + index: p4_entry_index, + child: pdpt_frame, + }); + new_table_count += 1; + } + + let p3_entry_index = p3_index(mapped_addr.as_usize()); + let (pd_frame, allocated) = self.get_next_level_or_allocate( + pdpt_frame, + p3_entry_index, + permissions.user_accessible, + allocator, + )?; + if allocated { + new_tables[new_table_count] = Some(NewTable { + parent: pdpt_frame, + index: p3_entry_index, + child: pd_frame, + }); + new_table_count += 1; + } + + let p2_entry_index = p2_index(mapped_addr.as_usize()); + let (pt_frame, allocated) = self.get_next_level_or_allocate( + pd_frame, + p2_entry_index, + permissions.user_accessible, + allocator, + )?; + if allocated { + new_tables[new_table_count] = Some(NewTable { + parent: pd_frame, + index: p2_entry_index, + child: pt_frame, + }); + new_table_count += 1; + } + + let config = self.config.clone(); + + let pt_table = self + .table_mut(pt_frame) + .ok_or(MapError::PageTableOutsideDirectMap)?; + let entry = &mut pt_table[p1_index(mapped_addr.as_usize())]; + if entry.is_present() { + return Err(MapError::PageAlreadyMapped); + } + + *entry = + PageTableEntry::new(frame.start_address(), permissions, config).map_err(|err| { + match err { + PageTableEntryError::PhysicalAddressTooLarge => { + MapError::PhysicalAddressTooLarge + } + PageTableEntryError::NoExecuteUnsupported => MapError::NoExecuteUnsupported, + } + })?; + Ok(()) + })(); + + if result.is_err() { + self.rollback_tables(&new_tables, new_table_count, allocator); + return result; + } + + self.flush_tlb_if_active(mapped_addr); + + Ok(()) + } + + /// # Safety + /// + /// The caller must ensure: + /// - The page being unmapped does not unmap the HHDM, kernel image, or stack + /// - The caller must ensure that the page is not currently in use + pub unsafe fn unmap( + &mut self, + mapped_addr: VirtualAddr, + allocator: &mut FrameAllocator, + ) -> Result { + if !self.is_canonical(mapped_addr.as_usize()) { + return Err(UnmapError::InvalidVirtualAddress); + } + + if mapped_addr.as_usize() % PAGE_SIZE != 0 { + return Err(UnmapError::VirtualAddressUnaligned); + } + + let config = self.config; + + let pdpt_frame = self.get_next_level(self.frame, p4_index(mapped_addr.as_usize()))?; + + let pd_frame = self.get_next_level(pdpt_frame, p3_index(mapped_addr.as_usize()))?; + + let pt_frame = self.get_next_level(pd_frame, p2_index(mapped_addr.as_usize()))?; + let pt_table = self + .table_mut(pt_frame) + .ok_or(UnmapError::PageTableOutsideDirectMap)?; + + let entry = pt_table[p1_index(mapped_addr.as_usize())]; + + if !entry.is_present() { + return Err(UnmapError::PageNotMapped); + } + + let frame = entry + .frame(config) + .ok_or(UnmapError::InvalidPageTableEntry)?; + pt_table[p1_index(mapped_addr.as_usize())] = PageTableEntry::null(); + + if pt_table.is_empty() { + self.table_mut(pd_frame).unwrap()[p2_index(mapped_addr.as_usize())] = + PageTableEntry::null(); + unsafe { allocator.dealloc(pt_frame) }; + + if self.table(pd_frame).unwrap().is_empty() { + self.table_mut(pdpt_frame).unwrap()[p3_index(mapped_addr.as_usize())] = + PageTableEntry::null(); + unsafe { allocator.dealloc(pd_frame) }; + + if self.table(pdpt_frame).unwrap().is_empty() { + self.table_mut(self.frame).unwrap()[p4_index(mapped_addr.as_usize())] = + PageTableEntry::null(); + unsafe { allocator.dealloc(pdpt_frame) }; + } + } + } + + self.flush_tlb_if_active(mapped_addr); + + Ok(frame) + } + + fn flush_tlb_if_active(&self, page: VirtualAddr) { + debug_assert!(self.is_canonical(page.as_usize())); + + if self.is_active() { + unsafe { + asm!("invlpg [{}]", in(reg) page.as_usize(), options(nostack, preserves_flags)); + } + } + } + + /// # Safety + /// + /// The caller must ensure: + /// - The new space must map the kernel image, stack, and the code being executed + /// - The new space must map the HHDM + pub unsafe fn activate(&self) { + unsafe { + asm!( + "mov cr3, {}", + in(reg) self.frame.start_address().as_usize(), + options(nostack, preserves_flags) + ); + }; } } -const _: () = assert!(core::mem::size_of::() == 4096); - fn translate_huge_page( entry: PageTableEntry, virtual_addr: usize, page_size: usize, + config: PagingConfig, ) -> Option { - let physical_base = entry.physical_address().as_usize() & !(page_size - 1); + let physical_base = entry.physical_address(config).as_usize() & !(page_size - 1); let offset = virtual_addr & (page_size - 1); physical_base.checked_add(offset).map(PhysicalAddr::new) } -#[derive(Debug)] -pub enum PageError { - NotCanonical, - Unaligned, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PagePermissions { - pub writable: bool, - pub executable: bool, - pub user_accessible: bool, -} - -impl PagePermissions { - // TODO: give more fine grained permissions - pub const HHDM: Self = Self::new(true, false, false); - pub const KERNEL: Self = Self::new(true, true, false); - pub const KERNEL_DATA: Self = Self::new(true, false, false); - - pub const fn new(writable: bool, executable: bool, user_accessible: bool) -> Self { - Self { - writable, - executable, - user_accessible, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Page { - start: VirtualAddr, -} - -impl Page { - pub fn from_start_address(start: VirtualAddr) -> Result { - if !is_canonical_48_bit(start.as_usize()) { - return Err(PageError::NotCanonical); - } - - if start.as_usize() & (4096 - 1) != 0 { - return Err(PageError::Unaligned); - } - - Ok(Self { start }) - } -} - fn p4_index(addr: usize) -> usize { (addr >> 39) & 0x1FF } @@ -171,7 +583,7 @@ fn page_offset(addr: usize) -> usize { addr & 0xFFF } -unsafe fn read_cr3() -> PhysicalFrame { +unsafe fn read_cr3(config: PagingConfig) -> PhysicalFrame { let value: usize; unsafe { asm!( @@ -181,463 +593,6 @@ unsafe fn read_cr3() -> PhysicalFrame { ); } - PhysicalFrame::from_start_address(PhysicalAddr::new(value & ADDRESS_MASK)) + PhysicalFrame::from_start_address(PhysicalAddr::new(value & config.physical_address_mask())) .expect("CR3 contains an unaligned page-table address") } - -#[derive(Debug)] -pub enum MapError { - MisalignedPage, - PageAlreadyMapped, - ParentIsHuge, - FrameAllocatorError, - AddressOverflow, - UserPageInKernelHalf, -} - -#[derive(Debug)] -pub enum UnmapError { - PageNotWithinHHDM, - PageNotPresent, - ParentIsHuge, - ParentNotPresent, - FrameAllocatorError, -} - -type PageTableRoot = PhysicalFrame; - -#[derive(Clone, Copy)] -struct NewTable { - parent: PhysicalFrame, - index: usize, - child: PhysicalFrame, -} - -#[derive(Debug)] -pub enum AddressSpaceError { - MalformedMemmap, - Map(MapError), - FrameAllocatorError, -} - -pub struct AddressSpace { - root: PageTableRoot, - direct_map: DirectMap, -} - -impl AddressSpace { - pub fn new + Clone>( - direct_map: DirectMap, - memmap: I, - kernel_address: KernelImage, - allocator: &mut FrameAllocator, - ) -> Result { - let frame = allocator - .alloc() - .ok_or(AddressSpaceError::FrameAllocatorError)?; - let mut space = AddressSpace { - root: frame, - direct_map, - }; - - // TODO: cleanup page tables if any allocations fail - // map hhdm - for region in memmap.clone() { - if region.kind == MemoryRegionKind::Reserved - || region.kind == MemoryRegionKind::BadMemory - { - continue; - } - - println!( - "Mapping: {:?} at {:#X} with length {:#X}", - region.kind, - region.start.as_usize(), - region.length - ); - - space - .map_range( - direct_map - .translate(region.start) - .ok_or(AddressSpaceError::MalformedMemmap)?, - region.start, - region.length, - PagePermissions::HHDM, - allocator, - ) - .map_err(|err| AddressSpaceError::Map(err))?; - } - - // map kernel - println!( - "Mapping kernel at {:#X} with length {:#X}", - kernel_address.virtual_base.as_usize(), - kernel_address.length - ); - - space - .map_range( - kernel_address.virtual_base, - kernel_address.physical_base, - kernel_address.length, - PagePermissions::KERNEL, - allocator, - ) - .map_err(|err| AddressSpaceError::Map(err))?; - - Ok(space) - } - - fn is_active(&self) -> bool { - let cr3 = unsafe { read_cr3() }; - - cr3.start_address() == self.root.start_address() - } - - fn table(&self, frame: PhysicalFrame) -> Option<&PageTable> { - let virtual_addr = self.direct_map.translate(frame.start_address())?; - - Some(unsafe { &*(virtual_addr.as_ptr::()) }) - } - - fn table_mut(&mut self, frame: PhysicalFrame) -> Option<&mut PageTable> { - let virtual_addr = self.direct_map.translate(frame.start_address())?; - - Some(unsafe { &mut *(virtual_addr.as_mut_ptr::()) }) - } - - pub fn translate(&self, addr: VirtualAddr) -> Option { - let addr = addr.as_usize(); - - if !is_canonical_48_bit(addr) { - return None; - } - - let p4 = self.table(self.root)?; - let p4_entry = p4.entries[p4_index(addr)]; - - if !p4_entry.is_present() { - return None; - } - - let p3 = self.table(p4_entry.frame()?)?; - let p3_entry = p3.entries[p3_index(addr)]; - - if !p3_entry.is_present() { - return None; - } - - if p3_entry.is_huge() { - return translate_huge_page(p3_entry, addr, 1 << 30); - } - - let p2 = self.table(p3_entry.frame()?)?; - let p2_entry = p2.entries[p2_index(addr)]; - - if !p2_entry.is_present() { - return None; - } - - if p2_entry.is_huge() { - return translate_huge_page(p2_entry, addr, 1 << 21); - } - - let p1 = self.table(p2_entry.frame()?)?; - let p1_entry = p1.entries[p1_index(addr)]; - - if !p1_entry.is_present() { - return None; - } - - let physical_base = p1_entry.physical_address().as_usize(); - - physical_base - .checked_add(page_offset(addr)) - .map(PhysicalAddr::new) - } - - fn get_next_level( - &self, - parent: PhysicalFrame, - index: usize, - ) -> Result { - let parent_table = self.table(parent).ok_or(UnmapError::PageNotWithinHHDM)?; - - // TODO: encode some level so we dont spuriously think a page is huge - if parent_table.entries[index].is_huge() { - return Err(UnmapError::ParentIsHuge); - } - - Ok(parent_table.entries[index] - .frame() - .ok_or(UnmapError::ParentNotPresent)?) - } - - fn get_next_level_or_allocate( - &mut self, - parent: PhysicalFrame, - index: usize, - user_accessible: bool, - allocator: &mut FrameAllocator, - ) -> Result<(PhysicalFrame, bool), MapError> { - let parent_table = self - .table_mut(parent) - .ok_or(MapError::FrameAllocatorError)?; - - let entry = &mut parent_table.entries[index]; - - if !entry.is_present() { - let frame = allocator.alloc().ok_or(MapError::FrameAllocatorError)?; - *entry = PageTableEntry::new_table(frame, user_accessible); - return Ok((frame, true)); - } - - if entry.is_huge() { - return Err(MapError::ParentIsHuge); - } - - // TODO: we upgrade intermediate entries, and dont carefully rollback if we fail - if user_accessible && !entry.is_user_accessible() { - entry.0 |= PageTableEntry::USER_ACCESSIBLE; - } - - entry - .frame() - .map(|frame| (frame, false)) - .ok_or(MapError::FrameAllocatorError) - } - - fn rollback_tables( - &mut self, - new_tables: &[Option], - count: usize, - allocator: &mut FrameAllocator, - ) { - for table in new_tables[..count].iter().rev().flatten() { - self.table_mut(table.parent).unwrap().entries[table.index] = PageTableEntry::null(); - - unsafe { allocator.dealloc(table.child) }; - } - } - - pub fn map( - &mut self, - page: Page, - frame: PhysicalFrame, - permissions: PagePermissions, - allocator: &mut FrameAllocator, - ) -> Result<(), MapError> { - if permissions.user_accessible && p4_index(page.start.as_usize()) >= 256 { - return Err(MapError::UserPageInKernelHalf); - } - - let mut new_tables: [Option; 3] = [None; 3]; - let mut new_table_count = 0; - - let result = (|| { - let p4_entry_index = p4_index(page.start.as_usize()); - let (pdpt_frame, allocated) = self.get_next_level_or_allocate( - self.root, - p4_entry_index, - permissions.user_accessible, - allocator, - )?; - if allocated { - new_tables[new_table_count] = Some(NewTable { - parent: self.root, - index: p4_entry_index, - child: pdpt_frame, - }); - new_table_count += 1; - } - - let p3_entry_index = p3_index(page.start.as_usize()); - let (pd_frame, allocated) = self.get_next_level_or_allocate( - pdpt_frame, - p3_entry_index, - permissions.user_accessible, - allocator, - )?; - if allocated { - new_tables[new_table_count] = Some(NewTable { - parent: pdpt_frame, - index: p3_entry_index, - child: pd_frame, - }); - new_table_count += 1; - } - - let p2_entry_index = p2_index(page.start.as_usize()); - let (pt_frame, allocated) = self.get_next_level_or_allocate( - pd_frame, - p2_entry_index, - permissions.user_accessible, - allocator, - )?; - if allocated { - new_tables[new_table_count] = Some(NewTable { - parent: pd_frame, - index: p2_entry_index, - child: pt_frame, - }); - new_table_count += 1; - } - - let pt_table = self - .table_mut(pt_frame) - .ok_or(MapError::FrameAllocatorError)?; - let entry = &mut pt_table.entries[p1_index(page.start.as_usize())]; - if entry.is_present() { - return Err(MapError::PageAlreadyMapped); - } - - *entry = PageTableEntry::new(frame.start_address(), permissions); - Ok(()) - })(); - - if result.is_err() { - self.rollback_tables(&new_tables, new_table_count, allocator); - return result; - } - - self.flush_tlb_if_active(page); - - Ok(()) - } - - pub fn map_range( - &mut self, - virtual_start: VirtualAddr, - physical_start: PhysicalAddr, - length: usize, - permissions: PagePermissions, - allocator: &mut FrameAllocator, - ) -> Result<(), MapError> { - if length % PAGE_SIZE != 0 { - return Err(MapError::MisalignedPage); - } - - let virtual_start = - Page::from_start_address(virtual_start).map_err(|_| MapError::MisalignedPage)?; - let physical_start = - PhysicalFrame::from_start_address(physical_start).ok_or(MapError::MisalignedPage)?; - virtual_start - .start - .as_usize() - .checked_add(length) - .ok_or(MapError::AddressOverflow)?; - physical_start - .start_address() - .as_usize() - .checked_add(length) - .ok_or(MapError::AddressOverflow)?; - - for page_idx in 0..length / PAGE_SIZE { - let offset = page_idx * PAGE_SIZE; - let page = - Page::from_start_address(VirtualAddr::new(virtual_start.start.as_usize() + offset)) - .unwrap(); - let frame = PhysicalFrame::from_start_address(PhysicalAddr::new( - physical_start.start_address().as_usize() + offset, - )) - .unwrap(); - - if let Err(error) = self.map(page, frame, permissions, allocator) { - for rollback_idx in (0..page_idx).rev() { - let rollback_page = Page::from_start_address(VirtualAddr::new( - virtual_start.start.as_usize() + rollback_idx * PAGE_SIZE, - )) - .unwrap(); - self.unmap(rollback_page, allocator) - .expect("failed to roll back a mapped page"); - } - - return Err(error); - } - } - - Ok(()) - } - - pub fn unmap( - &mut self, - page: Page, - allocator: &mut FrameAllocator, - ) -> Result { - let pdpt_frame = self.get_next_level(self.root, p4_index(page.start.as_usize()))?; - - let pd_frame = self.get_next_level(pdpt_frame, p3_index(page.start.as_usize()))?; - - let pt_frame = self.get_next_level(pd_frame, p2_index(page.start.as_usize()))?; - let pt_table = self - .table_mut(pt_frame) - .ok_or(UnmapError::FrameAllocatorError)?; - - let entry = pt_table.entries[p1_index(page.start.as_usize())]; - - if !entry.is_present() { - return Err(UnmapError::PageNotPresent); - } - - let frame = entry.frame().ok_or(UnmapError::FrameAllocatorError)?; - pt_table.entries[p1_index(page.start.as_usize())] = PageTableEntry::null(); - - if pt_table.is_empty() { - self.table_mut(pd_frame).unwrap().entries[p2_index(page.start.as_usize())] = - PageTableEntry::null(); - unsafe { allocator.dealloc(pt_frame) }; - - if self.table(pd_frame).unwrap().is_empty() { - self.table_mut(pdpt_frame).unwrap().entries[p3_index(page.start.as_usize())] = - PageTableEntry::null(); - unsafe { allocator.dealloc(pd_frame) }; - - if self.table(pdpt_frame).unwrap().is_empty() { - self.table_mut(self.root).unwrap().entries[p4_index(page.start.as_usize())] = - PageTableEntry::null(); - unsafe { allocator.dealloc(pdpt_frame) }; - } - } - } - - self.flush_tlb_if_active(page); - - Ok(frame) - } - - // TODO: unmap range - - fn flush_tlb_if_active(&self, page: Page) { - if self.is_active() { - println!("Flushing TLB"); - unsafe { - asm!("invlpg [{}]", in(reg) page.start.as_usize(), options(nostack, preserves_flags)); - } - } - } - - pub unsafe fn activate(&self) -> PageTableRoot { - println!("Activating"); - unsafe { - asm!( - "mov cr3, {}", - in(reg) self.root.start_address().as_usize(), - options(nostack, preserves_flags) - ); - } - - self.root - } -} - -fn is_canonical_48_bit(addr: usize) -> bool { - let upper = addr >> 48; - let sign_bit = (addr >> 47) & 1; - - if sign_bit == 0 { - upper == 0 - } else { - upper == 0xFFFF - } -} diff --git a/src/arch/x86_64/x86_64-unknown-none.json b/src/arch/x86_64/x86_64-unknown-none.json index a7cd4a7..1a16bfb 100644 --- a/src/arch/x86_64/x86_64-unknown-none.json +++ b/src/arch/x86_64/x86_64-unknown-none.json @@ -1,20 +1,22 @@ { - "arch": "x86_64", - "cpu": "x86-64", - "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", - "llvm-target": "x86_64-unknown-none", - "target-endian": "little", - "target-pointer-width": 64, - "target-c-int-width": 32, - "features": "-mmx,-sse,+soft-float", - "rustc-abi": "softfloat", - "os": "DawnOS", - "linker": "rust-lld", - "linker-flavor": "ld.lld", - "pre-link-args": { - "ld.lld": ["-melf_x86_64", "--script=./src/arch/x86_64/linker.ld"] - }, - "panic-strategy": "abort", - "exe-suffix": ".elf", - "disable-redzone": true -} + "arch": "x86_64", + "cpu": "x86-64", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "llvm-target": "x86_64-unknown-none", + "target-endian": "little", + "target-pointer-width": 64, + "target-c-int-width": 32, + "features": "-mmx,-sse,+soft-float", + "rustc-abi": "softfloat", + "linker": "rust-lld", + "linker-flavor": "ld.lld", + "pre-link-args": { + "ld.lld": [ + "-melf_x86_64", + "--script=./src/arch/x86_64/linker.ld" + ] + }, + "panic-strategy": "abort", + "exe-suffix": ".elf", + "disable-redzone": true +} \ No newline at end of file diff --git a/src/boot/limine.rs b/src/boot/limine.rs index bff2eeb..91acf06 100644 --- a/src/boot/limine.rs +++ b/src/boot/limine.rs @@ -3,7 +3,10 @@ use ::limine as limine_api; use limine_api::request::{ExecutableAddressRequest, HhdmRequest, MemmapRequest}; use limine_api::{BaseRevision, RequestsEndMarker, RequestsStartMarker}; -use crate::memory::{KernelImage, PhysicalAddr, VirtualAddr}; +use crate::memory::{ + KernelMemoryLayout, KernelSegment, PagePermissions, PhysicalAddr, VirtualAddr, +}; +use crate::println; /// Sets the base revision to the latest revision supported by the crate. /// See specification for further info. @@ -33,8 +36,19 @@ static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new(); #[unsafe(link_section = ".requests_end_marker")] static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new(); +unsafe extern "C" { + static __text_start: u64; + static __text_end: u64; + + static __rodata_start: u64; + static __rodata_end: u64; + + static __data_start: u64; + static __data_end: u64; +} + pub struct BootInfo { - pub kernel_address: KernelImage, + pub kernel_layout: KernelMemoryLayout, pub hhdm_offset: usize, entries: &'static [&'static limine_api::memmap::Entry], } @@ -92,27 +106,74 @@ pub fn load_boot_info() -> Result { .ok_or(BootError::FailedToGetMemmap)? .entries(); - let mut kernel_length = None; - for &entry in memmap.iter() { - if entry.type_ != limine_api::memmap::MEMMAP_EXECUTABLE_AND_MODULES { - continue; - } + let mut segment_physical = kernel_address.physical_base as usize; + let mut segment_virtual = core::ptr::addr_of!(__text_start) as usize; - if entry.base != kernel_address.physical_base { - continue; - } + let mut segment_length = 0; - kernel_length = Some(entry.length as usize); - break; + let kernel_text_segment = KernelSegment { + physical_base: PhysicalAddr::new(segment_physical), + virtual_base: VirtualAddr::new(segment_virtual), + length: (core::ptr::addr_of!(__text_end) as usize) + - (core::ptr::addr_of!(__text_start) as usize), + permissions: PagePermissions::new(false, true, false), + }; + + segment_length += kernel_text_segment.length; + + segment_virtual = core::ptr::addr_of!(__rodata_start) as usize; + segment_physical = kernel_address.physical_base as usize + + (segment_virtual - kernel_address.virtual_base as usize); + + let kernel_rodata_segment = KernelSegment { + physical_base: PhysicalAddr::new(segment_physical), + virtual_base: VirtualAddr::new(segment_virtual), + length: (core::ptr::addr_of!(__rodata_end) as usize) + - (core::ptr::addr_of!(__rodata_start) as usize), + permissions: PagePermissions::new(false, false, false), + }; + + segment_length += kernel_rodata_segment.length; + + segment_virtual = core::ptr::addr_of!(__data_start) as usize; + segment_physical = kernel_address.physical_base as usize + + (segment_virtual - kernel_address.virtual_base as usize); + + let kernel_data_segment = KernelSegment { + physical_base: PhysicalAddr::new(segment_physical), + virtual_base: VirtualAddr::new(segment_virtual), + length: (core::ptr::addr_of!(__data_end) as usize) + - (core::ptr::addr_of!(__data_start) as usize), + permissions: PagePermissions::new(true, false, false), + }; + + segment_length += kernel_data_segment.length; + + #[cfg(debug_assertions)] + { + let mut kernel_length = None; + for &entry in memmap.iter() { + if entry.type_ != limine_api::memmap::MEMMAP_EXECUTABLE_AND_MODULES { + continue; + } + + if entry.base != kernel_address.physical_base { + continue; + } + + kernel_length = Some(entry.length as usize); + break; + } + debug_assert_eq!(segment_length, kernel_length.unwrap()); } - let kernel_length = kernel_length.ok_or(BootError::FailedToLocateKernel)?; - Ok(BootInfo { - kernel_address: KernelImage { - physical_base: PhysicalAddr::new(kernel_address.physical_base as usize), - virtual_base: VirtualAddr::new(kernel_address.virtual_base as usize), - length: kernel_length, + kernel_layout: KernelMemoryLayout { + segments: [ + kernel_text_segment, + kernel_rodata_segment, + kernel_data_segment, + ], }, hhdm_offset: hhdm_offset as usize, entries: memmap, diff --git a/src/main.rs b/src/main.rs index e687ba3..6539ede 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,33 +9,38 @@ mod debug; mod memory; use crate::{ - arch::paging, debug::serial, - memory::{PhysicalAddr, VirtualAddr}, + memory::{AddressSpace, PagePermissions, PhysicalAddr, VirtualAddr}, }; #[unsafe(no_mangle)] pub extern "C" fn _start() -> ! { serial::init().unwrap(); - arch::init(); + let arch_state = arch::init(); let boot_info = boot::load_boot_info().unwrap(); let direct_map = memory::DirectMap::new(boot_info.hhdm_offset); let mut allocator = memory::FrameAllocator::new(boot_info.memory_regions(), direct_map) .expect("failed to create frame allocator"); - let mut page_table = paging::AddressSpace::new( + println!("Initializing page table..."); + let mut page_table = AddressSpace::new_kernel( direct_map, boot_info.memory_regions(), - boot_info.kernel_address, + &boot_info.kernel_layout, + arch_state.paging, &mut allocator, ) .expect("failed to create page table"); + println!("Activating page table..."); + // safety: trust me bro unsafe { page_table.activate() }; + println!("Allocating a frame..."); + let frame = allocator.alloc().unwrap(); let direct_mapped = direct_map.translate(frame.start_address()).unwrap(); @@ -47,13 +52,15 @@ pub extern "C" fn _start() -> ! { assert!(page_table.translate(new_virtual).is_none()); - let page = paging::Page::from_start_address(new_virtual).unwrap(); - page_table .map( - page, - frame, - paging::PagePermissions::KERNEL_DATA, + frame.start_address(), + new_virtual, + PagePermissions { + writable: true, + executable: false, + user_accessible: true, + }, &mut allocator, ) .unwrap(); @@ -79,7 +86,7 @@ pub extern "C" fn _start() -> ! { println!("{:#X}", slice[0]); - let unmapped_frame = page_table.unmap(page, &mut allocator).unwrap(); + let unmapped_frame = unsafe { page_table.unmap(new_virtual, &mut allocator) }.unwrap(); assert_eq!(unmapped_frame, frame); diff --git a/src/memory/address_space.rs b/src/memory/address_space.rs new file mode 100644 index 0000000..0912548 --- /dev/null +++ b/src/memory/address_space.rs @@ -0,0 +1,269 @@ +use crate::{ + arch::{PageTable, PageTableCreateError, PageTableMapError, PageTableUnmapError, PagingConfig}, + memory::{ + DirectMap, FRAME_SIZE, FrameAllocator, KernelMemoryLayout, MemoryRegion, MemoryRegionKind, + PagePermissions, PhysicalAddr, PhysicalFrame, VirtualAddr, + }, +}; + +#[derive(Debug)] +pub enum MapError { + InvalidVirtualAddress, + VirtualAddressUnaligned, + PhysicalAddressTooLarge, + PhysicalAddressUnaligned, + RangeLengthUnaligned, + AddressOverflow, + AlreadyMapped, + MappingConflict, + UnsupportedPermissions, + OutsideAddressSpace, + OutOfMemory, + PageTableUnavailable, + CorruptedPageTable, +} + +impl From for MapError { + fn from(error: PageTableMapError) -> Self { + match error { + PageTableMapError::InvalidVirtualAddress => Self::InvalidVirtualAddress, + PageTableMapError::VirtualAddressUnaligned => Self::VirtualAddressUnaligned, + PageTableMapError::PhysicalAddressTooLarge => Self::PhysicalAddressTooLarge, + PageTableMapError::PageAlreadyMapped => Self::AlreadyMapped, + PageTableMapError::HugePageConflict => Self::MappingConflict, + PageTableMapError::NoExecuteUnsupported => Self::UnsupportedPermissions, + PageTableMapError::OutOfFrames => Self::OutOfMemory, + PageTableMapError::PageTableOutsideDirectMap => Self::PageTableUnavailable, + PageTableMapError::InvalidPageTableEntry => Self::CorruptedPageTable, + } + } +} + +#[derive(Debug)] +pub enum UnmapError { + InvalidVirtualAddress, + VirtualAddressUnaligned, + NotMapped, + MappingConflict, + OutsideAddressSpace, + PageTableUnavailable, + CorruptedPageTable, +} + +impl From for UnmapError { + fn from(error: PageTableUnmapError) -> Self { + match error { + PageTableUnmapError::InvalidVirtualAddress => Self::InvalidVirtualAddress, + PageTableUnmapError::VirtualAddressUnaligned => Self::VirtualAddressUnaligned, + PageTableUnmapError::PageNotMapped => Self::NotMapped, + PageTableUnmapError::HugePageConflict => Self::MappingConflict, + PageTableUnmapError::PageTableOutsideDirectMap => Self::PageTableUnavailable, + PageTableUnmapError::InvalidPageTableEntry => Self::CorruptedPageTable, + } + } +} + +#[derive(Debug)] +pub enum AddressSpaceCreateError { + AddressOutsideDirectMap, + PhysicalAddressTooLarge, + OutOfMemory, + Map(MapError), +} + +impl From for AddressSpaceCreateError { + fn from(error: PageTableCreateError) -> Self { + match error { + PageTableCreateError::PhysicalAddressTooLarge => Self::PhysicalAddressTooLarge, + PageTableCreateError::OutOfFrames => Self::OutOfMemory, + } + } +} + +enum AddressSpaceKind { + Kernel, + User, +} + +pub struct AddressSpace { + root: PageTable, + kind: AddressSpaceKind, +} + +impl AddressSpace { + pub fn new_kernel + Clone>( + direct_map: DirectMap, + memory_regions: I, + layout: &KernelMemoryLayout, + paging_config: PagingConfig, + allocator: &mut FrameAllocator, + ) -> Result { + let mut space = AddressSpace { + root: PageTable::new(direct_map, paging_config, allocator)?, + kind: AddressSpaceKind::Kernel, + }; + + // map hhdm + for region in memory_regions.clone() { + if region.kind == MemoryRegionKind::Reserved + || region.kind == MemoryRegionKind::BadMemory + { + continue; + } + + let res = space.map_range( + region.start, + direct_map + .translate(region.start) + .ok_or(AddressSpaceCreateError::AddressOutsideDirectMap)?, + region.length, + PagePermissions { + writable: true, + executable: false, + user_accessible: false, + }, + allocator, + ); + + if let Err(err) = res { + space.destroy(allocator); + return Err(AddressSpaceCreateError::Map(err)); + } + } + + let mut old_segment_stop: Option = None; + + for segment in layout.segments.iter() { + if let Some(stop) = old_segment_stop { + debug_assert_eq!(segment.physical_base.as_usize(), stop); + } + + old_segment_stop = Some(segment.physical_base.as_usize() + segment.length); + + let res = space.map_range( + segment.physical_base, + segment.virtual_base, + segment.length, + segment.permissions, + allocator, + ); + + if let Err(err) = res { + space.destroy(allocator); + return Err(AddressSpaceCreateError::Map(err)); + } + } + + Ok(space) + } + + pub fn new_user(kernel_space: &AddressSpace, allocator: &mut FrameAllocator) -> Self { + todo!() + } + + pub fn map( + &mut self, + physical_addr: PhysicalAddr, + virtual_addr: VirtualAddr, + permissions: PagePermissions, + allocator: &mut FrameAllocator, + ) -> Result<(), MapError> { + let frame = PhysicalFrame::from_start_address(physical_addr) + .ok_or(MapError::PhysicalAddressUnaligned)?; + + self.root + .map(virtual_addr, frame, permissions, allocator) + .map_err(MapError::from) + } + + pub fn map_range( + &mut self, + physical_start: PhysicalAddr, + virtual_start: VirtualAddr, + length: usize, + permissions: PagePermissions, + allocator: &mut FrameAllocator, + ) -> Result<(), MapError> { + if length == 0 { + return Ok(()); + } + + if physical_start.as_usize() % FRAME_SIZE != 0 { + return Err(MapError::PhysicalAddressUnaligned); + } + + if virtual_start.as_usize() % FRAME_SIZE != 0 { + return Err(MapError::VirtualAddressUnaligned); + } + + if length % FRAME_SIZE != 0 { + return Err(MapError::RangeLengthUnaligned); + } + + let last_offset = length - FRAME_SIZE; + + physical_start + .as_usize() + .checked_add(last_offset) + .ok_or(MapError::AddressOverflow)?; + virtual_start + .as_usize() + .checked_add(last_offset) + .ok_or(MapError::AddressOverflow)?; + + let page_count = length / FRAME_SIZE; + let mut mapped_pages = 0; + + while mapped_pages < page_count { + let offset = mapped_pages * FRAME_SIZE; + let physical_addr = PhysicalAddr::new(physical_start.as_usize() + offset); + let virtual_addr = VirtualAddr::new(virtual_start.as_usize() + offset); + + if let Err(err) = self.map(physical_addr, virtual_addr, permissions, allocator) { + for rollback_idx in (0..mapped_pages).rev() { + let rollback_offset = rollback_idx * FRAME_SIZE; + let rollback_physical_addr = + PhysicalAddr::new(physical_start.as_usize() + rollback_offset); + + unsafe { + self.unmap( + VirtualAddr::new(virtual_start.as_usize() + rollback_offset), + allocator, + ) + .expect("failed to roll back a mapped page"); + } + } + + return Err(err); + } + + mapped_pages += 1; + } + + Ok(()) + } + + /// # Safety + /// + /// The caller must ensure: + /// - The caller must ensure that the page is not currently in use + pub unsafe fn unmap( + &mut self, + virtual_addr: VirtualAddr, + allocator: &mut FrameAllocator, + ) -> Result { + unsafe { self.root.unmap(virtual_addr, allocator) }.map_err(UnmapError::from) + } + + pub fn translate(&self, virtual_addr: VirtualAddr) -> Option { + self.root.translate(virtual_addr) + } + + pub unsafe fn activate(&self) { + unsafe { self.root.activate() } + } + + pub fn destroy(self, allocator: &mut FrameAllocator) { + todo!("destroy address space") + } +} diff --git a/src/memory/frame.rs b/src/memory/frame.rs index 2f8e0ca..4ab1fdd 100644 --- a/src/memory/frame.rs +++ b/src/memory/frame.rs @@ -19,6 +19,7 @@ enum FrameState { Allocated = 0b10, } +// 64 KiB per GiB #[derive(Debug)] struct Bitmap { start: VirtualAddr, @@ -68,7 +69,7 @@ pub enum FrameAllocatorInitError { BitmapOutsideDirectMap, } -// very very simple linked list frame/page allocator +// very very simple bitmap frame/page allocator #[derive(Debug)] pub struct FrameAllocator { bitmap: Bitmap, diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 866d4e0..931e595 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,12 +1,35 @@ +mod address_space; mod frame; +pub use address_space::{AddressSpace, AddressSpaceCreateError, MapError, UnmapError}; pub use frame::{FRAME_SIZE, FrameAllocator, PhysicalFrame}; -#[derive(Debug, Clone, Copy)] -pub struct KernelImage { +pub struct KernelSegment { pub physical_base: PhysicalAddr, pub virtual_base: VirtualAddr, pub length: usize, + pub permissions: PagePermissions, +} + +pub struct KernelMemoryLayout { + pub segments: [KernelSegment; 3], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PagePermissions { + pub writable: bool, + pub executable: bool, + pub user_accessible: bool, +} + +impl PagePermissions { + pub const fn new(writable: bool, executable: bool, user_accessible: bool) -> Self { + Self { + writable, + executable, + user_accessible, + } + } } #[repr(transparent)]