diff --git a/Cargo.lock b/Cargo.lock index 0187296..c7982bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,13 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "client" +version = "0.1.0" +dependencies = [ + "dusk-sys", +] + [[package]] name = "dusk" version = "0.1.0" @@ -10,7 +17,7 @@ dependencies = [ ] [[package]] -name = "init" +name = "dusk-sys" version = "0.1.0" [[package]] @@ -18,3 +25,10 @@ name = "limine" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29363c0f37e66e18575fadf7141c56ee7ea04ae5fecbeb25eff303f77af203a9" + +[[package]] +name = "omega3" +version = "0.1.0" +dependencies = [ + "dusk-sys", +] diff --git a/Cargo.toml b/Cargo.toml index 9183874..1dda9b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [workspace] -members = [".", "userspace/init"] +members = [".", "userspace/*"] [dependencies] limine = "0.6.5" diff --git a/Makefile b/Makefile index 982c293..9868db6 100644 --- a/Makefile +++ b/Makefile @@ -67,10 +67,12 @@ prepare-bin-files: mkdir -p ${INITRAMFS_PATH} compile-user: - RUSTFLAGS="-C relocation-model=static" cargo build --package init ${USERSPACE_CARGO_OPTS} + RUSTFLAGS="-C relocation-model=static" cargo build --package omega3 ${USERSPACE_CARGO_OPTS} + RUSTFLAGS="-C relocation-model=static" cargo build --package client ${USERSPACE_CARGO_OPTS} copy-initramfs-files: compile-user - cp -v target/${ARCH}-unknown-none/${MODE}/init ${INITRAMFS_PATH}/init.elf + cp -v target/${ARCH}-unknown-none/${MODE}/omega3 ${INITRAMFS_PATH}/omega3.elf + cp -v target/${ARCH}-unknown-none/${MODE}/client ${INITRAMFS_PATH}/client.elf compile-initramfs: copy-initramfs-files (cd ${INITRAMFS_PATH} && find . -mindepth 1 | cpio -o -H newc) > ${ARTIFACTS_PATH}/initramfs.img diff --git a/src/main.rs b/src/main.rs index 32a7415..3ca03ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,12 +12,9 @@ mod platform; mod syscall; mod task; -use core::arch::global_asm; - use crate::{ debug::serial, - memory::{AddressSpace, KernelStackPool, MemoryRegionKind, UserStack}, - task::tcb::Tcb, + memory::{AddressSpace, KernelStackPool, MemoryRegionKind}, }; pub struct KernelHandoff { @@ -119,9 +116,6 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { MemoryRegionKind::BootloaderReclaimable, ); - let init_code = format::cpio::find_file(boot_info.initramfs.data(), "init.elf") - .expect("Failed to load init program from initramfs"); - println!("Initializing local ACPI...",); let acpi = platform::acpi::init(&boot_info, direct_map).expect("failed to initialize ACPI"); @@ -139,35 +133,25 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space) .expect("failed to initialize interrupt controller"); - let user_kernel_stack = kernel_stack_pool - .allocate(&mut address_space, &mut allocator) - .expect("failed to allocate task kernel stack"); - - let mut user_address_space = address_space - .new_user(&mut allocator) - .expect("failed to create user address space"); - - let image = task::loader::load_elf( - init_code, - &mut user_address_space, + task::bootstrap::spawn( + "omega3.elf", + boot_info.initramfs.data(), + &mut address_space, &mut allocator, direct_map, + &mut kernel_stack_pool, ); - let user_stack = UserStack::allocate(&mut user_address_space, &mut allocator) - .expect("failed to allocate user stack"); - let task = Tcb::new_user( - 0, // overwritten by add_task for now - user_address_space, - user_kernel_stack, - image.expect("Failed to load elf"), - user_stack.top(), + task::bootstrap::spawn( + "client.elf", + boot_info.initramfs.data(), + &mut address_space, + &mut allocator, + direct_map, + &mut kernel_stack_pool, ); - task::scheduler::add_task(task).expect("scheduler is full"); task::scheduler::start(); - - hcf(); } #[panic_handler] diff --git a/src/memory/stack.rs b/src/memory/stack.rs index 207647e..2f03a09 100644 --- a/src/memory/stack.rs +++ b/src/memory/stack.rs @@ -176,6 +176,7 @@ impl KernelStack { } } +#[derive(Debug)] pub struct UserStack { mapping: StackMapping, } diff --git a/src/syscall/mod.rs b/src/syscall/mod.rs index caf10bb..43138f2 100644 --- a/src/syscall/mod.rs +++ b/src/syscall/mod.rs @@ -19,6 +19,8 @@ pub enum SyscallNumber { Yield = 1, Exit = 2, Write = 3, + Send = 4, + Recv = 5, } impl TryFrom for SyscallNumber { @@ -28,6 +30,8 @@ impl TryFrom for SyscallNumber { 1 => Ok(Self::Yield), 2 => Ok(Self::Exit), 3 => Ok(Self::Write), + 4 => Ok(Self::Send), + 5 => Ok(Self::Recv), _ => Err(Status::InvalidArgument), } } @@ -42,6 +46,10 @@ pub fn handle(num: u64, arg0: u64, arg1: u64, arg2: u64, arg3: u64, _arg4: u64, SyscallNumber::Write => { sys_write(arg0 as usize, arg1 as usize, arg2 as usize, arg3 as usize) } + SyscallNumber::Send => sys_send(arg0 as usize, arg1 as usize, arg2 as usize), + SyscallNumber::Recv => { + sys_recv(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 dd6baca..2e8143e 100644 --- a/src/syscall/table.rs +++ b/src/syscall/table.rs @@ -1,4 +1,7 @@ -use crate::memory::{VirtualAddr, copy_from_user, copy_val_to_user}; +use crate::{ + memory::{VirtualAddr, copy_from_user, copy_to_user, copy_val_to_user}, + task::tcb::{BlockReason, MAX_MSG_SIZE, Message}, +}; use super::Status; @@ -31,3 +34,75 @@ pub fn sys_write(fd: usize, buf_ptr: usize, len: usize, out_ptr: usize) -> Resul Ok(()) } + +pub fn sys_send(dest_task_id: usize, msg_ptr: usize, len: usize) -> Result<(), Status> { + if len > MAX_MSG_SIZE { + return Err(Status::InvalidArgument); + } + + let mut msg_buf = [0u8; MAX_MSG_SIZE]; + copy_from_user(VirtualAddr::new(msg_ptr), &mut msg_buf[..len])?; + + let dest_task = crate::task::scheduler::get_task_mut(dest_task_id).ok_or(Status::NoSuchTask)?; + let sender = crate::task::scheduler::current(); + + let msg = Message { + sender, + length: len, + data: msg_buf, + }; + + if !dest_task.mailbox.push(msg) { + return Err(Status::OutOfMemory); + } + + if matches!( + dest_task.state, + crate::task::tcb::ThreadState::Blocked(BlockReason::Recv { .. }) + ) { + crate::task::scheduler::unblock(dest_task_id); + } + + Ok(()) +} + +pub fn sys_recv( + out_ptr: usize, + max_len: usize, + out_actual_len: usize, + out_sender: usize, +) -> Result<(), Status> { + if out_ptr == 0 { + 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 msg = current_task.mailbox.pop().ok_or(Status::NoSuchTask)?; + + copy_to_user( + VirtualAddr::new(out_ptr), + &msg.data[..msg.length.min(max_len)], + )?; + if out_actual_len != 0 { + copy_val_to_user(VirtualAddr::new(out_actual_len), &msg.length)?; + } + if out_sender != 0 { + copy_val_to_user(VirtualAddr::new(out_sender), &msg.sender)?; + } + + crate::arch::restore_interrupts(interrupt_state); + + Ok(()) +} diff --git a/src/task/bootstrap.rs b/src/task/bootstrap.rs new file mode 100644 index 0000000..f934122 --- /dev/null +++ b/src/task/bootstrap.rs @@ -0,0 +1,194 @@ +use crate::{ + format, + memory::{ + self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, PagePermissions, UserStack, + VirtualAddr, + }, + println, + task::tcb::Tcb, +}; + +pub fn spawn( + name: &str, + initramfs: &[u8], + kernel_as: &mut AddressSpace, + allocator: &mut FrameAllocator, + direct_map: DirectMap, + stacks: &mut crate::memory::KernelStackPool, +) -> usize { + let bytes = format::cpio::find_file(initramfs, name) + .unwrap_or_else(|| panic!("{name} missing from initramfs")); + let kernel_stack = stacks + .allocate(kernel_as, allocator) + .expect("kernel stack allocation failed"); + + let mut address_space = kernel_as + .new_user(allocator) + .expect("address space allocation failed"); + 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") +} + +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, + ); +} + +#[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, + allocator: &mut FrameAllocator, + direct_map: DirectMap, +) -> Result { + let program = format::elf::Elf::parse(bytes).map_err(|_| ElfLoadError::InvalidElf)?; + if !is_loadable(&program) { + 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())) +} diff --git a/src/task/loader.rs b/src/task/loader.rs deleted file mode 100644 index c316d08..0000000 --- a/src/task/loader.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::{ - format, - memory::{ - self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, OwnedFrame, PagePermissions, - VirtualAddr, - }, - println, -}; - -const MAX_LOAD_SEGMENTS: usize = 32; - -#[derive(Debug)] -struct LoadedRegion { - start: VirtualAddr, - mapped_pages: usize, -} - -#[derive(Debug)] -pub struct LoadedImage { - pub entry: VirtualAddr, - regions: [LoadedRegion; MAX_LOAD_SEGMENTS], - region_count: usize, -} - -impl LoadedImage { - /// # Safety - /// The supplied address space must contain this image's original mappings. - /// Its frames must be exclusively owned by this image and no longer in use. - pub unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) { - for region in self.regions[..self.region_count].iter().rev() { - for page in (0..region.mapped_pages).rev() { - let address = VirtualAddr::new(region.start.as_usize() + page * FRAME_SIZE); - let frame = unsafe { - address_space - .unmap(address, allocator) - .expect("loaded image mapping was unexpectedly missing") - }; - unsafe { allocator.dealloc(OwnedFrame::from_raw(frame)) }; - } - } - } -} - -#[derive(Debug)] -pub enum ElfLoadError { - AddressTranslationFailed, - FailedToMapSegment, - AddressOverflow, - InvalidStack, - OutOfMemory, - InvalidElf, - TooManyLoadSegments, -} - -#[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 -} - -pub fn load_elf( - bytes: &[u8], - user_address_space: &mut AddressSpace, - allocator: &mut FrameAllocator, - direct_map: DirectMap, -) -> Result { - let program = format::elf::Elf::parse(bytes).map_err(|_| ElfLoadError::InvalidElf)?; - if !is_loadable(&program) { - return Err(ElfLoadError::InvalidElf); - } - - let mut image = LoadedImage { - entry: VirtualAddr::new(program.entry()), - regions: core::array::from_fn(|_| LoadedRegion { - start: VirtualAddr::new(0), - mapped_pages: 0, - }), - region_count: 0, - }; - - let result = (|| { - 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 => { - if image.region_count == MAX_LOAD_SEGMENTS { - return Err(ElfLoadError::TooManyLoadSegments); - } - // 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; - - let region = &mut image.regions[image.region_count]; - region.start = VirtualAddr::new(page_start); - image.region_count += 1; - - 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(); - region.mapped_pages += 1; - } - - 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(()) - })(); - - if let Err(error) = result { - // Only pages created by this load are recorded; none have been handed to a task. - unsafe { image.destroy(user_address_space, allocator) }; - return Err(error); - } - - Ok(image) -} diff --git a/src/task/mod.rs b/src/task/mod.rs index b639d4a..46447c7 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -1,3 +1,3 @@ -pub mod loader; +pub mod bootstrap; pub mod scheduler; pub mod tcb; diff --git a/src/task/scheduler.rs b/src/task/scheduler.rs index 891afaf..f65a5b6 100644 --- a/src/task/scheduler.rs +++ b/src/task/scheduler.rs @@ -4,7 +4,7 @@ use crate::{ arch::ThreadContext, memory::{AddressSpace, VirtualAddr}, println, - task::tcb::{ExitReason, Tcb, ThreadState}, + task::tcb::{BlockReason, ExitReason, Tcb, ThreadState}, }; const MAX_TASKS: usize = 32; @@ -42,7 +42,7 @@ impl Scheduler { previous_context: prev_ctx, next_context: next_ctx, next_address_space: next_addr_space, - next_kernel_stack: next_kernel_stack, + next_kernel_stack, activate_address_space: unsafe { *next_addr_space != *prev_addr_space }, } } @@ -172,6 +172,65 @@ pub fn start() -> ! { panic!("scheduler returned to bootstrap context"); } +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) +} + +pub fn get_task_mut(id: TaskId) -> Option<&'static mut Tcb> { + let scheduler = unsafe { &mut *SCHEDULER.0.get() }; + scheduler.tasks.get_mut(id).and_then(Option::as_mut) +} + +pub fn current() -> TaskId { + let scheduler = unsafe { &mut *SCHEDULER.0.get() }; + scheduler.current.expect("no current task") +} + +pub fn block_current(reason: BlockReason) { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + + let switch = { + let scheduler = unsafe { &mut *SCHEDULER.0.get() }; + + let Some(next_id) = scheduler.ready.pop_front() else { + println!("Deadlock: all tasks blocked"); + crate::hcf(); + }; + + let current_id = scheduler.current.expect("no current task"); + + scheduler.tasks[current_id].as_mut().unwrap().state = ThreadState::Blocked(reason); + // explicitly do NOT push back the current task, because it is not ready + + scheduler.tasks[next_id].as_mut().unwrap().state = ThreadState::Running; + scheduler.current = Some(next_id); + + scheduler.make_switch(current_id, next_id) + }; + + unsafe { + switch.perform(); + } + + // this runs when this task is selected to run again + crate::arch::restore_interrupts(interrupt_state); +} + +pub fn unblock(id: TaskId) { + let interrupt_state = crate::arch::disable_interrupts_and_save(); + + let scheduler = unsafe { &mut *SCHEDULER.0.get() }; + if let Some(task) = scheduler.tasks[id].as_mut() { + if matches!(task.state, ThreadState::Blocked(_)) { + task.state = ThreadState::Ready; + assert!(scheduler.ready.push_back(id)); + } + } + + crate::arch::restore_interrupts(interrupt_state); +} + pub fn yield_current() { let interrupt_state = crate::arch::disable_interrupts_and_save(); diff --git a/src/task/tcb.rs b/src/task/tcb.rs index da4884a..3430f53 100644 --- a/src/task/tcb.rs +++ b/src/task/tcb.rs @@ -1,7 +1,6 @@ use crate::{ arch::ThreadContext, memory::{AddressSpace, KernelStack, VirtualAddr}, - task::loader::LoadedImage, }; // Thread Control Block @@ -12,14 +11,70 @@ pub enum ExitReason { Fault, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BlockReason { + Send { ep: usize }, + Recv { ep: usize }, + Reply { client: usize }, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ThreadState { Ready, Running, - Blocked, + Blocked(BlockReason), Dead(ExitReason), } +pub const MAX_MSG_SIZE: usize = 128; +pub const MAILBOX_CAPACITY: usize = 4; + +#[derive(Clone, Copy, Debug)] +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, + pub len: usize, +} + +impl Mailbox { + const fn new() -> Self { + Self { + messages: [None; MAILBOX_CAPACITY], + head: 0, + len: 0, + } + } + + pub fn pop(&mut self) -> Option { + if self.len == 0 { + return None; + } + + let msg = self.messages[self.head]; + self.head = (self.head + 1) % MAILBOX_CAPACITY; + self.len -= 1; + msg + } + + pub fn push(&mut self, msg: Message) -> bool { + if self.len == MAILBOX_CAPACITY { + return false; + } + + let tail = (self.head + self.len) % MAILBOX_CAPACITY; + self.messages[tail] = Some(msg); + self.len += 1; + true + } +} + #[derive(Debug)] pub struct Tcb { pub id: usize, @@ -27,7 +82,7 @@ pub struct Tcb { pub kernel_stack: KernelStack, pub context: ThreadContext, pub address_space: AddressSpace, - pub image: LoadedImage, + pub mailbox: Mailbox, } impl Tcb { @@ -35,10 +90,10 @@ impl Tcb { id: usize, address_space: AddressSpace, kernel_stack: KernelStack, - image: LoadedImage, + entry: VirtualAddr, user_stack: VirtualAddr, ) -> Self { - let context = ThreadContext::new(image.entry, user_stack, kernel_stack.top()); + let context = ThreadContext::new(entry, user_stack, kernel_stack.top()); Self { id, @@ -46,7 +101,7 @@ impl Tcb { kernel_stack, context, address_space, - image, + mailbox: Mailbox::new(), } } } diff --git a/userspace/client/Cargo.toml b/userspace/client/Cargo.toml new file mode 100644 index 0000000..455bee6 --- /dev/null +++ b/userspace/client/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2024" + +[dependencies] +dusk-sys = { path = "../dusk-sys" } + +[[bin]] +name = "client" +test = false +bench = false diff --git a/userspace/client/src/main.rs b/userspace/client/src/main.rs new file mode 100644 index 0000000..30be886 --- /dev/null +++ b/userspace/client/src/main.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use dusk_sys::{println, sys_exit, sys_recv, sys_send}; + +#[unsafe(no_mangle)] +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(); + 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(); + println!( + "[client] Received: {}", + core::str::from_utf8(&out[..actual_len]).unwrap() + ); + sys_exit(0); +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + println!("{info}"); + sys_exit(1); +} diff --git a/userspace/init/Cargo.toml b/userspace/dusk-sys/Cargo.toml similarity index 66% rename from userspace/init/Cargo.toml rename to userspace/dusk-sys/Cargo.toml index a8a2c76..d752339 100644 --- a/userspace/init/Cargo.toml +++ b/userspace/dusk-sys/Cargo.toml @@ -1,9 +1,8 @@ [package] -name = "init" +name = "dusk-sys" version = "0.1.0" edition = "2024" -[[bin]] -name = "init" +[lib] test = false bench = false diff --git a/userspace/dusk-sys/src/lib.rs b/userspace/dusk-sys/src/lib.rs new file mode 100644 index 0000000..a04944e --- /dev/null +++ b/userspace/dusk-sys/src/lib.rs @@ -0,0 +1,151 @@ +#![no_std] + +use core::arch::asm; + +#[derive(Debug, PartialEq, Eq)] +pub enum Status { + // Success = 0, + InvalidArgument = 1, + BadAddress = 2, + BadFileDescriptor = 3, + NoSuchTask = 4, + OutOfMemory = 5, +} + +impl From for Status { + fn from(value: usize) -> Self { + match value { + 1 => Self::InvalidArgument, + 2 => Self::BadAddress, + 3 => Self::BadFileDescriptor, + 4 => Self::NoSuchTask, + 5 => Self::OutOfMemory, + _ => Self::InvalidArgument, + } + } +} + +pub fn sys_yield() { + unsafe { + asm!( + "syscall", + in("rax") 1usize, + lateout("rcx") _, + lateout("r11") _, + ); + } +} + +fn debug_write(buf: &str) -> Result<(), Status> { + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") 1, + in("rsi") buf.as_ptr(), + in("rdx") buf.len(), + in("r10") 0, + inlateout("rax") 3usize => status, + lateout("rcx") _, + lateout("r11") _ + ); + + if status != 0 { + Err(status.into()) + } else { + Ok(()) + } + } +} + +struct DebugWriter; + +impl core::fmt::Write for DebugWriter { + fn write_str(&mut self, value: &str) -> core::fmt::Result { + debug_write(value).map_err(|_| core::fmt::Error) + } +} + +#[doc(hidden)] +pub fn __print(arguments: core::fmt::Arguments<'_>) { + use core::fmt::Write; + + let _ = DebugWriter.write_fmt(arguments); +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => {{ + $crate::__print(core::format_args!($($arg)*)); + }}; +} + +#[macro_export] +macro_rules! println { + () => {{ + $crate::print!("\n"); + }}; + ($($arg:tt)*) => {{ + $crate::print!("{}\n", core::format_args!($($arg)*)); + }}; +} + +pub fn sys_exit(exit_code: usize) -> ! { + unsafe { + asm!( + "syscall", + in("rdi") exit_code, + in("rax") 2usize, + options(noreturn) + ); + } +} + +pub fn sys_send(dest_task_id: usize, msg_ptr: usize, len: usize) -> Result<(), Status> { + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") dest_task_id, + in("rsi") msg_ptr, + in("rdx") len, + inlateout("rax") 4usize => status, + lateout("rcx") _, + lateout("r11") _, + ); + + if status != 0 { + Err(status.into()) + } else { + Ok(()) + } + } +} + +pub fn sys_recv(buf_ptr: usize, max_len: usize) -> Result<(usize, usize), Status> { + let mut actual_len: usize = 0; + let mut sender: usize = 0; + + unsafe { + let status: usize; + + asm!( + "syscall", + in("rdi") buf_ptr, + in("rsi") max_len, + in("rdx") &raw mut actual_len as usize, + in("r10") &raw mut sender as usize, + inlateout("rax") 5usize => status, + lateout("rcx") _, + lateout("r11") _, + ); + + if status != 0 { + Err(status.into()) + } else { + Ok((actual_len, sender)) + } + } +} diff --git a/userspace/init/src/main.rs b/userspace/init/src/main.rs deleted file mode 100644 index 04c79b9..0000000 --- a/userspace/init/src/main.rs +++ /dev/null @@ -1,115 +0,0 @@ -#![no_std] -#![no_main] - -use core::arch::asm; -use core::cell::UnsafeCell; -use core::fmt::Write; - -fn sys_yield() { - unsafe { - asm!("mov rax, 1", "syscall"); - } -} - -fn sys_write(fd: usize, buf: &str) -> Result<(), usize> { - unsafe { - let status; - - asm!( - "mov rax, 3", - "syscall", - in("rdi") fd, - in("rsi") buf.as_ptr(), - in("rdx") buf.len(), - in("r10") 0, - lateout("rax") status, - ); - - if status != 0 { Err(status) } else { Ok(()) } - } -} - -struct Writer; - -impl core::fmt::Write for Writer { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - sys_write(1, s).map_err(|_| core::fmt::Error)?; - Ok(()) - } -} - -#[macro_export] -macro_rules! print { - ($($arg:tt)*) => (let _ = $crate::Writer.write_fmt(format_args!($($arg)*));); -} - -#[macro_export] -macro_rules! println { - () => ($crate::print!("\n")); - ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); -} - -pub fn sys_exit(exit_code: usize) -> ! { - unsafe { - asm!( - "mov rax, 2", - "syscall", - in("rdi") exit_code, - options(noreturn) - ); - } -} - -struct Heap { - pub data: UnsafeCell<[u8; 1024]>, -} - -impl Heap { - const fn new() -> Self { - Self { - data: UnsafeCell::new([0; 1024]), - } - } - - fn len(&self) -> usize { - unsafe { (*self.data.get()).len() } - } -} - -impl core::ops::Deref for Heap { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - unsafe { (*self.data.get()).as_ref() } - } -} - -unsafe impl Sync for Heap {} - -static HEAP: Heap = Heap::new(); - -#[unsafe(no_mangle)] -pub extern "C" fn _start() -> ! { - (0..100).for_each(|i| { - println!("Hello {}", i); - sys_yield(); - }); - - let len = HEAP.len(); - for i in 0..len { - unsafe { HEAP.data.get().as_mut().unwrap()[i] = i as u8 }; - } - - (0..1024).for_each(|i| { - println!("{}", unsafe { HEAP.data.get().as_ref().unwrap()[i] }); - sys_yield(); - }); - - sys_exit(0); -} - -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - println!("{info}"); - sys_exit(1); -} diff --git a/userspace/omega3/Cargo.toml b/userspace/omega3/Cargo.toml new file mode 100644 index 0000000..34bdfd7 --- /dev/null +++ b/userspace/omega3/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "omega3" +version = "0.1.0" +edition = "2024" + +[dependencies] +dusk-sys = { path = "../dusk-sys" } + +[[bin]] +name = "omega3" +test = false +bench = false diff --git a/userspace/omega3/src/main.rs b/userspace/omega3/src/main.rs new file mode 100644 index 0000000..da683fe --- /dev/null +++ b/userspace/omega3/src/main.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use dusk_sys::{println, sys_exit, sys_recv, sys_send}; + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + println!(r#"-----------------------------"#); + println!(r#" .d88888888b. .d88888b. "#); + println!(r#" d88P" "Y88b 88P" "Y88 "#); + println!(r#" 888 888 .od88P "#); + println!(r#" Y88b d88P "Y88b "#); + println!(r#" "88bo od88" 88b d88 "#); + 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(); + } + + sys_exit(0); +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + println!("{info}"); + sys_exit(1); +}