diff --git a/Cargo.lock b/Cargo.lock index c7982bb..9ec230c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,13 @@ dependencies = [ name = "dusk-sys" version = "0.1.0" +[[package]] +name = "echo" +version = "0.1.0" +dependencies = [ + "dusk-sys", +] + [[package]] name = "limine" version = "0.6.5" diff --git a/Makefile b/Makefile index 9868db6..541f200 100644 --- a/Makefile +++ b/Makefile @@ -69,10 +69,12 @@ prepare-bin-files: compile-user: RUSTFLAGS="-C relocation-model=static" cargo build --package omega3 ${USERSPACE_CARGO_OPTS} RUSTFLAGS="-C relocation-model=static" cargo build --package client ${USERSPACE_CARGO_OPTS} + RUSTFLAGS="-C relocation-model=static" cargo build --package echo ${USERSPACE_CARGO_OPTS} copy-initramfs-files: compile-user cp -v target/${ARCH}-unknown-none/${MODE}/omega3 ${INITRAMFS_PATH}/omega3.elf cp -v target/${ARCH}-unknown-none/${MODE}/client ${INITRAMFS_PATH}/client.elf + cp -v target/${ARCH}-unknown-none/${MODE}/echo ${INITRAMFS_PATH}/echo.elf compile-initramfs: copy-initramfs-files (cd ${INITRAMFS_PATH} && find . -mindepth 1 | cpio -o -H newc) > ${ARTIFACTS_PATH}/initramfs.img diff --git a/src/arch/x86_64/apic.rs b/src/arch/x86_64/apic.rs index 1c6d3ca..f795a98 100644 --- a/src/arch/x86_64/apic.rs +++ b/src/arch/x86_64/apic.rs @@ -36,7 +36,6 @@ const APIC_TIMER_INITIAL_COUNT: u32 = 0x380; const APIC_TIMER_CURRENT_COUNT: u32 = 0x390; const APIC_TIMER_DIVIDE_CONFIG: u32 = 0x3E0; -#[derive(Debug)] enum LocalApicAccess { X2Apic, XApic, @@ -88,7 +87,6 @@ pub enum LocalApicError { NotBootSystemProcessor, } -#[derive(Debug)] pub struct LocalApic { id: u32, access: LocalApicAccess, diff --git a/src/arch/x86_64/cpu.rs b/src/arch/x86_64/cpu.rs index a0c1826..76050a0 100644 --- a/src/arch/x86_64/cpu.rs +++ b/src/arch/x86_64/cpu.rs @@ -103,7 +103,7 @@ pub enum CpuFeaturesError { InvalidVirtualAddressWidth, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub(crate) struct CpuFeatures { pub nx_supported: bool, pub nx_enabled: bool, diff --git a/src/arch/x86_64/interrupts/idt.rs b/src/arch/x86_64/interrupts/idt.rs index b71f9af..fd2de2a 100644 --- a/src/arch/x86_64/interrupts/idt.rs +++ b/src/arch/x86_64/interrupts/idt.rs @@ -37,7 +37,7 @@ struct IdtPointer { } #[repr(C)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub(super) struct InterruptStackFrame { pub instruction_pointer: VirtualAddr, pub code_segment: u64, diff --git a/src/arch/x86_64/io_apic.rs b/src/arch/x86_64/io_apic.rs index c78321d..f18e6f4 100644 --- a/src/arch/x86_64/io_apic.rs +++ b/src/arch/x86_64/io_apic.rs @@ -31,7 +31,6 @@ pub struct RedirectionConfig { pub trigger: TriggerMode, } -#[derive(Debug)] pub struct IoApic { base: VirtualAddr, global_interrupt_base: u32, diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 4fb2549..8586e70 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -64,7 +64,6 @@ pub enum InterruptInitError { PitNotHandled, } -#[derive(Debug)] pub struct InterruptController { local_apic: apic::LocalApic, io_apic: io_apic::IoApic, @@ -164,8 +163,6 @@ pub unsafe fn enter_user( user_instruction_pointer: VirtualAddr, user_stack_pointer: VirtualAddr, ) -> ! { - println!("Entering user mode"); - unsafe { asm!( "mov ds, {user_data_selector:x}", diff --git a/src/arch/x86_64/paging.rs b/src/arch/x86_64/paging.rs index a7bbca3..c7ea8fd 100644 --- a/src/arch/x86_64/paging.rs +++ b/src/arch/x86_64/paging.rs @@ -11,7 +11,7 @@ use crate::{ pub const PAGE_SIZE: usize = 4096; pub const PAGE_TABLE_ENTRIES: usize = 512; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct PagingConfig { physical_address_bits: u8, global_pages: bool, @@ -42,7 +42,7 @@ impl PagingConfig { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] enum PagingMode { FourLevel, FiveLevel, @@ -77,7 +77,7 @@ impl PagingMode { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] enum PageTableLevel { Pml5, Pml4, @@ -106,14 +106,13 @@ impl PageTableLevel { } } -#[derive(Debug)] enum PageTableEntryError { PhysicalAddressTooLarge, NoExecuteUnsupported, } #[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] struct PageTableEntry(u64); impl PageTableEntry { @@ -229,7 +228,6 @@ impl PageTableEntry { } } -#[derive(Debug)] pub(crate) enum MapError { InvalidVirtualAddress, VirtualAddressUnaligned, @@ -264,7 +262,6 @@ pub(crate) enum PageTableCreateError { OutOfFrames, } -#[derive(Debug)] pub struct PageTable { pub direct_map: DirectMap, config: PagingConfig, diff --git a/src/arch/x86_64/syscall.rs b/src/arch/x86_64/syscall.rs index 97da27e..622174d 100644 --- a/src/arch/x86_64/syscall.rs +++ b/src/arch/x86_64/syscall.rs @@ -15,7 +15,6 @@ const IA32_KERNEL_GS_BASE: u32 = 0xC000_0102; const RFLAGS_MASK: u64 = 0x257FD5; // Clear IF, TF, DF, IOPL, NT, AC #[repr(C)] -#[derive(Debug)] struct SyscallFrame { pub r15: u64, pub r14: u64, diff --git a/src/format/elf.rs b/src/format/elf.rs index 290ac46..81fc737 100644 --- a/src/format/elf.rs +++ b/src/format/elf.rs @@ -1,423 +1,105 @@ -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ElfIsa { - None, - Sparc, - X86, - Mips, - Ppc, - Arm, - SuperH, - Ia64, - Amd64, - AArch64, - Riscv, -} +pub struct ElfError; -impl ElfIsa { - fn from_u16(value: u16) -> Result { - match value { - 0x00 => Ok(Self::None), - 0x02 => Ok(Self::Sparc), - 0x03 => Ok(Self::X86), - 0x08 => Ok(Self::Mips), - 0x14 => Ok(Self::Ppc), - 0x28 => Ok(Self::Arm), - 0x2A => Ok(Self::SuperH), - 0x32 => Ok(Self::Ia64), - 0x3E => Ok(Self::Amd64), - 0xB7 => Ok(Self::AArch64), - 0xF3 => Ok(Self::Riscv), - _ => Err(ElfError::InvalidElf), - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ElfClass { - Elf32, - Elf64, -} - -impl ElfClass { - fn from_u8(value: u8) -> Result { - match value { - 1 => Ok(Self::Elf32), - 2 => Ok(Self::Elf64), - _ => Err(ElfError::InvalidElf), - } - } - - const fn header_size(self) -> u16 { - match self { - Self::Elf32 => 52, - Self::Elf64 => 64, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Endianness { - Little, - Big, -} - -impl Endianness { - fn from_u8(value: u8) -> Result { - match value { - 1 => Ok(Self::Little), - 2 => Ok(Self::Big), - _ => Err(ElfError::InvalidElf), - } - } -} - -#[repr(u16)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ElfType { - Relocatable = 1, - Executable = 2, - SharedObject = 3, - Core = 4, -} - -impl ElfType { - fn from_u16(value: u16) -> Result { - match value { - 1 => Ok(Self::Relocatable), - 2 => Ok(Self::Executable), - 3 => Ok(Self::SharedObject), - 4 => Ok(Self::Core), - _ => Err(ElfError::InvalidElf), - } - } -} - -#[derive(Debug)] -#[allow(unused)] -pub struct ElfHeader { - magic: [u8; 4], - pub class: ElfClass, - endianness: Endianness, - version: u8, - os_abi: u8, - _reserved: [u8; 8], - pub object_type: ElfType, - pub machine: ElfIsa, - version_1: u32, - entry: u64, // 2115136 - program_header_offset: u64, // 64 - section_header_offset: u64, // 2759752 - flags: u32, // 0 - header_size: u16, // 64 - program_header_entry_size: u16, // 56 - program_header_count: u16, // 6 - section_header_entry_size: u16, // 64 - section_header_count: u16, // 17 - section_name_index: u16, // 15 -} - -#[derive(Debug)] -pub enum ElfError { - InvalidElf, -} - -impl ElfHeader { - pub fn parse(bytes: &[u8]) -> Result { - let mut reader = Reader::new(bytes); - - let magic = reader.read_array()?; - if magic != *b"\x7fELF" { - return Err(ElfError::InvalidElf); - } - - let class = ElfClass::from_u8(reader.read_u8()?)?; - let endianness = Endianness::from_u8(reader.read_u8()?)?; - reader.set_endianness(endianness); - - let version = reader.read_u8()?; - let os_abi = reader.read_u8()?; - let reserved = reader.read_array()?; - let object_type = ElfType::from_u16(reader.read_u16()?)?; - let machine = ElfIsa::from_u16(reader.read_u16()?)?; - let version_1 = reader.read_u32()?; - let entry = reader.read_word(class)?; - let program_header_offset = reader.read_word(class)?; - let section_header_offset = reader.read_word(class)?; - let flags = reader.read_u32()?; - let header_size = reader.read_u16()?; - let program_header_entry_size = reader.read_u16()?; - let program_header_count = reader.read_u16()?; - let section_header_entry_size = reader.read_u16()?; - let section_header_count = reader.read_u16()?; - let section_name_index = reader.read_u16()?; - - if header_size != class.header_size() { - return Err(ElfError::InvalidElf); - } - - Ok(Self { - magic, - class, - endianness, - version, - os_abi, - _reserved: reserved, - object_type, - machine, - version_1, - entry, - program_header_offset, - section_header_offset, - flags, - header_size, - program_header_entry_size, - program_header_count, - section_header_entry_size, - section_header_count, - section_name_index, - }) - } -} - -#[repr(u32)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ProgramHeaderType { - Null = 0, - Load = 1, - Dynamic = 2, - Interpreter = 3, - Note = 4, - Shlib = 5, - Phdr = 6, - GnuStack = 0x6474e551, - Relro = 0x6474e552, - Other(u32), -} - -impl ProgramHeaderType { - fn from_u32(value: u32) -> Self { - match value { - 0 => Self::Null, - 1 => Self::Load, - 2 => Self::Dynamic, - 3 => Self::Interpreter, - 4 => Self::Note, - 5 => Self::Shlib, - 6 => Self::Phdr, - 0x6474e551 => Self::GnuStack, - 0x6474e552 => Self::Relro, - _ => Self::Other(value), - } - } -} - -#[derive(Debug)] -pub struct ProgramHeader { - pub segment_type: ProgramHeaderType, - pub flags: u32, - pub file_offset: u64, - pub virtual_address: u64, - _physical_address: u64, - pub file_size: u64, - pub memory_size: u64, - pub alignment: u64, -} - -impl ProgramHeader { - pub fn parse(bytes: &[u8], class: ElfClass, endianness: Endianness) -> Result { - let mut reader = Reader::new(bytes); - reader.set_endianness(endianness); - - let segment_type = ProgramHeaderType::from_u32(reader.read_u32()?); - - let flags = if class == ElfClass::Elf64 { - reader.read_u32()? - } else { - 0 - }; - - let file_offset = reader.read_word(class)?; - let virtual_address = reader.read_word(class)?; - let physical_address = reader.read_word(class)?; - let file_size = reader.read_word(class)?; - let memory_size = reader.read_word(class)?; - - let flags = if class == ElfClass::Elf32 { - reader.read_u32()? - } else { - flags - }; - - let alignment = reader.read_word(class)?; - - Ok(Self { - segment_type, - flags, - file_offset, - virtual_address, - _physical_address: physical_address, - file_size, - memory_size, - alignment, - }) - } -} +#[cfg(target_arch = "x86_64")] +const MACHINE: u16 = 62; +#[cfg(target_arch = "aarch64")] +const MACHINE: u16 = 183; +#[cfg(target_arch = "riscv64")] +const MACHINE: u16 = 243; pub struct Elf<'a> { bytes: &'a [u8], - header: ElfHeader, + headers: &'a [u8], + pub entry: usize, +} + +pub struct Segment<'a> { + pub data: &'a [u8], + pub address: usize, + pub memory_size: usize, + pub writable: bool, + pub executable: bool, } impl<'a> Elf<'a> { pub fn parse(bytes: &'a [u8]) -> Result { - let header = ElfHeader::parse(bytes)?; - - Ok(Self { bytes, header }) - } - - pub fn program_headers(&self) -> Result, ElfError> { - let offset = - usize::try_from(self.header.program_header_offset).map_err(|_| ElfError::InvalidElf)?; - let entry_size = usize::from(self.header.program_header_entry_size); - let count = usize::from(self.header.program_header_count); - - let expected_entry_size = match self.header.class { - ElfClass::Elf32 => 32, - ElfClass::Elf64 => 56, - }; - - if entry_size != expected_entry_size { - return Err(ElfError::InvalidElf); + let header = bytes.get(..64).ok_or(ElfError)?; + // Bootstrap images are static ELF64 executables in the native ISA, always LE. + if &header[..7] != b"\x7fELF\x02\x01\x01" + || u16_at(header, 16) != 2 + || u16_at(header, 18) != MACHINE + || u32_at(header, 20) != 1 + || u16_at(header, 52) != 64 + || u16_at(header, 54) != 56 + { + return Err(ElfError); } - let table_size = entry_size.checked_mul(count).ok_or(ElfError::InvalidElf)?; - let table_end = offset.checked_add(table_size).ok_or(ElfError::InvalidElf)?; - let bytes = self - .bytes - .get(offset..table_end) - .ok_or(ElfError::InvalidElf)?; + let offset = usize_at(header, 32); + let count = usize::from(u16_at(header, 56)); + let end = offset.checked_add(count * 56).ok_or(ElfError)?; + let headers = bytes.get(offset..end).ok_or(ElfError)?; + if headers + .chunks_exact(56) + .any(|h| matches!(u32_at(h, 0), 2 | 3)) + { + // There is no dynamic linker or relocation processing during bootstrap. + return Err(ElfError); + } - Ok(ProgramHeaders { + Ok(Self { bytes, - class: self.header.class, - endianness: self.header.endianness, - entry_size, - remaining: count, + headers, + entry: usize_at(header, 24), }) } - pub fn bytes(&self) -> &[u8] { - self.bytes - } - - pub fn machine(&self) -> ElfIsa { - self.header.machine - } - - pub fn entry(&self) -> usize { - self.header.entry as usize + pub fn segments(&self) -> impl Iterator, ElfError>> + '_ { + self.headers + .chunks_exact(56) + .filter(|h| u32_at(h, 0) == 1) + .map(|h| { + let offset = usize_at(h, 8); + let address = usize_at(h, 16); + let file_size = usize_at(h, 32); + let memory_size = usize_at(h, 40); + let alignment = usize_at(h, 48); + if file_size > memory_size + || (alignment > 1 + && (!alignment.is_power_of_two() + || address % alignment != offset % alignment)) + { + return Err(ElfError); + } + let end = offset.checked_add(file_size).ok_or(ElfError)?; + let data = self.bytes.get(offset..end).ok_or(ElfError)?; + let flags = u32_at(h, 4); + Ok(Segment { + data, + address, + memory_size, + writable: flags & 2 != 0, + executable: flags & 1 != 0, + }) + }) } } -pub struct ProgramHeaders<'a> { - bytes: &'a [u8], - class: ElfClass, - endianness: Endianness, - entry_size: usize, - remaining: usize, +// Callers only read fixed offsets within already bounds-checked headers. +fn u16_at(bytes: &[u8], offset: usize) -> u16 { + let mut value = [0; 2]; + value.copy_from_slice(&bytes[offset..offset + 2]); + u16::from_le_bytes(value) } -impl<'a> Iterator for ProgramHeaders<'a> { - type Item = Result; - - fn next(&mut self) -> Option { - if self.remaining == 0 { - return None; - } - - let entry = match self.bytes.get(..self.entry_size) { - Some(entry) => entry, - None => { - self.remaining = 0; - return None; - } - }; - - self.bytes = &self.bytes[self.entry_size..]; - self.remaining -= 1; - - Some(ProgramHeader::parse(entry, self.class, self.endianness)) - } - - fn size_hint(&self) -> (usize, Option) { - (self.remaining, Some(self.remaining)) - } +fn u32_at(bytes: &[u8], offset: usize) -> u32 { + let mut value = [0; 4]; + value.copy_from_slice(&bytes[offset..offset + 4]); + u32::from_le_bytes(value) } -impl ExactSizeIterator for ProgramHeaders<'_> {} - -struct Reader<'a> { - bytes: &'a [u8], - offset: usize, - endianness: Endianness, -} - -impl<'a> Reader<'a> { - const fn new(bytes: &'a [u8]) -> Self { - Self { - bytes, - offset: 0, - endianness: Endianness::Little, - } - } - - fn set_endianness(&mut self, endianness: Endianness) { - self.endianness = endianness; - } - - fn read_array(&mut self) -> Result<[u8; N], ElfError> { - let end = self.offset.checked_add(N).ok_or(ElfError::InvalidElf)?; - let bytes = self - .bytes - .get(self.offset..end) - .ok_or(ElfError::InvalidElf)?; - self.offset = end; - - bytes.try_into().map_err(|_| ElfError::InvalidElf) - } - - fn read_u8(&mut self) -> Result { - Ok(self.read_array::<1>()?[0]) - } - - fn read_u16(&mut self) -> Result { - let bytes = self.read_array()?; - Ok(match self.endianness { - Endianness::Little => u16::from_le_bytes(bytes), - Endianness::Big => u16::from_be_bytes(bytes), - }) - } - - fn read_u32(&mut self) -> Result { - let bytes = self.read_array()?; - Ok(match self.endianness { - Endianness::Little => u32::from_le_bytes(bytes), - Endianness::Big => u32::from_be_bytes(bytes), - }) - } - - fn read_u64(&mut self) -> Result { - let bytes = self.read_array()?; - Ok(match self.endianness { - Endianness::Little => u64::from_le_bytes(bytes), - Endianness::Big => u64::from_be_bytes(bytes), - }) - } - - fn read_word(&mut self, class: ElfClass) -> Result { - match class { - ElfClass::Elf32 => Ok(u64::from(self.read_u32()?)), - ElfClass::Elf64 => self.read_u64(), - } - } +fn usize_at(bytes: &[u8], offset: usize) -> usize { + let mut value = [0; 8]; + value.copy_from_slice(&bytes[offset..offset + 8]); + u64::from_le_bytes(value) as usize } diff --git a/src/main.rs b/src/main.rs index 3ca03ca..26036a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ mod task; use crate::{ debug::serial, - memory::{AddressSpace, KernelStackPool, MemoryRegionKind}, + memory::{AddressSpace, MemoryRegionKind, init_frame_allocator, init_kernel_address_space}, }; pub struct KernelHandoff { @@ -22,7 +22,6 @@ pub struct KernelHandoff { address_space: AddressSpace, direct_map: memory::DirectMap, boot_info: boot::BootInfo, - kernel_stack_pool: KernelStackPool, handoff_frame: memory::OwnedFrame, } @@ -51,11 +50,9 @@ pub extern "C" fn _start() -> ! { println!("Entering kernel main..."); - let mut kernel_stack_pool = KernelStackPool::new(); - - let kernel_stack = kernel_stack_pool - .allocate(&mut address_space, &mut allocator) - .expect("failed to allocate bootstrap stack"); + let kernel_stack = + crate::task::scheduler::allocate_kernel_stack(&mut address_space, &mut allocator) + .expect("failed to allocate bootstrap stack"); let handoff_frame = allocator .alloc() @@ -70,7 +67,6 @@ pub extern "C" fn _start() -> ! { address_space, direct_map, boot_info, - kernel_stack_pool, handoff_frame, }; @@ -88,21 +84,13 @@ pub extern "C" fn _start() -> ! { } pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { - let ( - mut allocator, - mut address_space, - direct_map, - boot_info, - mut kernel_stack_pool, - handoff_frame, - ) = unsafe { + let (mut allocator, mut address_space, direct_map, boot_info, handoff_frame) = unsafe { let handoff = handoff.read(); ( handoff.allocator, handoff.address_space, handoff.direct_map, handoff.boot_info, - handoff.kernel_stack_pool, handoff.handoff_frame, ) }; @@ -129,27 +117,20 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { println!("Initializing interrupt controller..."); - let interrupt_controller = + let _interrupt_controller = arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space) .expect("failed to initialize interrupt controller"); task::bootstrap::spawn( "omega3.elf", - boot_info.initramfs.data(), + &boot_info.initramfs, &mut address_space, &mut allocator, direct_map, - &mut kernel_stack_pool, ); - task::bootstrap::spawn( - "client.elf", - boot_info.initramfs.data(), - &mut address_space, - &mut allocator, - direct_map, - &mut kernel_stack_pool, - ); + init_frame_allocator(allocator); + init_kernel_address_space(address_space); task::scheduler::start(); } diff --git a/src/memory/address_space.rs b/src/memory/address_space.rs index 95c45be..9b4f795 100644 --- a/src/memory/address_space.rs +++ b/src/memory/address_space.rs @@ -1,3 +1,5 @@ +use core::cell::UnsafeCell; + use crate::{ arch::{PageTable, PageTableCreateError, PageTableMapError, PageTableUnmapError, PagingConfig}, memory::{ @@ -6,6 +8,105 @@ use crate::{ }, }; +const MAX_ADDRESS_SPACES: usize = 32; +struct AddressSpaceTable { + entries: [Option; MAX_ADDRESS_SPACES], +} + +impl AddressSpaceTable { + const fn new() -> Self { + Self { + entries: [const { None }; MAX_ADDRESS_SPACES], + } + } + + fn insert(&mut self, address_space: AddressSpace) -> Option { + for (i, slot) in self.entries.iter_mut().enumerate() { + if slot.is_none() { + *slot = Some(address_space); + return Some(i); + } + } + + None + } + + fn get(&self, id: usize) -> Option<&AddressSpace> { + self.entries.get(id).and_then(Option::as_ref) + } + + fn get_mut(&mut self, id: usize) -> Option<&mut AddressSpace> { + self.entries.get_mut(id).and_then(Option::as_mut) + } + + fn remove(&mut self, id: usize) -> Option { + self.entries.get_mut(id).and_then(Option::take) + } +} + +struct GlobalAddressSpaceTable(UnsafeCell); + +unsafe impl Sync for GlobalAddressSpaceTable {} + +static ADDRESS_SPACE_TABLE: GlobalAddressSpaceTable = + GlobalAddressSpaceTable(UnsafeCell::new(AddressSpaceTable::new())); + +pub fn insert_address_space(address_space: AddressSpace) -> Option { + let table = unsafe { &mut *ADDRESS_SPACE_TABLE.0.get() }; + + let id = table.insert(address_space); + id +} + +pub fn remove_address_space(id: usize) -> Option { + let table = unsafe { &mut *ADDRESS_SPACE_TABLE.0.get() }; + + table.remove(id) +} + +pub fn with_address_space(id: usize, f: impl FnOnce(&AddressSpace) -> R) -> Option { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + let table = unsafe { &*ADDRESS_SPACE_TABLE.0.get() }; + let res = table.get(id).map(f); + crate::arch::restore_interrupts(interrupt_state); + res +} + +pub fn with_address_space_mut(id: usize, f: impl FnOnce(&mut AddressSpace) -> R) -> Option { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + let table = unsafe { &mut *ADDRESS_SPACE_TABLE.0.get() }; + let res = table.get_mut(id).map(f); + crate::arch::restore_interrupts(interrupt_state); + res +} + +struct GlobalKernelAddressSpace(UnsafeCell>); + +unsafe impl Sync for GlobalKernelAddressSpace {} + +static KERNEL_ADDRESS_SPACE: GlobalKernelAddressSpace = + GlobalKernelAddressSpace(UnsafeCell::new(None)); + +pub fn init_kernel_address_space(address_space: AddressSpace) { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + unsafe { + *KERNEL_ADDRESS_SPACE.0.get() = Some(address_space); + } + crate::arch::restore_interrupts(interrupt_state); +} + +pub fn with_kernel_address_space(f: impl FnOnce(&mut AddressSpace) -> R) -> R { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + let space = unsafe { + (&mut *KERNEL_ADDRESS_SPACE.0.get()) + .as_mut() + .expect("kernel address space not initialized") + }; + let res = f(space); + crate::arch::restore_interrupts(interrupt_state); + res +} + #[derive(Debug)] pub enum MapError { InvalidVirtualAddress, @@ -82,13 +183,13 @@ impl From for AddressSpaceCreateError { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(PartialEq, Eq)] enum AddressSpaceKind { Kernel, User, } -#[derive(Debug, PartialEq, Eq)] +#[derive(PartialEq, Eq)] pub struct AddressSpace { root: PageTable, kind: AddressSpaceKind, diff --git a/src/memory/frame.rs b/src/memory/frame.rs index 4ec9112..b26af76 100644 --- a/src/memory/frame.rs +++ b/src/memory/frame.rs @@ -1,3 +1,5 @@ +use core::cell::UnsafeCell; + use crate::memory::{DirectMap, MemoryRegion, MemoryRegionKind, PhysicalAddr, VirtualAddr}; pub const FRAME_SIZE: usize = 4096; @@ -12,15 +14,57 @@ pub fn align_down_to_frame(addr: usize) -> usize { } #[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] enum FrameState { Reserved = 0b00, Free = 0b01, Allocated = 0b10, } +struct GlobalFrameAllocator(UnsafeCell>); +unsafe impl Sync for GlobalFrameAllocator {} + +static FRAME_ALLOCATOR: GlobalFrameAllocator = GlobalFrameAllocator(UnsafeCell::new(None)); + +pub fn init_global(allocator: FrameAllocator) { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + unsafe { + *FRAME_ALLOCATOR.0.get() = Some(allocator); + } + crate::arch::restore_interrupts(interrupt_state); +} + +pub fn alloc_frame() -> Option { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + let allocator = unsafe { &mut *FRAME_ALLOCATOR.0.get() }; + let frame = allocator.as_mut().and_then(|a| a.alloc()); + crate::arch::restore_interrupts(interrupt_state); + frame +} + +pub unsafe fn dealloc_frame(frame: OwnedFrame) { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + let allocator = unsafe { &mut *FRAME_ALLOCATOR.0.get() }; + if let Some(a) = allocator.as_mut() { + unsafe { a.dealloc(frame) }; + } + crate::arch::restore_interrupts(interrupt_state); +} + +#[allow(unused)] +pub fn with_allocator(f: impl FnOnce(&mut FrameAllocator) -> R) -> R { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + let allocator = unsafe { + (&mut *FRAME_ALLOCATOR.0.get()) + .as_mut() + .expect("frame allocator not initialized") + }; + let result = f(allocator); + crate::arch::restore_interrupts(interrupt_state); + result +} + // 64 KiB per GiB -#[derive(Debug)] struct Bitmap { start: VirtualAddr, frame_count: usize, @@ -70,7 +114,6 @@ pub enum FrameAllocatorInitError { } // very very simple bitmap frame/page allocator -#[derive(Debug)] pub struct FrameAllocator { bitmap: Bitmap, next_search: usize, @@ -327,7 +370,6 @@ impl FrameAddr { } // specifically not Clone or Copy -#[derive(Debug)] pub struct OwnedFrame { frame: FrameAddr, } diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 3efecd1..e8dc970 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -6,8 +6,15 @@ mod user; use core::ops::Add; #[allow(unused)] -pub use address_space::{AddressSpace, AddressSpaceCreateError, MapError, UnmapError}; -pub use frame::{FRAME_SIZE, FrameAddr, FrameAllocator, OwnedFrame}; +pub use address_space::{ + AddressSpace, AddressSpaceCreateError, MapError, UnmapError, init_kernel_address_space, + insert_address_space, remove_address_space, with_address_space, with_address_space_mut, + with_kernel_address_space, +}; +pub use frame::{ + FRAME_SIZE, FrameAddr, FrameAllocator, OwnedFrame, alloc_frame, dealloc_frame, + init_global as init_frame_allocator, with_allocator, +}; #[allow(unused)] pub use stack::{KernelStack, KernelStackPool, StackCreateError, UserStack}; #[allow(unused)] @@ -31,8 +38,6 @@ impl BootString { } } -const MAX_MODULE_PATH_LENGTH: usize = 256; - pub struct InitramfsImage { pub start: VirtualAddr, pub length: usize, @@ -55,7 +60,7 @@ pub struct KernelMemoryLayout { pub segments: [KernelSegment; 3], } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct PagePermissions { pub writable: bool, pub executable: bool, @@ -117,7 +122,7 @@ impl Add for VirtualAddr { } #[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum MemoryRegionKind { Usable, Reserved, @@ -130,20 +135,20 @@ pub enum MemoryRegionKind { MappedReserved, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub enum CachePolicy { Uncacheable, WriteBack, } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct MemoryRegion { pub start: PhysicalAddr, pub length: usize, pub kind: MemoryRegionKind, } -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] pub struct DirectMap { offset: usize, } diff --git a/src/memory/stack.rs b/src/memory/stack.rs index 2f03a09..59f65d9 100644 --- a/src/memory/stack.rs +++ b/src/memory/stack.rs @@ -176,7 +176,6 @@ impl KernelStack { } } -#[derive(Debug)] pub struct UserStack { mapping: StackMapping, } @@ -216,7 +215,7 @@ pub struct KernelStackPool { } impl KernelStackPool { - pub fn new() -> Self { + pub const fn new() -> Self { Self { free_slots: 0 } } diff --git a/src/platform/acpi.rs b/src/platform/acpi.rs index cd1d2bd..c7e9f38 100644 --- a/src/platform/acpi.rs +++ b/src/platform/acpi.rs @@ -12,7 +12,6 @@ pub enum AcpiError { MultipleIoApicsUnsupported, } -#[derive(Debug)] pub struct AcpiTables { direct_map: DirectMap, root: RootTable, @@ -215,7 +214,6 @@ impl AcpiTables { } } -#[derive(Debug)] enum RootTable { Rsdt(Sdt), Xsdt(Sdt), @@ -251,7 +249,7 @@ impl RootTable { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] struct Rsdp { signature: [u8; 8], checksum: u8, @@ -261,7 +259,7 @@ struct Rsdp { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] struct Xsdp { rsdp: Rsdp, length: u32, @@ -271,7 +269,7 @@ struct Xsdp { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] struct SDTHeader { signature: [u8; 4], length: u32, @@ -284,14 +282,12 @@ struct SDTHeader { creator_revision: u32, } -#[derive(Debug)] pub struct Sdt { physical_addr: PhysicalAddr, length: usize, signature: [u8; 4], } -#[derive(Debug)] #[allow(unused)] pub struct Madt<'a> { acpi: &'a AcpiTables, @@ -301,7 +297,7 @@ pub struct Madt<'a> { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] struct MadtBody { local_apic_address: u32, flags: u32, @@ -543,21 +539,20 @@ impl<'a> Iterator for MadtEntries<'a> { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct MadtEntryHeader { kind: u8, length: u8, } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct LocalApicEntry { processor_id: u8, id: u8, flags: u32, } -#[derive(Debug)] pub struct IoApicInfo { pub id: u8, pub apic_address: PhysicalAddr, @@ -565,7 +560,7 @@ pub struct IoApicInfo { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct IoApicEntry { id: u8, reserved: u8, @@ -574,7 +569,7 @@ pub struct IoApicEntry { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct InterruptSourceOverride { bus: u8, source: u8, @@ -582,19 +577,19 @@ pub struct InterruptSourceOverride { flags: u16, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub enum InterruptPolarity { ActiveHigh, ActiveLow, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub enum TriggerMode { Edge, Level, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct IsaIrqRoute { pub gsi: u32, pub polarity: InterruptPolarity, @@ -602,7 +597,7 @@ pub struct IsaIrqRoute { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct IoApicNmiEntry { nmi_source: u8, reserved: u8, @@ -611,7 +606,7 @@ pub struct IoApicNmiEntry { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct LocalApicNmiEntry { processor_id: u8, flags: u16, @@ -619,14 +614,14 @@ pub struct LocalApicNmiEntry { } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct LocalApicAddressOverride { reserved: u16, local_apic_address: u64, } #[repr(C, packed)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct LocalX2ApicEntry { reserved: u16, local_x2apic_id: u32, @@ -634,7 +629,7 @@ pub struct LocalX2ApicEntry { acpi_processor_uid: u32, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] #[allow(unused)] pub enum MadtEntry { LocalApic(LocalApicEntry), diff --git a/src/syscall/mod.rs b/src/syscall/mod.rs index 43138f2..c31286a 100644 --- a/src/syscall/mod.rs +++ b/src/syscall/mod.rs @@ -2,7 +2,7 @@ mod table; use table::*; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] #[repr(u64)] pub enum Status { // Status::Success = 0 @@ -11,9 +11,10 @@ pub enum Status { BadFileDescriptor = 3, // EBADF NoSuchTask = 4, // ESRCH OutOfMemory = 5, // ENOMEM + BadHandle = 6, // EBADH } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] #[repr(u64)] pub enum SyscallNumber { Yield = 1, @@ -21,6 +22,12 @@ pub enum SyscallNumber { Write = 3, Send = 4, Recv = 5, + FrameAlloc = 6, + FrameDealloc = 7, + AsCreate = 8, + Map = 9, + Unmap = 10, + TaskCreate = 11, } impl TryFrom for SyscallNumber { @@ -32,6 +39,12 @@ impl TryFrom for SyscallNumber { 3 => Ok(Self::Write), 4 => Ok(Self::Send), 5 => Ok(Self::Recv), + 6 => Ok(Self::FrameAlloc), + 7 => Ok(Self::FrameDealloc), + 8 => Ok(Self::AsCreate), + 9 => Ok(Self::Map), + 10 => Ok(Self::Unmap), + 11 => Ok(Self::TaskCreate), _ => Err(Status::InvalidArgument), } } @@ -50,6 +63,16 @@ pub fn handle(num: u64, arg0: u64, arg1: u64, arg2: u64, arg3: u64, _arg4: u64, SyscallNumber::Recv => { sys_recv(arg0 as usize, arg1 as usize, arg2 as usize, arg3 as usize) } + SyscallNumber::FrameAlloc => sys_frame_alloc(arg0 as usize), + SyscallNumber::FrameDealloc => sys_frame_dealloc(arg0 as usize), + SyscallNumber::AsCreate => sys_as_create(arg0 as usize), + SyscallNumber::Map => { + sys_map(arg0 as usize, arg1 as usize, arg2 as usize, arg3 as usize) + } + SyscallNumber::Unmap => sys_unmap(arg0 as usize, arg1 as usize), + SyscallNumber::TaskCreate => { + sys_task_create(arg0 as usize, arg1 as usize, arg2 as usize, arg3 as usize) + } } })(); diff --git a/src/syscall/table.rs b/src/syscall/table.rs index 2e8143e..b310180 100644 --- a/src/syscall/table.rs +++ b/src/syscall/table.rs @@ -1,6 +1,10 @@ use crate::{ - memory::{VirtualAddr, copy_from_user, copy_to_user, copy_val_to_user}, - task::tcb::{BlockReason, MAX_MSG_SIZE, Message}, + memory::{ + FRAME_SIZE, OwnedFrame, PagePermissions, USER_SPACE_END, VirtualAddr, copy_from_user, + copy_to_user, copy_val_to_user, + }, + println, + task::tcb::{BlockReason, Handle, KernelObject, MAX_MSG_SIZE, Message, Rights}, }; use super::Status; @@ -76,19 +80,17 @@ pub fn sys_recv( return Err(Status::InvalidArgument); } - let mut interrupt_state = crate::arch::disable_interrupts_and_save(); - let current_task = crate::task::scheduler::get_task_mut(crate::task::scheduler::current()) .ok_or(Status::NoSuchTask)?; if current_task.mailbox.len == 0 { - crate::arch::restore_interrupts(interrupt_state); crate::task::scheduler::block_current(BlockReason::Recv { ep: 0 }); - interrupt_state = crate::arch::disable_interrupts_and_save(); } // if we blocked, we will wake up when the mailbox is non-empty + let current_task = crate::task::scheduler::get_task_mut(crate::task::scheduler::current()) + .ok_or(Status::NoSuchTask)?; let msg = current_task.mailbox.pop().ok_or(Status::NoSuchTask)?; copy_to_user( @@ -102,7 +104,255 @@ pub fn sys_recv( copy_val_to_user(VirtualAddr::new(out_sender), &msg.sender)?; } - crate::arch::restore_interrupts(interrupt_state); + Ok(()) +} + +pub fn sys_frame_alloc(out_handle: usize) -> Result<(), Status> { + if out_handle == 0 { + return Err(Status::InvalidArgument); + } + + let frame = crate::memory::alloc_frame().ok_or(Status::OutOfMemory)?; + + let task_id = crate::task::scheduler::current(); + let task = match crate::task::scheduler::get_task_mut(task_id) { + Some(task) => task, + None => { + unsafe { crate::memory::dealloc_frame(frame) }; + return Err(Status::NoSuchTask); + } + }; + + let frame_addr = frame.into_raw(); + let handle = Handle { + object: KernelObject::Frame(frame_addr), + rights: Rights::READ | Rights::WRITE | Rights::EXECUTE, + }; + + let handle_id = match task.handles.push(handle) { + Some(id) => id, + None => { + unsafe { crate::memory::dealloc_frame(OwnedFrame::from_raw(frame_addr)) }; + return Err(Status::OutOfMemory); + } + }; + + copy_val_to_user(VirtualAddr::new(out_handle), &handle_id)?; + + Ok(()) +} + +pub fn sys_frame_dealloc(frame_handle: usize) -> Result<(), Status> { + let task = crate::task::scheduler::current(); + let task = crate::task::scheduler::get_task_mut(task).ok_or(Status::NoSuchTask)?; + + let frame_handle = task.handles.get(frame_handle).ok_or(Status::BadHandle)?; + let frame = match frame_handle.object { + KernelObject::Frame(frame_addr) => frame_addr, + _ => return Err(Status::InvalidArgument), + }; + + unsafe { crate::memory::dealloc_frame(OwnedFrame::from_raw(frame)) }; + + Ok(()) +} + +pub fn sys_as_create(out_handle: usize) -> Result<(), Status> { + if out_handle == 0 { + return Err(Status::InvalidArgument); + } + + let task_id = crate::task::scheduler::current(); + let task = crate::task::scheduler::get_task_mut(task_id).ok_or(Status::NoSuchTask)?; + + let new_as = match crate::memory::with_address_space(task.as_id, |caller_as| { + crate::memory::with_allocator(|allocator| caller_as.new_user(allocator)) + }) { + Some(Ok(as_space)) => as_space, + _ => return Err(Status::OutOfMemory), + }; + + let as_id = crate::memory::insert_address_space(new_as).ok_or(Status::OutOfMemory)?; + + let handle = Handle { + object: KernelObject::AddressSpace(as_id), + rights: Rights::READ | Rights::WRITE | Rights::EXECUTE, + }; + + let handle_id = match task.handles.push(handle) { + Some(id) => id, + None => { + crate::memory::remove_address_space(as_id); + return Err(Status::OutOfMemory); + } + }; + + copy_val_to_user(VirtualAddr::new(out_handle), &handle_id)?; + + Ok(()) +} + +pub fn sys_map( + as_handle: usize, + frame_handle: usize, + virtual_addr: usize, + permissions: usize, +) -> Result<(), Status> { + if virtual_addr % FRAME_SIZE != 0 { + return Err(Status::InvalidArgument); + } + + if virtual_addr >= USER_SPACE_END.as_usize() { + return Err(Status::InvalidArgument); + } + + let task = crate::task::scheduler::current(); + let task = crate::task::scheduler::get_task_mut(task).ok_or(Status::NoSuchTask)?; + + let as_handle = task.handles.get(as_handle).ok_or(Status::BadHandle)?; + let as_id = match as_handle.object { + KernelObject::AddressSpace(as_id) => as_id, + _ => return Err(Status::InvalidArgument), + }; + + let frame_handle = task.handles.get(frame_handle).ok_or(Status::BadHandle)?; + let frame = match frame_handle.object { + KernelObject::Frame(frame_addr) => frame_addr, + _ => return Err(Status::InvalidArgument), + }; + + let virtual_addr = VirtualAddr::new(virtual_addr); + let permissions = PagePermissions::new( + permissions & (1 << 0) != 0, + permissions & (1 << 1) != 0, + true, + ); + + let map_result = crate::memory::with_address_space_mut(as_id, |target_as| { + crate::memory::with_allocator(|allocator| { + target_as.map( + frame.start_address(), + virtual_addr, + permissions, + allocator, + crate::memory::CachePolicy::WriteBack, + ) + }) + }) + .ok_or(Status::BadHandle)?; + + map_result.map_err(|_| Status::OutOfMemory)?; + + Ok(()) +} + +pub fn sys_unmap(as_handle: usize, virtual_addr: usize) -> Result<(), Status> { + if virtual_addr % FRAME_SIZE != 0 { + return Err(Status::InvalidArgument); + } + + if virtual_addr >= USER_SPACE_END.as_usize() { + return Err(Status::InvalidArgument); + } + + let task = crate::task::scheduler::current(); + let task = crate::task::scheduler::get_task_mut(task).ok_or(Status::NoSuchTask)?; + + let as_handle = task.handles.get(as_handle).ok_or(Status::BadHandle)?; + let as_id = match as_handle.object { + KernelObject::AddressSpace(as_id) => as_id, + _ => return Err(Status::InvalidArgument), + }; + + let virtual_addr = VirtualAddr::new(virtual_addr); + + let map_result = crate::memory::with_address_space_mut(as_id, |target_as| { + if target_as.to_physical(virtual_addr).is_none() { + return Err(Status::InvalidArgument); + } + + crate::memory::with_allocator(|allocator| unsafe { + target_as.unmap(virtual_addr, allocator) + }) + .map_err(|_| Status::BadAddress) + }) + .ok_or(Status::BadHandle)?; + + map_result.map_err(|_| Status::OutOfMemory)?; + + Ok(()) +} + +pub fn sys_task_create( + as_handle: usize, + entry: usize, + user_stack: usize, + out_task_handle: usize, +) -> Result<(), Status> { + if out_task_handle == 0 || entry == 0 || user_stack == 0 { + return Err(Status::InvalidArgument); + } + + if entry >= 0x0000_8000_0000_0000 || user_stack >= 0x0000_8000_0000_0000 { + return Err(Status::BadAddress); + } + + let task_id = crate::task::scheduler::current(); + let task = crate::task::scheduler::get_task_mut(task_id).ok_or(Status::NoSuchTask)?; + + let as_handle = task.handles.get(as_handle).ok_or(Status::BadHandle)?; + let as_id = match as_handle.object { + KernelObject::AddressSpace(as_id) => as_id, + _ => return Err(Status::InvalidArgument), + }; + + if as_handle.rights.0 & Rights::EXECUTE.0 == 0 { + return Err(Status::InvalidArgument); + } + + if crate::memory::with_address_space(as_id, |_| ()).is_none() { + return Err(Status::BadHandle); + } + + let kernel_stack = match crate::memory::with_kernel_address_space(|kernel_as| { + crate::memory::with_allocator(|allocator| { + crate::task::scheduler::allocate_kernel_stack(kernel_as, allocator) + }) + }) { + Ok(stack) => stack, + Err(err) => { + println!("Failed to allocate kernel stack: {:?}", err); + return Err(Status::OutOfMemory); + } + }; + + let new_tcb = crate::task::tcb::Tcb::new_user( + 0, // assigned by scheduler::add_task + as_id, + kernel_stack, + VirtualAddr::new(entry), + VirtualAddr::new(user_stack), + ); + + let new_task_id = match crate::task::scheduler::add_task(new_tcb) { + Ok(id) => id, + Err(_) => return Err(Status::OutOfMemory), + }; + + let handle = Handle { + object: KernelObject::Thread(new_task_id), + rights: Rights::READ | Rights::WRITE | Rights::EXECUTE, + }; + + let handle_id = match task.handles.push(handle) { + Some(id) => id, + None => { + crate::task::scheduler::remove_task(new_task_id); + return Err(Status::OutOfMemory); + } + }; + + copy_val_to_user(VirtualAddr::new(out_task_handle), &handle_id)?; Ok(()) } diff --git a/src/task/bootstrap.rs b/src/task/bootstrap.rs index f934122..29262c7 100644 --- a/src/task/bootstrap.rs +++ b/src/task/bootstrap.rs @@ -1,80 +1,61 @@ use crate::{ format, memory::{ - self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, PagePermissions, UserStack, - VirtualAddr, + self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, InitramfsImage, PagePermissions, + UserStack, VirtualAddr, }, - println, task::tcb::Tcb, }; pub fn spawn( name: &str, - initramfs: &[u8], + initramfs: &InitramfsImage, kernel_as: &mut AddressSpace, allocator: &mut FrameAllocator, direct_map: DirectMap, - stacks: &mut crate::memory::KernelStackPool, ) -> usize { - let bytes = format::cpio::find_file(initramfs, name) + let bytes = format::cpio::find_file(initramfs.data(), name) .unwrap_or_else(|| panic!("{name} missing from initramfs")); - let kernel_stack = stacks - .allocate(kernel_as, allocator) + let kernel_stack = crate::task::scheduler::allocate_kernel_stack(kernel_as, allocator) .expect("kernel stack allocation failed"); + let initramfs_physical_addr = kernel_as + .to_physical(initramfs.start) + .expect("failed to translate initramfs start address"); + let mut address_space = kernel_as .new_user(allocator) .expect("address space allocation failed"); + address_space + .map_range( + initramfs_physical_addr, + VirtualAddr::new(0x4000_0000), + ((initramfs.length) + 0xFFF) & !0xFFF, + PagePermissions::new(true, false, true), + allocator, + memory::CachePolicy::WriteBack, + ) + .expect("failed to map initramfs"); let user_stack = UserStack::allocate(&mut address_space, allocator).expect("user stack allocation failed"); let entry = load_elf(bytes, &mut address_space, allocator, direct_map).expect("invalid ELF"); - let task = Tcb::new_user(0, address_space, kernel_stack, entry, user_stack.top()); - crate::task::scheduler::add_task(task).expect("scheduler is full") -} + let as_id = + crate::memory::insert_address_space(address_space).expect("address space table is full"); -pub fn root( - initramfs: &[u8], - kernel_as: &mut AddressSpace, - allocator: &mut FrameAllocator, - direct_map: DirectMap, - stacks: &mut crate::memory::KernelStackPool, -) { - spawn( - "omega3.elf", - initramfs, - kernel_as, - allocator, - direct_map, - stacks, - ); + let task = Tcb::new_user(0, as_id, kernel_stack, entry, user_stack.top()); + crate::task::scheduler::add_task(task).expect("scheduler is full") } #[derive(Debug)] enum ElfLoadError { AddressTranslationFailed, FailedToMapSegment, - AddressOverflow, - InvalidStack, OutOfMemory, InvalidElf, } -#[cfg(target_arch = "x86_64")] -fn is_loadable(elf: &format::elf::Elf) -> bool { - // on x86_64, we only support ELFs that are either 32 bit x86 or 64 bit x86 - matches!( - elf.machine(), - format::elf::ElfIsa::X86 | format::elf::ElfIsa::Amd64 - ) -} - -#[cfg(not(target_arch = "x86_64"))] -fn is_loadable(elf: &format::elf::Elf) -> bool { - false -} - fn load_elf( bytes: &[u8], user_address_space: &mut AddressSpace, @@ -82,113 +63,74 @@ fn load_elf( direct_map: DirectMap, ) -> Result { let program = format::elf::Elf::parse(bytes).map_err(|_| ElfLoadError::InvalidElf)?; - if !is_loadable(&program) { + let mut executable_entry = false; + + for segment in program.segments() { + let segment = segment.map_err(|_| ElfLoadError::InvalidElf)?; + let end = segment + .address + .checked_add(segment.memory_size) + .filter(|&end| end <= memory::USER_SPACE_END.as_usize()) + .ok_or(ElfLoadError::InvalidElf)?; + if segment.memory_size == 0 { + continue; + } + executable_entry |= segment.executable && (segment.address..end).contains(&program.entry); + + let page_start = segment.address & !(FRAME_SIZE - 1); + let file_end = segment.address + segment.data.len(); + let permissions = PagePermissions::new(segment.writable, segment.executable, true); + + // Overlapping segment pages are rejected by map(), including stack/archive collisions. + for page in (page_start..end).step_by(FRAME_SIZE) { + let frame = allocator.alloc_nozero().ok_or(ElfLoadError::OutOfMemory)?; + let physical = frame.frame_address().start_address(); + let Some(destination) = direct_map.translate(physical) else { + unsafe { allocator.dealloc(frame) }; + return Err(ElfLoadError::AddressTranslationFailed); + }; + + let copy_start = page.max(segment.address).min(page + FRAME_SIZE); + let copy_end = (page + FRAME_SIZE).min(file_end).max(copy_start); + let prefix = copy_start - page; + let copied = copy_end - copy_start; + unsafe { + let destination = destination.as_mut_ptr::(); + // Initialize padding and BSS, but don't zero bytes we're about to overwrite. + core::ptr::write_bytes(destination, 0, prefix); + if copied != 0 { + core::ptr::copy_nonoverlapping( + segment.data.as_ptr().add(copy_start - segment.address), + destination.add(prefix), + copied, + ); + } + core::ptr::write_bytes( + destination.add(prefix + copied), + 0, + FRAME_SIZE - prefix - copied, + ); + } + + if user_address_space + .map( + physical, + VirtualAddr::new(page), + permissions, + allocator, + memory::CachePolicy::WriteBack, + ) + .is_err() + { + unsafe { allocator.dealloc(frame) }; + return Err(ElfLoadError::FailedToMapSegment); + } + let _ = frame.into_raw(); + } + } + + if !executable_entry { return Err(ElfLoadError::InvalidElf); } - - for header in program - .program_headers() - .map_err(|_| ElfLoadError::InvalidElf)? - { - println!("Processing program header: {:?}", header); - let header = header.map_err(|_| ElfLoadError::InvalidElf)?; - - if header.file_size > header.memory_size { - return Err(ElfLoadError::InvalidElf); - } - - match header.segment_type { - format::elf::ProgramHeaderType::Load => { - // TODO: give a fuck about alignment - // TODO: handle program segments that overlap - let segment_start = header.virtual_address as usize; - if (program.entry() >= segment_start - && program.entry() < segment_start + header.memory_size as usize) - && header.flags & 0x01 == 0 - { - // entry is within NX segment - return Err(ElfLoadError::InvalidElf); - } - - let page_start = segment_start & !(FRAME_SIZE - 1); - let page_offset = segment_start - page_start; - - let mapped_length = page_offset - .checked_add(header.memory_size as usize) - .ok_or(ElfLoadError::AddressOverflow)? - .div_ceil(FRAME_SIZE) - * FRAME_SIZE; - - let frame_count = mapped_length / FRAME_SIZE; - - let executable = header.flags & 0x01 != 0; - let writable = header.flags & 0x02 != 0; - // TODO: support only-executable segments - // let readable = header.flags & 0x04 != 0; - - for i in 0..frame_count { - let frame = allocator.alloc().ok_or(ElfLoadError::OutOfMemory)?; - - println!( - "Mapping code frame: {:X?} to {:X?}", - frame, - page_start + i * FRAME_SIZE - ); - - if user_address_space - .map( - frame.frame_address().start_address(), - VirtualAddr::new(page_start + i * FRAME_SIZE), - PagePermissions::new(writable, executable, true), - allocator, - memory::CachePolicy::WriteBack, - ) - .is_err() - { - unsafe { allocator.dealloc(frame) }; - return Err(ElfLoadError::FailedToMapSegment); - } - - let _ = frame.into_raw(); - } - - let mut copied = 0; - - while copied < header.file_size as usize { - let destination = VirtualAddr::new(segment_start + copied); - let physical = user_address_space - .to_physical(destination) - .ok_or(ElfLoadError::AddressTranslationFailed)?; - let direct_mapped = direct_map - .translate(physical) - .ok_or(ElfLoadError::AddressTranslationFailed)?; - - let page_remaining = FRAME_SIZE - destination.as_usize() % FRAME_SIZE; - let copy_length = page_remaining.min(header.file_size as usize - copied); - - unsafe { - core::ptr::copy_nonoverlapping( - program - .bytes() - .as_ptr() - .add(header.file_offset as usize + copied), - direct_mapped.as_mut_ptr(), - copy_length, - ); - } - - copied += copy_length; - } - } - format::elf::ProgramHeaderType::GnuStack => { - // if the stack is NOT R/W NX, we refuse to map it - if header.flags != 6 { - return Err(ElfLoadError::InvalidStack); - } - } - _ => {} - } - } - - Ok(VirtualAddr::new(program.entry())) + Ok(VirtualAddr::new(program.entry)) } diff --git a/src/task/scheduler.rs b/src/task/scheduler.rs index f65a5b6..29beb37 100644 --- a/src/task/scheduler.rs +++ b/src/task/scheduler.rs @@ -2,9 +2,11 @@ use core::cell::UnsafeCell; use crate::{ arch::ThreadContext, - memory::{AddressSpace, VirtualAddr}, + memory::{ + AddressSpace, FrameAllocator, KernelStack, KernelStackPool, StackCreateError, VirtualAddr, + }, println, - task::tcb::{BlockReason, ExitReason, Tcb, ThreadState}, + task::tcb::{BlockReason, ExitReason, Handle, KernelObject, Rights, Tcb, ThreadState}, }; const MAX_TASKS: usize = 32; @@ -15,6 +17,7 @@ struct Scheduler { current: Option, tasks: [Option; MAX_TASKS], ready: ReadyQueue, + stacks: KernelStackPool, } impl Scheduler { @@ -23,6 +26,7 @@ impl Scheduler { current: None, tasks: [const { None }; MAX_TASKS], ready: ReadyQueue::new(), + stacks: KernelStackPool::new(), } } @@ -31,19 +35,19 @@ impl Scheduler { let current = self.tasks[current_id].as_mut().unwrap(); let prev_ctx = &mut current.context as *mut ThreadContext; - let prev_addr_space = ¤t.address_space as *const AddressSpace; + let prev_as_id = current.as_id; let next = self.tasks[next_id].as_ref().unwrap(); let next_ctx = &next.context as *const ThreadContext; - let next_addr_space = &next.address_space as *const AddressSpace; + let next_as_id = next.as_id; let next_kernel_stack = next.kernel_stack.top(); Switch { previous_context: prev_ctx, next_context: next_ctx, - next_address_space: next_addr_space, + next_as_id, next_kernel_stack, - activate_address_space: unsafe { *next_addr_space != *prev_addr_space }, + activate_address_space: next_as_id != prev_as_id, } } } @@ -51,7 +55,7 @@ impl Scheduler { struct Switch { previous_context: *mut ThreadContext, next_context: *const ThreadContext, - next_address_space: *const AddressSpace, + next_as_id: usize, next_kernel_stack: VirtualAddr, activate_address_space: bool, } @@ -59,9 +63,9 @@ struct Switch { impl Switch { unsafe fn perform(self) { if self.activate_address_space { - unsafe { - (&*self.next_address_space).activate(); - } + crate::memory::with_address_space(self.next_as_id, |as_ref| unsafe { + as_ref.activate(); + }); } crate::arch::set_kernel_stack(self.next_kernel_stack); @@ -110,6 +114,23 @@ impl ReadyQueue { Some(task) } + + pub fn remove(&mut self, task: TaskId) -> bool { + for i in 0..self.len { + let idx = (self.head + i) % MAX_TASKS; + if self.entries[idx] == task { + for j in i..(self.len - 1) { + let from = (self.head + j + 1) % MAX_TASKS; + let to = (self.head + j) % MAX_TASKS; + self.entries[to] = self.entries[from]; + } + self.len -= 1; + return true; + } + } + + false + } } struct GlobalScheduler(UnsafeCell); @@ -127,6 +148,12 @@ pub fn add_task(mut task: Tcb) -> Result { match scheduler.tasks.iter().position(Option::is_none) { Some(id) => { task.id = id; + + task.handles.push(Handle { + object: KernelObject::Thread(id), + rights: Rights::READ | Rights::WRITE | Rights::EXECUTE, + }); + task.state = ThreadState::Ready; scheduler.tasks[id] = Some(task); assert!(scheduler.ready.push_back(id)); @@ -159,7 +186,7 @@ pub fn start() -> ! { Switch { previous_context: &mut bootstrap_context, next_context: &next.context, - next_address_space: &next.address_space, + next_as_id: next.as_id, next_kernel_stack: next.kernel_stack.top(), activate_address_space: true, } @@ -172,6 +199,38 @@ pub fn start() -> ! { panic!("scheduler returned to bootstrap context"); } +pub fn allocate_kernel_stack( + address_space: &mut AddressSpace, + allocator: &mut FrameAllocator, +) -> Result { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + let scheduler = unsafe { &mut *SCHEDULER.0.get() }; + let result = scheduler.stacks.allocate(address_space, allocator); + crate::arch::restore_interrupts(interrupt_state); + result +} + +pub fn remove_task(id: TaskId) -> Option { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + + let result = { + let scheduler = unsafe { &mut *SCHEDULER.0.get() }; + + if scheduler.current == Some(id) { + None + } else if let Some(task) = scheduler.tasks.get_mut(id).and_then(Option::take) { + scheduler.ready.remove(id); + scheduler.stacks.free(&task.kernel_stack); + Some(task) + } else { + None + } + }; + + crate::arch::restore_interrupts(interrupt_state); + result +} + pub fn get_task(id: TaskId) -> Option<&'static Tcb> { let scheduler = unsafe { &mut *SCHEDULER.0.get() }; scheduler.tasks.get(id).and_then(Option::as_ref) diff --git a/src/task/tcb.rs b/src/task/tcb.rs index 3430f53..1430c20 100644 --- a/src/task/tcb.rs +++ b/src/task/tcb.rs @@ -1,6 +1,8 @@ +use core::ops::BitOr; + use crate::{ arch::ThreadContext, - memory::{AddressSpace, KernelStack, VirtualAddr}, + memory::{FrameAddr, KernelStack, VirtualAddr}, }; // Thread Control Block @@ -29,14 +31,13 @@ pub enum ThreadState { pub const MAX_MSG_SIZE: usize = 128; pub const MAILBOX_CAPACITY: usize = 4; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct Message { pub sender: usize, pub length: usize, pub data: [u8; MAX_MSG_SIZE], } -#[derive(Debug)] pub struct Mailbox { pub messages: [Option; MAILBOX_CAPACITY], pub head: usize, @@ -75,33 +76,112 @@ impl Mailbox { } } -#[derive(Debug)] +const MAX_HANDLES: usize = 32; + +pub enum KernelObject { + AddressSpace(usize), + Frame(FrameAddr), + Thread(usize), +} + +pub struct Handle { + pub object: KernelObject, + pub rights: Rights, +} + +#[derive(Clone, Copy)] +pub struct Rights(pub u32); + +impl Rights { + pub const READ: Self = Self(1 << 0); + pub const WRITE: Self = Self(1 << 1); + pub const EXECUTE: Self = Self(1 << 2); + pub const MAP: Self = Self(1 << 3); +} + +impl BitOr for Rights { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +pub struct HandleTable { + handles: [Option; MAX_HANDLES], +} + +impl HandleTable { + pub const fn new() -> Self { + Self { + handles: [const { None }; MAX_HANDLES], + } + } + + pub fn push(&mut self, handle: Handle) -> Option { + for (i, slot) in self.handles.iter_mut().enumerate() { + if slot.is_none() { + *slot = Some(handle); + return Some(i); + } + } + + None + } + + pub fn get(&self, id: usize) -> Option<&Handle> { + self.handles.get(id).and_then(Option::as_ref) + } + + pub fn get_mut(&mut self, id: usize) -> Option<&mut Handle> { + self.handles.get_mut(id).and_then(Option::as_mut) + } +} + pub struct Tcb { pub id: usize, + pub as_id: usize, pub state: ThreadState, pub kernel_stack: KernelStack, pub context: ThreadContext, - pub address_space: AddressSpace, pub mailbox: Mailbox, + pub handles: HandleTable, +} + +impl core::fmt::Debug for Tcb { + fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + formatter + .debug_struct("Tcb") + .field("id", &self.id) + .field("as_id", &self.as_id) + .field("state", &self.state) + .finish_non_exhaustive() + } } impl Tcb { pub fn new_user( id: usize, - address_space: AddressSpace, + as_id: usize, kernel_stack: KernelStack, entry: VirtualAddr, user_stack: VirtualAddr, ) -> Self { let context = ThreadContext::new(entry, user_stack, kernel_stack.top()); + let mut handles = HandleTable::new(); + handles.push(Handle { + object: KernelObject::AddressSpace(as_id), + rights: Rights::READ | Rights::WRITE | Rights::EXECUTE, + }); Self { id, + as_id, state: ThreadState::Ready, kernel_stack, context, - address_space, mailbox: Mailbox::new(), + handles, } } } diff --git a/userspace/client/src/main.rs b/userspace/client/src/main.rs index 30be886..470d07b 100644 --- a/userspace/client/src/main.rs +++ b/userspace/client/src/main.rs @@ -7,15 +7,18 @@ use dusk_sys::{println, sys_exit, sys_recv, sys_send}; pub extern "C" fn _start() -> ! { let msg = "Hello from client!"; println!("[client] Sent: {}", msg); - sys_send(0, msg.as_ptr() as usize, msg.len()).unwrap(); + // TODO: we assume the echo server is task 1 (spawned by omega3) + sys_send(1, msg.as_ptr() as usize, msg.len()).unwrap(); + let out = [0u8; 128]; let out_ptr = out.as_ptr() as usize; let max_len = out.len(); - let (actual_len, sender) = sys_recv(out_ptr, max_len).unwrap(); + let (actual_len, _) = sys_recv(out_ptr, max_len).unwrap(); println!( "[client] Received: {}", core::str::from_utf8(&out[..actual_len]).unwrap() ); + sys_exit(0); } diff --git a/userspace/dusk-sys/src/lib.rs b/userspace/dusk-sys/src/lib.rs index a04944e..37d0657 100644 --- a/userspace/dusk-sys/src/lib.rs +++ b/userspace/dusk-sys/src/lib.rs @@ -12,6 +12,14 @@ pub enum Status { OutOfMemory = 5, } +// Opaque handle type +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Handle(usize); + +// our own address space and thread handle are always given to us +pub const SELF_AS: Handle = Handle(0); +pub const SELF_THREAD: Handle = Handle(1); + impl From for Status { fn from(value: usize) -> Self { match value { @@ -25,6 +33,21 @@ impl From for Status { } } +#[repr(u64)] +pub enum SyscallNumber { + Yield = 1, + Exit = 2, + Write = 3, + Send = 4, + Recv = 5, + FrameAlloc = 6, + FrameDealloc = 7, + AsCreate = 8, + Map = 9, + Unmap = 10, + TaskCreate = 11, +} + pub fn sys_yield() { unsafe { asm!( @@ -46,7 +69,7 @@ fn debug_write(buf: &str) -> Result<(), Status> { in("rsi") buf.as_ptr(), in("rdx") buf.len(), in("r10") 0, - inlateout("rax") 3usize => status, + inlateout("rax") SyscallNumber::Write as usize => status, lateout("rcx") _, lateout("r11") _ ); @@ -96,7 +119,7 @@ pub fn sys_exit(exit_code: usize) -> ! { asm!( "syscall", in("rdi") exit_code, - in("rax") 2usize, + in("rax") SyscallNumber::Exit as usize, options(noreturn) ); } @@ -111,7 +134,7 @@ pub fn sys_send(dest_task_id: usize, msg_ptr: usize, len: usize) -> Result<(), S in("rdi") dest_task_id, in("rsi") msg_ptr, in("rdx") len, - inlateout("rax") 4usize => status, + inlateout("rax") SyscallNumber::Send as usize => status, lateout("rcx") _, lateout("r11") _, ); @@ -137,7 +160,7 @@ pub fn sys_recv(buf_ptr: usize, max_len: usize) -> Result<(usize, usize), Status in("rsi") max_len, in("rdx") &raw mut actual_len as usize, in("r10") &raw mut sender as usize, - inlateout("rax") 5usize => status, + inlateout("rax") SyscallNumber::Recv as usize => status, lateout("rcx") _, lateout("r11") _, ); @@ -149,3 +172,143 @@ pub fn sys_recv(buf_ptr: usize, max_len: usize) -> Result<(usize, usize), Status } } } + +pub fn sys_frame_alloc() -> Result { + let mut handle: usize = 0; + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") &raw mut handle as usize, + inlateout("rax") SyscallNumber::FrameAlloc as usize => status, + lateout("rcx") _, + lateout("r11") _, + ); + + if status != 0 { + Err(status.into()) + } else { + Ok(Handle(handle)) + } + } +} + +pub fn sys_frame_dealloc(frame_handle: Handle) -> Result<(), Status> { + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") frame_handle.0, + inlateout("rax") SyscallNumber::FrameDealloc as usize => status, + lateout("rcx") _, + lateout("r11") _, + ); + + if status != 0 { + Err(status.into()) + } else { + Ok(()) + } + } +} + +pub fn sys_as_create() -> Result { + let mut handle: usize = 0; + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") &raw mut handle as usize, + inlateout("rax") SyscallNumber::AsCreate as usize => status, + lateout("rcx") _, + lateout("r11") _, + ); + + if status != 0 { + Err(status.into()) + } else { + Ok(Handle(handle)) + } + } +} + +pub fn sys_map( + as_handle: Handle, + frame_handle: Handle, + virtual_addr: usize, + permissions: usize, +) -> Result<(), Status> { + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") as_handle.0, + in("rsi") frame_handle.0, + in("rdx") virtual_addr, + in("r10") permissions, + inlateout("rax") SyscallNumber::Map as usize => status, + lateout("rcx") _, + lateout("r11") _, + ); + + if status != 0 { + Err(status.into()) + } else { + Ok(()) + } + } +} + +pub fn sys_unmap(as_handle: Handle, virtual_addr: usize) -> Result<(), Status> { + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") as_handle.0, + in("rsi") virtual_addr, + inlateout("rax") SyscallNumber::Unmap as usize => status, + lateout("rcx") _, + lateout("r11") _, + ); + + if status != 0 { + Err(status.into()) + } else { + Ok(()) + } + } +} + +pub fn sys_task_create( + as_handle: Handle, + entry: usize, + user_stack: usize, +) -> Result { + let mut handle: usize = 0; + + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") as_handle.0, + in("rsi") entry, + in("rdx") user_stack, + in("r10") &raw mut handle as usize, + inlateout("rax") SyscallNumber::TaskCreate as usize => status, + lateout("rcx") _, + lateout("r11") _, + ); + + if status != 0 { + Err(status.into()) + } else { + Ok(Handle(handle)) + } + } +} diff --git a/userspace/echo/Cargo.toml b/userspace/echo/Cargo.toml new file mode 100644 index 0000000..6a5753d --- /dev/null +++ b/userspace/echo/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "echo" +version = "0.1.0" +edition = "2024" + +[dependencies] +dusk-sys = { path = "../dusk-sys" } + +[[bin]] +name = "echo" +test = false +bench = false diff --git a/userspace/echo/src/main.rs b/userspace/echo/src/main.rs new file mode 100644 index 0000000..3b3e503 --- /dev/null +++ b/userspace/echo/src/main.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use dusk_sys::{println, sys_exit, sys_recv, sys_send}; + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + let out = [0u8; 128]; + loop { + let (actual_len, sender) = sys_recv(out.as_ptr() as usize, out.len()).unwrap(); + println!( + "[echo] Received: {}", + core::str::from_utf8(&out[..actual_len]).unwrap() + ); + sys_send(sender, out.as_ptr() as usize, actual_len).unwrap(); + } +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + println!("{info}"); + sys_exit(1); +} diff --git a/userspace/omega3/src/cpio.rs b/userspace/omega3/src/cpio.rs new file mode 100644 index 0000000..75ff8db --- /dev/null +++ b/userspace/omega3/src/cpio.rs @@ -0,0 +1,73 @@ +// CPIO newc archive parser + +#[repr(C)] +struct Header { + pub c_magic: [u8; 6], + pub c_ino: [u8; 8], + pub c_mode: [u8; 8], + pub c_uid: [u8; 8], + pub c_gid: [u8; 8], + pub c_nlink: [u8; 8], + pub c_mtime: [u8; 8], + pub c_filesize: [u8; 8], + pub c_devmajor: [u8; 8], + pub c_devminor: [u8; 8], + pub c_rdevmajor: [u8; 8], + pub c_rdevminor: [u8; 8], + pub c_namesize: [u8; 8], + pub c_check: [u8; 8], +} + +impl Header { + pub fn from_bytes(bytes: &[u8]) -> Option { + if bytes.len() < core::mem::size_of::
() { + return None; + } + + let header: Header = unsafe { core::ptr::read(bytes.as_ptr() as *const Header) }; + + if header.c_magic != *b"070701" { + return None; + } + + Some(header) + } +} + +pub fn find_file<'a>(archive: *const u8, target: &str) -> Option<&'a [u8]> { + let mut offset = 0; + + loop { + let header = Header::from_bytes(&unsafe { + core::slice::from_raw_parts(archive.add(offset), core::mem::size_of::
()) + })?; + let header_start = offset; + offset += core::mem::size_of::
(); + + let file_len = + usize::from_str_radix(core::str::from_utf8(&header.c_filesize).ok()?, 16).ok()?; + let name_len = + usize::from_str_radix(core::str::from_utf8(&header.c_namesize).ok()?, 16).ok()?; + + let name_bytes = &unsafe { core::slice::from_raw_parts(archive.add(offset), name_len) }; + let name = core::str::from_utf8(name_bytes) + .ok()? + .trim_end_matches('\0'); + + if name == "TRAILER!!!" { + break; + } + + let data_start = header_start + ((core::mem::size_of::
() + name_len + 3) & !3); + + if name == target { + return Some(&unsafe { + core::slice::from_raw_parts(archive.add(data_start), file_len) + }); + } + + offset = data_start + ((file_len + 3) & !3); + } + + None +} diff --git a/userspace/omega3/src/elf.rs b/userspace/omega3/src/elf.rs new file mode 100644 index 0000000..dc80cf5 --- /dev/null +++ b/userspace/omega3/src/elf.rs @@ -0,0 +1,425 @@ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ElfIsa { + None, + Sparc, + X86, + Mips, + Ppc, + Arm, + SuperH, + Ia64, + Amd64, + AArch64, + Riscv, +} + +impl ElfIsa { + fn from_u16(value: u16) -> Result { + match value { + 0x00 => Ok(Self::None), + 0x02 => Ok(Self::Sparc), + 0x03 => Ok(Self::X86), + 0x08 => Ok(Self::Mips), + 0x14 => Ok(Self::Ppc), + 0x28 => Ok(Self::Arm), + 0x2A => Ok(Self::SuperH), + 0x32 => Ok(Self::Ia64), + 0x3E => Ok(Self::Amd64), + 0xB7 => Ok(Self::AArch64), + 0xF3 => Ok(Self::Riscv), + _ => Err(ElfError::InvalidElf), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ElfClass { + Elf32, + Elf64, +} + +impl ElfClass { + fn from_u8(value: u8) -> Result { + match value { + 1 => Ok(Self::Elf32), + 2 => Ok(Self::Elf64), + _ => Err(ElfError::InvalidElf), + } + } + + const fn header_size(self) -> u16 { + match self { + Self::Elf32 => 52, + Self::Elf64 => 64, + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Endianness { + Little, + Big, +} + +impl Endianness { + fn from_u8(value: u8) -> Result { + match value { + 1 => Ok(Self::Little), + 2 => Ok(Self::Big), + _ => Err(ElfError::InvalidElf), + } + } +} + +#[repr(u16)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ElfType { + Relocatable = 1, + Executable = 2, + SharedObject = 3, + Core = 4, +} + +impl ElfType { + fn from_u16(value: u16) -> Result { + match value { + 1 => Ok(Self::Relocatable), + 2 => Ok(Self::Executable), + 3 => Ok(Self::SharedObject), + 4 => Ok(Self::Core), + _ => Err(ElfError::InvalidElf), + } + } +} + +#[derive(Debug)] +#[allow(unused)] +pub struct ElfHeader { + magic: [u8; 4], + pub class: ElfClass, + endianness: Endianness, + version: u8, + os_abi: u8, + _reserved: [u8; 8], + pub object_type: ElfType, + pub machine: ElfIsa, + version_1: u32, + entry: u64, // 2115136 + program_header_offset: u64, // 64 + section_header_offset: u64, // 2759752 + flags: u32, // 0 + header_size: u16, // 64 + program_header_entry_size: u16, // 56 + program_header_count: u16, // 6 + section_header_entry_size: u16, // 64 + section_header_count: u16, // 17 + section_name_index: u16, // 15 +} + +#[derive(Debug)] +pub enum ElfError { + InvalidElf, +} + +impl ElfHeader { + pub fn parse(bytes: &[u8]) -> Result { + let mut reader = Reader::new(bytes); + + let magic = reader.read_array()?; + if magic != *b"\x7fELF" { + return Err(ElfError::InvalidElf); + } + + let class = ElfClass::from_u8(reader.read_u8()?)?; + let endianness = Endianness::from_u8(reader.read_u8()?)?; + reader.set_endianness(endianness); + + let version = reader.read_u8()?; + let os_abi = reader.read_u8()?; + let reserved = reader.read_array()?; + let object_type = ElfType::from_u16(reader.read_u16()?)?; + let machine = ElfIsa::from_u16(reader.read_u16()?)?; + let version_1 = reader.read_u32()?; + let entry = reader.read_word(class)?; + let program_header_offset = reader.read_word(class)?; + let section_header_offset = reader.read_word(class)?; + let flags = reader.read_u32()?; + let header_size = reader.read_u16()?; + let program_header_entry_size = reader.read_u16()?; + let program_header_count = reader.read_u16()?; + let section_header_entry_size = reader.read_u16()?; + let section_header_count = reader.read_u16()?; + let section_name_index = reader.read_u16()?; + + if header_size != class.header_size() { + return Err(ElfError::InvalidElf); + } + + Ok(Self { + magic, + class, + endianness, + version, + os_abi, + _reserved: reserved, + object_type, + machine, + version_1, + entry, + program_header_offset, + section_header_offset, + flags, + header_size, + program_header_entry_size, + program_header_count, + section_header_entry_size, + section_header_count, + section_name_index, + }) + } +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ProgramHeaderType { + Null = 0, + Load = 1, + Dynamic = 2, + Interpreter = 3, + Note = 4, + Shlib = 5, + Phdr = 6, + GnuStack = 0x6474e551, + Relro = 0x6474e552, + Other(u32), +} + +impl ProgramHeaderType { + fn from_u32(value: u32) -> Self { + match value { + 0 => Self::Null, + 1 => Self::Load, + 2 => Self::Dynamic, + 3 => Self::Interpreter, + 4 => Self::Note, + 5 => Self::Shlib, + 6 => Self::Phdr, + 0x6474e551 => Self::GnuStack, + 0x6474e552 => Self::Relro, + _ => Self::Other(value), + } + } +} + +#[derive(Debug)] +#[allow(unused)] +pub struct ProgramHeader { + pub segment_type: ProgramHeaderType, + pub flags: u32, + pub file_offset: u64, + pub virtual_address: u64, + _physical_address: u64, + pub file_size: u64, + pub memory_size: u64, + pub alignment: u64, +} + +impl ProgramHeader { + pub fn parse(bytes: &[u8], class: ElfClass, endianness: Endianness) -> Result { + let mut reader = Reader::new(bytes); + reader.set_endianness(endianness); + + let segment_type = ProgramHeaderType::from_u32(reader.read_u32()?); + + let flags = if class == ElfClass::Elf64 { + reader.read_u32()? + } else { + 0 + }; + + let file_offset = reader.read_word(class)?; + let virtual_address = reader.read_word(class)?; + let physical_address = reader.read_word(class)?; + let file_size = reader.read_word(class)?; + let memory_size = reader.read_word(class)?; + + let flags = if class == ElfClass::Elf32 { + reader.read_u32()? + } else { + flags + }; + + let alignment = reader.read_word(class)?; + + Ok(Self { + segment_type, + flags, + file_offset, + virtual_address, + _physical_address: physical_address, + file_size, + memory_size, + alignment, + }) + } +} + +pub struct Elf<'a> { + bytes: &'a [u8], + header: ElfHeader, +} + +impl<'a> Elf<'a> { + pub fn parse(bytes: &'a [u8]) -> Result { + let header = ElfHeader::parse(bytes)?; + + Ok(Self { bytes, header }) + } + + pub fn program_headers(&self) -> Result, ElfError> { + let offset = + usize::try_from(self.header.program_header_offset).map_err(|_| ElfError::InvalidElf)?; + let entry_size = usize::from(self.header.program_header_entry_size); + let count = usize::from(self.header.program_header_count); + + let expected_entry_size = match self.header.class { + ElfClass::Elf32 => 32, + ElfClass::Elf64 => 56, + }; + + if entry_size != expected_entry_size { + return Err(ElfError::InvalidElf); + } + + let table_size = entry_size.checked_mul(count).ok_or(ElfError::InvalidElf)?; + let table_end = offset.checked_add(table_size).ok_or(ElfError::InvalidElf)?; + let bytes = self + .bytes + .get(offset..table_end) + .ok_or(ElfError::InvalidElf)?; + + Ok(ProgramHeaders { + bytes, + class: self.header.class, + endianness: self.header.endianness, + entry_size, + remaining: count, + }) + } + + pub fn bytes(&self) -> &[u8] { + self.bytes + } + + #[allow(unused)] + pub fn machine(&self) -> ElfIsa { + self.header.machine + } + + pub fn entry(&self) -> usize { + self.header.entry as usize + } +} + +pub struct ProgramHeaders<'a> { + bytes: &'a [u8], + class: ElfClass, + endianness: Endianness, + entry_size: usize, + remaining: usize, +} + +impl<'a> Iterator for ProgramHeaders<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + if self.remaining == 0 { + return None; + } + + let entry = match self.bytes.get(..self.entry_size) { + Some(entry) => entry, + None => { + self.remaining = 0; + return None; + } + }; + + self.bytes = &self.bytes[self.entry_size..]; + self.remaining -= 1; + + Some(ProgramHeader::parse(entry, self.class, self.endianness)) + } + + fn size_hint(&self) -> (usize, Option) { + (self.remaining, Some(self.remaining)) + } +} + +impl ExactSizeIterator for ProgramHeaders<'_> {} + +struct Reader<'a> { + bytes: &'a [u8], + offset: usize, + endianness: Endianness, +} + +impl<'a> Reader<'a> { + const fn new(bytes: &'a [u8]) -> Self { + Self { + bytes, + offset: 0, + endianness: Endianness::Little, + } + } + + fn set_endianness(&mut self, endianness: Endianness) { + self.endianness = endianness; + } + + fn read_array(&mut self) -> Result<[u8; N], ElfError> { + let end = self.offset.checked_add(N).ok_or(ElfError::InvalidElf)?; + let bytes = self + .bytes + .get(self.offset..end) + .ok_or(ElfError::InvalidElf)?; + self.offset = end; + + bytes.try_into().map_err(|_| ElfError::InvalidElf) + } + + fn read_u8(&mut self) -> Result { + Ok(self.read_array::<1>()?[0]) + } + + fn read_u16(&mut self) -> Result { + let bytes = self.read_array()?; + Ok(match self.endianness { + Endianness::Little => u16::from_le_bytes(bytes), + Endianness::Big => u16::from_be_bytes(bytes), + }) + } + + fn read_u32(&mut self) -> Result { + let bytes = self.read_array()?; + Ok(match self.endianness { + Endianness::Little => u32::from_le_bytes(bytes), + Endianness::Big => u32::from_be_bytes(bytes), + }) + } + + fn read_u64(&mut self) -> Result { + let bytes = self.read_array()?; + Ok(match self.endianness { + Endianness::Little => u64::from_le_bytes(bytes), + Endianness::Big => u64::from_be_bytes(bytes), + }) + } + + fn read_word(&mut self, class: ElfClass) -> Result { + match class { + ElfClass::Elf32 => Ok(u64::from(self.read_u32()?)), + ElfClass::Elf64 => self.read_u64(), + } + } +} diff --git a/userspace/omega3/src/main.rs b/userspace/omega3/src/main.rs index da683fe..aca8ccd 100644 --- a/userspace/omega3/src/main.rs +++ b/userspace/omega3/src/main.rs @@ -1,7 +1,19 @@ #![no_std] #![no_main] -use dusk_sys::{println, sys_exit, sys_recv, sys_send}; +mod cpio; +mod elf; + +use dusk_sys::{ + Handle, SELF_AS, println, sys_as_create, sys_exit, sys_frame_alloc, sys_map, sys_task_create, + sys_unmap, sys_yield, +}; + +// Mapped into the root task's address space by the kernel. +static INITRAMFS_START: usize = 0x4000_0000; +const SCRATCH_PAGE: usize = 0x8000_0000; +const STACK_TOP: usize = 0x0000_7FFF_FFFF_F000; +const STACK_PAGES: usize = 4; #[unsafe(no_mangle)] pub extern "C" fn _start() -> ! { @@ -14,21 +26,81 @@ pub extern "C" fn _start() -> ! { println!(r#" d88888 88888b "Y88888P" "#); println!(r#"----- Omega3 Dusk Root Server"#); - let buf = [0u8; 128]; - let buf_ptr = buf.as_ptr() as usize; - let max_len = buf.len(); - loop { - let (actual_len, sender) = sys_recv(buf_ptr, max_len).unwrap(); - println!( - "[server] Received: {}", - core::str::from_utf8(&buf[..actual_len]).unwrap() - ); - sys_send(sender, buf_ptr, actual_len).unwrap(); - } + let echo_bytes = cpio::find_file(INITRAMFS_START as *const u8, "echo.elf").unwrap(); + let echo_elf = elf::Elf::parse(echo_bytes).unwrap(); + let echo_as = sys_as_create().unwrap(); + let echo_entry = load_elf(&echo_elf, echo_as); + map_stack(echo_as, STACK_TOP, STACK_PAGES); + let _ = sys_task_create(echo_as, echo_entry, STACK_TOP).unwrap(); + + let client_bytes = cpio::find_file(INITRAMFS_START as *const u8, "client.elf").unwrap(); + let client_elf = elf::Elf::parse(client_bytes).unwrap(); + let client_as = sys_as_create().unwrap(); + let client_entry = load_elf(&client_elf, client_as); + map_stack(client_as, STACK_TOP, STACK_PAGES); + let _ = sys_task_create(client_as, client_entry, STACK_TOP).unwrap(); sys_exit(0); } +fn load_elf(elf: &elf::Elf, target_as: Handle) -> usize { + for header in elf.program_headers().unwrap() { + let header = header.unwrap(); + if header.segment_type != elf::ProgramHeaderType::Load || header.memory_size == 0 { + continue; + } + + let mut perms = 0; + if header.flags & 2 != 0 { + perms |= 1 << 0; + } + if header.flags & 1 != 0 { + perms |= 1 << 1; + } + + let segment_start = header.virtual_address as usize; + let segment_end = segment_start + header.memory_size as usize; + let file_end = segment_start + header.file_size as usize; + let page_start = segment_start & !0xFFF; + + for page in (page_start..segment_end).step_by(0x1000) { + let frame = sys_frame_alloc().unwrap(); + + sys_map(SELF_AS, frame, SCRATCH_PAGE, 0b01).unwrap(); + unsafe { + core::ptr::write_bytes(SCRATCH_PAGE as *mut u8, 0, 0x1000); + + let copy_start = page.max(segment_start).min(page + 0x1000); + let copy_end = (page + 0x1000).min(file_end).max(copy_start); + if copy_end > copy_start { + let page_offset = copy_start - page; + let file_offset = header.file_offset as usize + (copy_start - segment_start); + let len = copy_end - copy_start; + + core::ptr::copy_nonoverlapping( + elf.bytes().as_ptr().add(file_offset), + (SCRATCH_PAGE as *mut u8).add(page_offset), + len, + ); + } + } + sys_unmap(SELF_AS, SCRATCH_PAGE).unwrap(); + + sys_map(target_as, frame, page, perms).unwrap(); + } + } + + elf.entry() +} + +fn map_stack(target_as: Handle, stack_top: usize, pages: usize) { + for i in 1..=pages { + let frame = sys_frame_alloc().unwrap(); + let page_addr = stack_top - i * 0x1000; + sys_map(target_as, frame, page_addr, 0b01).unwrap(); + } +} + #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { println!("{info}");